स्विच कॉम्पोनेंट बनाना

रिस्पॉन्सिव और ऐक्सेस किया जा सकने वाला स्विच कॉम्पोनेंट बनाने के बारे में बुनियादी जानकारी.

इस पोस्ट में, मैं स्विच कॉम्पोनेंट बनाने के तरीके के बारे में अपनी राय शेयर करना चाहता हूं. डेमो आज़माएं.

डेमो

अगर आपको वीडियो देखना ज़्यादा पसंद है, तो इस पोस्ट का YouTube वर्शन यहां दिया गया है:

खास जानकारी

स्विच, चेकबॉक्स की तरह ही काम करता है. हालांकि, यह साफ़ तौर पर बताता है कि बूलियन चालू है या बंद है.

इस डेमो में, ज़्यादातर फ़ंक्शन के लिए <input type="checkbox" role="switch"> का इस्तेमाल किया जाता है. इसका फ़ायदा यह है कि इसे पूरी तरह से काम करने और ऐक्सेस करने के लिए, सीएसएस या JavaScript की ज़रूरत नहीं होती. सीएसएस लोड करने की सुविधा से, दाएं से बाएं लिखी जाने वाली भाषाओं, वर्टिकल ओरिएंटेशन, ऐनिमेशन वगैरह के लिए सहायता मिलती है. JavaScript लोड करने से, स्विच को खींचकर छोड़ा जा सकता है और उसे छुआ जा सकता है.

कस्टम प्रॉपर्टी

यहां दिए गए वैरिएबल, स्विच के अलग-अलग हिस्सों और उनके विकल्पों के बारे में बताते हैं. टॉप-लेवल क्लास के तौर पर, .gui-switch में कस्टम प्रॉपर्टी होती हैं. इनका इस्तेमाल कॉम्पोनेंट के सभी चाइल्ड में किया जाता है. साथ ही, इसमें कस्टम प्रॉपर्टी को एक जगह से मैनेज करने के लिए एंट्री पॉइंट भी होते हैं.

ट्रैक

लंबाई (--track-size), पैडिंग, और दो रंग:

.gui-switch {
  --track-size: calc(var(--thumb-size) * 2);
  --track-padding: 2px;

  --track-inactive: hsl(80 0% 80%);
  --track-active: hsl(80 60% 45%);

  --track-color-inactive: var(--track-inactive);
  --track-color-active: var(--track-active);

  @media (prefers-color-scheme: dark) {
    --track-inactive: hsl(80 0% 35%);
    --track-active: hsl(80 60% 60%);
  }
}

थंब

आइकॉन का साइज़, बैकग्राउंड का रंग, और इंटरैक्शन को हाइलाइट करने वाले रंग:

.gui-switch {
  --thumb-size: 2rem;
  --thumb: hsl(0 0% 100%);
  --thumb-highlight: hsl(0 0% 0% / 25%);

  --thumb-color: var(--thumb);
  --thumb-color-highlight: var(--thumb-highlight);

  @media (prefers-color-scheme: dark) {
    --thumb: hsl(0 0% 5%);
    --thumb-highlight: hsl(0 0% 100% / 25%);
  }
}

हलचल कम करें

साफ़ तौर पर दिखने वाला एलियास जोड़ने और दोहराव कम करने के लिए, मोशन कम करने की सुविधा को प्राथमिकता देने वाले उपयोगकर्ता की मीडिया क्वेरी को PostCSS प्लगिन के साथ कस्टम प्रॉपर्टी में रखा जा सकता है. यह Media Queries 5 में ड्राफ़्ट स्पेसिफ़िकेशन पर आधारित है:

@custom-media --motionOK (prefers-reduced-motion: no-preference);

मार्कअप

मैंने अपने <input type="checkbox" role="switch"> एलिमेंट को <label> के साथ रैप किया है. इससे चेकबॉक्स और लेबल के बीच के संबंध को बंडल किया जा सकेगा. साथ ही, उपयोगकर्ता को लेबल के साथ इंटरैक्ट करने की सुविधा मिलेगी, ताकि वह इनपुट को टॉगल कर सके.

नैचुरल, बिना स्टाइल वाला लेबल और चेकबॉक्स.

<label for="switch" class="gui-switch">
  Label text
  <input type="checkbox" role="switch" id="switch">
</label>

<input type="checkbox"> में पहले से ही एपीआई और स्टेट शामिल होते हैं. ब्राउज़र, checked प्रॉपर्टी और इनपुट इवेंट जैसे कि oninput और onchanged को मैनेज करता है.

लेआउट

इस कॉम्पोनेंट की स्टाइल बनाए रखने के लिए, Flexbox, ग्रिड, और कस्टम प्रॉपर्टी बहुत ज़रूरी हैं. ये वैल्यू को एक जगह पर इकट्ठा करते हैं. साथ ही, ये उन कैलकुलेशन या एरिया को नाम देते हैं जिनके बारे में साफ़ तौर पर जानकारी नहीं होती. इसके अलावा, ये कॉम्पोनेंट को आसानी से पसंद के मुताबिक बनाने के लिए, कस्टम प्रॉपर्टी एपीआई को चालू करते हैं.

.gui-switch

स्विच के लिए टॉप-लेवल लेआउट, फ़्लेक्सबॉक्स है. क्लास .gui-switch में, निजी और सार्वजनिक कस्टम प्रॉपर्टी होती हैं. इनका इस्तेमाल बच्चे अपने लेआउट का हिसाब लगाने के लिए करते हैं.

हॉरिज़ॉन्टल लेबल और स्विच पर Flexbox DevTools ओवरले किया गया है. इसमें स्पेस के लेआउट डिस्ट्रिब्यूशन को दिखाया गया है.

.gui-switch {
  display: flex;
  align-items: center;
  gap: 2ch;
  justify-content: space-between;
}

फ़्लेक्सबॉक्स लेआउट को बढ़ाना और उसमें बदलाव करना, किसी भी फ़्लेक्सबॉक्स लेआउट को बदलने जैसा होता है. उदाहरण के लिए, किसी स्विच के ऊपर या नीचे लेबल लगाने के लिए या flex-direction को बदलने के लिए:

वर्टिकल लेबल और स्विच को ओवरले करने वाला Flexbox DevTools.

<label for="light-switch" class="gui-switch" style="flex-direction: column">
  Default
  <input type="checkbox" role="switch" id="light-switch">
</label>

ट्रैक

चेकबॉक्स इनपुट को स्विच ट्रैक के तौर पर स्टाइल किया गया है. इसके लिए, इसके सामान्य appearance: checkbox को हटाकर, इसका साइज़ दिया गया है:

ग्रिड DevTools, स्विच ट्रैक पर ओवरले किया गया है. इसमें, &#39;ट्रैक&#39; नाम वाले ग्रिड ट्रैक एरिया दिख रहे हैं.

.gui-switch > input {
  appearance: none;

  inline-size: var(--track-size);
  block-size: var(--thumb-size);
  padding: var(--track-padding);

  flex-shrink: 0;
  display: grid;
  align-items: center;
  grid: [track] 1fr / [track] 1fr;
}

यह ट्रैक, एक-एक करके सिंगल सेल ग्रिड ट्रैक एरिया भी बनाता है, ताकि थंबनेल पर दावा किया जा सके.

थंब

स्टाइल appearance: none, ब्राउज़र की ओर से दिए गए विज़ुअल चेकमार्क को भी हटा देती है. यह कॉम्पोनेंट, इस विज़ुअल इंडिकेटर को बदलने के लिए इनपुट पर स्यूडो-एलिमेंट और :checked स्यूडो-क्लास का इस्तेमाल करता है.

थंब, input[type="checkbox"] से जुड़ा एक सूडो-एलिमेंट चाइल्ड है. यह ट्रैक के नीचे की बजाय, उसके ऊपर स्टैक होता है. ऐसा इसलिए होता है, क्योंकि यह ग्रिड एरिया track का इस्तेमाल करता है:

DevTools में, सूडो-एलिमेंट थंब को सीएसएस ग्रिड में रखा गया है.

.gui-switch > input::before {
  content: "";
  grid-area: track;
  inline-size: var(--thumb-size);
  block-size: var(--thumb-size);
}

स्टाइल

कस्टम प्रॉपर्टी की मदद से, स्विच करने वाला एक ऐसा कॉम्पोनेंट बनाया जा सकता है जो अलग-अलग कलर स्कीम, दाएं से बाएं लिखी जाने वाली भाषाओं, और मोशन की प्राथमिकताओं के हिसाब से काम करता है.

स्विच और उसकी स्थितियों के लिए, हल्के और गहरे रंग वाली थीम की तुलना करने वाली इमेज.

टच इंटरैक्शन की स्टाइल

मोबाइल पर, ब्राउज़र लेबल और इनपुट में टैप हाइलाइट और टेक्स्ट चुनने की सुविधाएं जोड़ते हैं. इनसे स्टाइल और विज़ुअल इंटरैक्शन के बारे में मिलने वाली प्रतिक्रिया पर बुरा असर पड़ा. इस स्विच के लिए यह ज़रूरी है. सीएसएस की कुछ लाइनों की मदद से, उन इफ़ेक्ट को हटाया जा सकता है और अपनी cursor: pointer स्टाइल जोड़ी जा सकती है:

.gui-switch {
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

इन स्टाइल को हमेशा हटाने का सुझाव नहीं दिया जाता, क्योंकि ये विज़ुअल इंटरैक्शन के बारे में अहम जानकारी दे सकती हैं. अगर आपने उन्हें हटा दिया है, तो कस्टम विकल्प ज़रूर दें.

ट्रैक

इस एलिमेंट की स्टाइल, ज़्यादातर इसके आकार और रंग के बारे में होती हैं. यह पैरंट .gui-switch से कैस्केड के ज़रिए इन स्टाइल को ऐक्सेस करता है.

कस्टम ट्रैक साइज़ और रंगों वाले स्विच वैरिएंट.

.gui-switch > input {
  appearance: none;
  border: none;
  outline-offset: 5px;
  box-sizing: content-box;

  padding: var(--track-padding);
  background: var(--track-color-inactive);
  inline-size: var(--track-size);
  block-size: var(--thumb-size);
  border-radius: var(--track-size);
}

स्विच ट्रैक को पसंद के मुताबिक बनाने के कई विकल्प, चार कस्टम प्रॉपर्टी से मिलते हैं. border: none को इसलिए जोड़ा गया है, क्योंकि appearance: none सभी ब्राउज़र पर चेकबॉक्स से बॉर्डर नहीं हटाता है.

थंब

थंब एलिमेंट पहले से ही दाईं ओर track है, लेकिन इसमें सर्कल स्टाइल जोड़ने की ज़रूरत है:

.gui-switch > input::before {
  background: var(--thumb-color);
  border-radius: 50%;
}

DevTools में सर्कल थंब स्यूडो-एलिमेंट को हाइलाइट करके दिखाया गया है.

बातचीत

कस्टम प्रॉपर्टी का इस्तेमाल करके, इंटरैक्शन के लिए तैयारी करें. इससे, होवर करने पर हाइलाइट दिखने और थंब की पोज़िशन में बदलाव होने की सुविधा मिलेगी. मोशन या होवर हाइलाइट स्टाइल में बदलाव करने से पहले, उपयोगकर्ता की प्राथमिकता की भी जांच की जाती है.

.gui-switch > input::before {
  box-shadow: 0 0 0 var(--highlight-size) var(--thumb-color-highlight);

  @media (--motionOK) { & {
    transition:
      transform var(--thumb-transition-duration) ease,
      box-shadow .25s ease;
  }}
}

अंगूठे की पोज़िशन

कस्टम प्रॉपर्टी, ट्रैक में थंब को पोज़िशन करने के लिए एक ही सोर्स मैकेनिज़्म उपलब्ध कराती हैं. हमारे पास ट्रैक और थंब के साइज़ हैं. हम इनका इस्तेमाल कैलकुलेशन में करेंगे, ताकि थंब को ट्रैक के बीच में सही तरीके से ऑफ़सेट किया जा सके: 0% और 100%.

input एलिमेंट के पास, --thumb-position पोज़िशन वैरिएबल होता है. साथ ही, थंब सूडो एलिमेंट इसका इस्तेमाल translateX पोज़िशन के तौर पर करता है:

.gui-switch > input {
  --thumb-position: 0%;
}

.gui-switch > input::before {
  transform: translateX(var(--thumb-position));
}

अब हम सीएसएस और चेकबॉक्स एलिमेंट पर उपलब्ध सूडो-क्लास से --thumb-position को बदल सकते हैं. हमने इस एलिमेंट पर transition: transform var(--thumb-transition-duration) ease को पहले ही सेट कर दिया था. इसलिए, बदलाव होने पर ये एनिमेट हो सकते हैं:

/* positioned at the end of the track: track length - 100% (thumb width) */
.gui-switch > input:checked {
  --thumb-position: calc(var(--track-size) - 100%);
}

/* positioned in the center of the track: half the track - half the thumb */
.gui-switch > input:indeterminate {
  --thumb-position: calc(
    (var(--track-size) / 2) - (var(--thumb-size) / 2)
  );
}

मुझे लगता है कि यह डीकपल्ड ऑर्केस्ट्रेशन अच्छी तरह से काम करता है. थंब एलिमेंट सिर्फ़ एक स्टाइल, translateX पोज़िशन के बारे में बताता है. इनपुट से, सभी जटिलताओं और कैलकुलेशन को मैनेज किया जा सकता है.

वर्टिकल

इसके लिए, मॉडिफ़ायर क्लास -vertical का इस्तेमाल किया गया है. यह input एलिमेंट में सीएसएस ट्रांसफ़ॉर्म के साथ रोटेशन जोड़ता है.

3D में घुमाए गए एलिमेंट से, कॉम्पोनेंट की कुल ऊंचाई में कोई बदलाव नहीं होता. हालांकि, इससे ब्लॉक लेआउट में गड़बड़ी हो सकती है. इसके लिए, --track-size और --track-padding वैरिएबल का इस्तेमाल करें. लेआउट में वर्टिकल बटन को सही तरीके से दिखाने के लिए, कम से कम कितनी जगह की ज़रूरत होगी, इसकी गणना करें:

.gui-switch.-vertical {
  min-block-size: calc(var(--track-size) + calc(var(--track-padding) * 2));

  & > input {
    transform: rotate(-90deg);
  }
}

(आरटीएल) दाएं से बाएं

सीएसएस के मेरे दोस्त, एलैड शेक्टर और मैंने मिलकर एक स्लाइड आउट साइड मेन्यू का प्रोटोटाइप बनाया. इसमें सीएसएस ट्रांसफ़ॉर्म का इस्तेमाल किया गया था. यह मेन्यू, एक ही वैरिएबल को फ़्लिप करके, दाएं से बाएं लिखी जाने वाली भाषाओं को हैंडल करता था. हमने ऐसा इसलिए किया, क्योंकि सीएसएस में लॉजिकल प्रॉपर्टी ट्रांसफ़ॉर्म नहीं होते हैं. ऐसा हो सकता है कि ये कभी न हों. एलैड को कस्टम प्रॉपर्टी वैल्यू का इस्तेमाल करने का शानदार आइडिया मिला. इससे, प्रतिशत को उलटा किया जा सकता है. साथ ही, लॉजिकल ट्रांसफ़ॉर्म के लिए, हमारे कस्टम लॉजिक की एक ही जगह से मैनेज करने की अनुमति दी जा सकती है. मैंने इस स्विच में भी इसी तकनीक का इस्तेमाल किया है और मुझे लगता है कि यह बहुत कारगर रही:

.gui-switch {
  --isLTR: 1;

  &:dir(rtl) {
    --isLTR: -1;
  }
}

--isLTR नाम की कस्टम प्रॉपर्टी की शुरुआती वैल्यू 1 है. इसका मतलब है कि यह true है, क्योंकि हमारा लेआउट डिफ़ॉल्ट रूप से बाएं से दाएं होता है. इसके बाद, सीएसएस स्यूडो क्लास :dir() का इस्तेमाल करके, कॉम्पोनेंट के दाएं से बाएं लेआउट में होने पर वैल्यू को -1 पर सेट किया जाता है.

--isLTR को ट्रांसफ़ॉर्म के अंदर मौजूद calc() में इस्तेमाल करके, इसे लागू करें:

.gui-switch.-vertical > input {
  transform: rotate(-90deg);
  transform: rotate(calc(90deg * var(--isLTR) * -1));
}

अब वर्टिकल स्विच के रोटेशन में, दाएं से बाएं लेआउट के लिए ज़रूरी दूसरी साइड की पोज़िशन का हिसाब लगाया जाता है.

थंब के स्यूडो-एलिमेंट पर लागू होने वाले translateX को भी अपडेट करना होगा, ताकि दूसरी तरफ़ की ज़रूरत को पूरा किया जा सके:

.gui-switch > input:checked {
  --thumb-position: calc(var(--track-size) - 100%);
  --thumb-position: calc((var(--track-size) - 100%) * var(--isLTR));
}

.gui-switch > input:indeterminate {
  --thumb-position: calc(
    (var(--track-size) / 2) - (var(--thumb-size) / 2)
  );
  --thumb-position: calc(
   ((var(--track-size) / 2) - (var(--thumb-size) / 2))
    * var(--isLTR)
  );
}

यह तरीका, लॉजिकल सीएसएस ट्रांसफ़ॉर्म जैसे कॉन्सेप्ट से जुड़ी सभी ज़रूरतों को पूरा नहीं कर सकता. हालांकि, यह कई इस्तेमाल के उदाहरणों के लिए, DRY के कुछ सिद्धांत ज़रूर देता है.

राज्य

input[type="checkbox"] का इस्तेमाल करने के लिए, यह ज़रूरी है कि आप इसकी अलग-अलग स्थितियों को हैंडल करें. जैसे: :checked, :disabled, :indeterminate, और :hover. :focus को जान-बूझकर अकेला छोड़ दिया गया था. इसमें सिर्फ़ ऑफ़सेट में बदलाव किया गया था. फ़ोकस रिंग, Firefox और Safari पर अच्छी दिख रही थी:

Firefox और Safari में स्विच पर फ़ोकस की गई फ़ोकस रिंग का स्क्रीनशॉट.

सही का निशान लगाया गया

<label for="switch-checked" class="gui-switch">
  Default
  <input type="checkbox" role="switch" id="switch-checked" checked="true">
</label>

यह स्थिति, on स्थिति को दिखाती है. इस स्थिति में, "ट्रैक" इनपुट का बैकग्राउंड, चालू रंग पर सेट होता है. साथ ही, थंब की पोज़िशन "आखिरी" पर सेट होती है.

.gui-switch > input:checked {
  background: var(--track-color-active);
  --thumb-position: calc((var(--track-size) - 100%) * var(--isLTR));
}

बंद है

<label for="switch-disabled" class="gui-switch">
  Default
  <input type="checkbox" role="switch" id="switch-disabled" disabled="true">
</label>

:disabled बटन न सिर्फ़ दिखने में अलग होता है, बल्कि इससे एलिमेंट में बदलाव नहीं किया जा सकता. इंटरैक्शन में बदलाव न होने की सुविधा ब्राउज़र में मुफ़्त में मिलती है. हालांकि, :disabled का इस्तेमाल करने की वजह से, विज़ुअल स्टेट के लिए स्टाइल की ज़रूरत होती है.appearance: none

.gui-switch > input:disabled {
  cursor: not-allowed;
  --thumb-color: transparent;

  &::before {
    cursor: not-allowed;
    box-shadow: inset 0 0 0 2px hsl(0 0% 100% / 50%);

    @media (prefers-color-scheme: dark) { & {
      box-shadow: inset 0 0 0 2px hsl(0 0% 0% / 50%);
    }}
  }
}

गहरे रंग वाला स्विच, चालू नहीं है, चालू है, और बंद है.

यह स्थिति मुश्किल है, क्योंकि इसमें गहरे और हल्के रंग वाली थीम की ज़रूरत होती है. साथ ही, इसमें चुने गए और नहीं चुने गए, दोनों तरह के विकल्प होने चाहिए. मैंने इन स्थितियों के लिए, कम से कम स्टाइल चुनी हैं, ताकि स्टाइल के कॉम्बिनेशन को बनाए रखना आसान हो.

तय नहीं है

अक्सर लोग :indeterminate स्थिति को भूल जाते हैं. इसमें चेकबॉक्स न तो चुना जाता है और न ही अनचेक किया जाता है. यह एक मज़ेदार स्थिति है. यह लोगों को न्योता देती है और इसमें कोई दिखावा नहीं होता. यह एक अच्छा रिमाइंडर है कि बूलियन स्टेट में, बीच की स्टेट भी हो सकती हैं.

चेकबॉक्स को इंडिटरमिनेट पर सेट करना मुश्किल है. इसे सिर्फ़ JavaScript सेट कर सकती है:

<label for="switch-indeterminate" class="gui-switch">
  Indeterminate
  <input type="checkbox" role="switch" id="switch-indeterminate">
  <script>document.getElementById('switch-indeterminate').indeterminate = true</script>
</label>

अनिश्चित स्थिति, जिसमें ट्रैक थंब बीच में होता है. इससे यह पता चलता है कि कोई फ़ैसला नहीं लिया गया है.

मुझे यह स्थिति सामान्य और आकर्षक लगी. इसलिए, मैंने स्विच थंब की पोज़िशन को बीच में रखा:

.gui-switch > input:indeterminate {
  --thumb-position: calc(
    calc(calc(var(--track-size) / 2) - calc(var(--thumb-size) / 2))
    * var(--isLTR)
  );
}

कर्सर घुमाएं

होवर इंटरैक्शन से, कनेक्ट किए गए यूज़र इंटरफ़ेस (यूआई) के लिए विज़ुअल सपोर्ट मिलना चाहिए. साथ ही, इंटरैक्टिव यूज़र इंटरफ़ेस (यूआई) के बारे में जानकारी भी मिलनी चाहिए. इस स्विच में, लेबल या इनपुट पर कर्सर घुमाने पर थंब को सेमी-ट्रांसपैरेंट रिंग से हाइलाइट किया जाता है. इसके बाद, यह होवर ऐनिमेशन, इंटरैक्टिव थंब एलिमेंट की ओर जाता है.

"हाइलाइट" इफ़ेक्ट, box-shadow की मदद से किया जाता है. जब किसी चालू इनपुट पर कर्सर घुमाया जाता है, तब --highlight-size का साइज़ बढ़ जाता है. अगर उपयोगकर्ता को मोशन से कोई समस्या नहीं है, तो हम box-shadow को ट्रांज़िशन करते हैं और उसे बढ़ते हुए देखते हैं. अगर उपयोगकर्ता को मोशन से समस्या है, तो हाइलाइट तुरंत दिखती है:

.gui-switch > input::before {
  box-shadow: 0 0 0 var(--highlight-size) var(--thumb-color-highlight);

  @media (--motionOK) { & {
    transition:
      transform var(--thumb-transition-duration) ease,
      box-shadow .25s ease;
  }}
}

.gui-switch > input:not(:disabled):hover::before {
  --highlight-size: .5rem;
}

JavaScript

मुझे लगता है कि स्विच इंटरफ़ेस, फ़िज़िकल इंटरफ़ेस की नकल करने की कोशिश में अजीब लग सकता है. खास तौर पर, इस तरह का इंटरफ़ेस जिसमें ट्रैक के अंदर एक सर्कल होता है. iOS ने अपने स्विच इंटरफ़ेस को सही तरीके से बनाया है. इन्हें एक से दूसरी ओर खींचा जा सकता है. साथ ही, इस विकल्प का इस्तेमाल करना बहुत अच्छा लगता है. इसके उलट, अगर किसी यूज़र इंटरफ़ेस (यूआई) एलिमेंट को खींचने की कोशिश की जाती है और कुछ नहीं होता है, तो वह एलिमेंट काम नहीं कर रहा है.

खींचने और छोड़ने लायक थंब

थंब स्यूडो-एलिमेंट को अपनी पोज़िशन, .gui-switch > input स्कोप किए गए var(--thumb-position) से मिलती है. JavaScript, इनपुट पर इनलाइन स्टाइल वैल्यू दे सकता है, ताकि थंब की पोज़िशन को डाइनैमिक रूप से अपडेट किया जा सके. इससे ऐसा लगता है कि थंब, पॉइंटर के जेस्चर को फ़ॉलो कर रहा है. जब पॉइंटर छोड़ दिया जाता है, तब इनलाइन स्टाइल हटा दी जाती हैं. साथ ही, कस्टम प्रॉपर्टी --thumb-position का इस्तेमाल करके यह तय किया जाता है कि ड्रैग करने की कार्रवाई, बंद करने के ज़्यादा करीब थी या चालू करने के. यह समाधान का मुख्य हिस्सा है. इसमें पॉइंटर इवेंट, सीएसएस कस्टम प्रॉपर्टी में बदलाव करने के लिए, पॉइंटर की पोज़िशन को शर्तों के हिसाब से ट्रैक करते हैं.

यह स्क्रिप्ट दिखने से पहले, कॉम्पोनेंट पूरी तरह से काम कर रहा था. इसलिए, मौजूदा तरीके को बनाए रखने के लिए काफ़ी काम करना पड़ता है. जैसे, इनपुट को टॉगल करने के लिए किसी लेबल पर क्लिक करना. हमारे JavaScript को मौजूदा सुविधाओं की कीमत पर नई सुविधाएं नहीं जोड़नी चाहिए.

touch-action

ड्रैग करना एक जेस्चर है, जो कस्टम जेस्चर है. इसलिए, यह touch-action के फ़ायदों के लिए एक बेहतरीन विकल्प है. इस स्विच के मामले में, हॉरिज़ॉन्टल जेस्चर को हमारी स्क्रिप्ट से हैंडल किया जाना चाहिए. इसके अलावा, वर्टिकल स्विच के वैरिएंट के लिए वर्टिकल जेस्चर कैप्चर किया जाना चाहिए. touch-action की मदद से, हम ब्राउज़र को यह बता सकते हैं कि इस एलिमेंट पर कौनसे जेस्चर हैंडल करने हैं. इससे कोई स्क्रिप्ट, बिना किसी रुकावट के जेस्चर को हैंडल कर सकती है.

नीचे दी गई सीएसएस, ब्राउज़र को यह निर्देश देती है कि जब पॉइंटर जेस्चर इस स्विच ट्रैक के अंदर से शुरू हो, तो वर्टिकल जेस्चर को हैंडल करें और हॉरिज़ॉन्टल जेस्चर के साथ कुछ न करें:

.gui-switch > input {
  touch-action: pan-y;
}

हमें ऐसा हॉरिज़ॉन्टल जेस्चर चाहिए जिससे पेज पैन या स्क्रोल न हो. पॉइंटर, इनपुट के अंदर से शुरू होकर पेज को ऊपर-नीचे स्क्रोल कर सकता है. हालांकि, दाएं-बाएं स्क्रोल करने वाले पॉइंटर को कस्टम तरीके से हैंडल किया जाता है.

पिक्सल वैल्यू स्टाइल यूटिलिटी

सेटअप के दौरान और खींचकर छोड़ने की प्रोसेस के दौरान, एलिमेंट से कैलकुलेट की गई अलग-अलग संख्या वाली वैल्यू को पाना होगा. यहां दिए गए JavaScript फ़ंक्शन, सीएसएस प्रॉपर्टी के हिसाब से पिक्सल वैल्यू दिखाते हैं. इसका इस्तेमाल सेटअप स्क्रिप्ट में इस तरह किया जाता है getStyle(checkbox, 'padding-left').

​​const getStyle = (element, prop) => {
  return parseInt(window.getComputedStyle(element).getPropertyValue(prop));
}

const getPseudoStyle = (element, prop) => {
  return parseInt(window.getComputedStyle(element, ':before').getPropertyValue(prop));
}

export {
  getStyle,
  getPseudoStyle,
}

ध्यान दें कि window.getComputedStyle() दूसरे आर्ग्युमेंट, टारगेट स्यूडो एलिमेंट को स्वीकार करता है. यह बहुत अच्छी बात है कि JavaScript, एलिमेंट से इतनी सारी वैल्यू पढ़ सकता है. यहां तक कि छद्म एलिमेंट से भी वैल्यू पढ़ी जा सकती है.

dragging

यह ड्रैग लॉजिक के लिए एक अहम समय होता है. फ़ंक्शन इवेंट हैंडलर से कुछ चीज़ें नोट की जाती हैं:

const dragging = event => {
  if (!state.activethumb) return

  let {thumbsize, bounds, padding} = switches.get(state.activethumb.parentElement)
  let directionality = getStyle(state.activethumb, '--isLTR')

  let track = (directionality === -1)
    ? (state.activethumb.clientWidth * -1) + thumbsize + padding
    : 0

  let pos = Math.round(event.offsetX - thumbsize / 2)

  if (pos < bounds.lower) pos = 0
  if (pos > bounds.upper) pos = bounds.upper

  state.activethumb.style.setProperty('--thumb-position', `${track + pos}px`)
}

स्क्रिप्ट हीरो state.activethumb है. यह छोटा सर्कल, इस स्क्रिप्ट को पॉइंटर के साथ पोज़िशन कर रहा है. switches ऑब्जेक्ट एक Map() होता है. इसमें कुंजियां .gui-switch होती हैं और वैल्यू, कैश किए गए बाउंड और साइज़ होते हैं. इससे स्क्रिप्ट को बेहतर तरीके से काम करने में मदद मिलती है. दाईं से बाईं ओर लिखी जाने वाली भाषाओं के लिए, उसी कस्टम प्रॉपर्टी का इस्तेमाल किया जाता है जिसका इस्तेमाल सीएसएस --isLTR करती है. साथ ही, सीएसएस इस प्रॉपर्टी का इस्तेमाल करके लॉजिक को उलट सकती है और दाईं से बाईं ओर लिखी जाने वाली भाषाओं के लिए काम करना जारी रख सकती है. event.offsetX भी अहम है, क्योंकि इसमें डेल्टा वैल्यू होती है. यह वैल्यू, थंब को सही जगह पर रखने के लिए काम आती है.

state.activethumb.style.setProperty('--thumb-position', `${track + pos}px`)

सीएसएस की यह आखिरी लाइन, थंब एलिमेंट के लिए इस्तेमाल की गई कस्टम प्रॉपर्टी सेट करती है. आम तौर पर, यह वैल्यू असाइनमेंट समय के साथ बदलता है. हालांकि, पिछले पॉइंटर इवेंट ने कुछ समय के लिए --thumb-transition-duration को 0s पर सेट कर दिया है. इससे, धीमे इंटरैक्शन को हटा दिया गया है.

dragEnd

उपयोगकर्ता को स्विच से काफ़ी दूर तक खींचने और छोड़ने की अनुमति देने के लिए, एक ग्लोबल विंडो इवेंट रजिस्टर करना ज़रूरी था:

window.addEventListener('pointerup', event => {
  if (!state.activethumb) return

  dragEnd(event)
})

मुझे लगता है कि यह बहुत ज़रूरी है कि उपयोगकर्ता के पास, आइटम को किसी भी जगह पर खींचकर छोड़ने की सुविधा हो. साथ ही, इंटरफ़ेस इतना स्मार्ट होना चाहिए कि वह इस सुविधा को ध्यान में रखकर काम कर सके. इस स्विच को हैंडल करने में ज़्यादा समय नहीं लगा. हालांकि, डेवलपमेंट प्रोसेस के दौरान इस पर ध्यान देने की ज़रूरत थी.

const dragEnd = event => {
  if (!state.activethumb) return

  state.activethumb.checked = determineChecked()

  if (state.activethumb.indeterminate)
    state.activethumb.indeterminate = false

  state.activethumb.style.removeProperty('--thumb-transition-duration')
  state.activethumb.style.removeProperty('--thumb-position')
  state.activethumb.removeEventListener('pointermove', dragging)
  state.activethumb = null

  padRelease()
}

एलिमेंट के साथ इंटरैक्शन पूरा हो गया है. अब इनपुट की जांच की गई प्रॉपर्टी सेट करने और सभी जेस्चर इवेंट हटाने का समय है. चेकबॉक्स को state.activethumb.checked = determineChecked() से बदल दिया गया है.

determineChecked()

dragEnd फ़ंक्शन इस फ़ंक्शन को कॉल करता है. यह फ़ंक्शन तय करता है कि थंब, ट्रैक की सीमाओं के अंदर कहां है. अगर थंब, ट्रैक के आधे हिस्से के बराबर या उससे ज़्यादा है, तो यह फ़ंक्शन 'सही' दिखाता है:

const determineChecked = () => {
  let {bounds} = switches.get(state.activethumb.parentElement)

  let curpos =
    Math.abs(
      parseInt(
        state.activethumb.style.getPropertyValue('--thumb-position')))

  if (!curpos) {
    curpos = state.activethumb.checked
      ? bounds.lower
      : bounds.upper
  }

  return curpos >= bounds.middle
}

अन्य विचार

शुरुआती एचटीएमएल स्ट्रक्चर चुनने की वजह से, ड्रैग जेस्चर में कोड डेब्ड की समस्या आई. खास तौर पर, इनपुट को लेबल में रैप करने की वजह से. लेबल, पैरंट एलिमेंट होने की वजह से, इनपुट के बाद क्लिक इंटरैक्शन को स्वीकार करेगा. dragEnd इवेंट के आखिर में, आपने padRelease() को एक अजीब फ़ंक्शन के तौर पर देखा होगा.

const padRelease = () => {
  state.recentlyDragged = true

  setTimeout(_ => {
    state.recentlyDragged = false
  }, 300)
}

ऐसा इसलिए किया जाता है, ताकि इस लेबल को बाद में क्लिक करने का श्रेय दिया जा सके. इससे उपयोगकर्ता के इंटरैक्शन को अनचेक या चेक किया जा सकेगा.

अगर मुझे यह काम फिर से करना हो, तो मैं UX अपग्रेड के दौरान JavaScript की मदद से DOM को अडजस्ट करने के बारे में सोच सकता हूं. इससे एक ऐसा एलिमेंट बनाया जा सकता है जो लेबल क्लिक को खुद मैनेज करता है और बिल्ट-इन व्यवहार के साथ काम करता है.

मुझे इस तरह की JavaScript लिखना पसंद नहीं है. मुझे इवेंट बब्लिंग को मैनेज नहीं करना है:

const preventBubbles = event => {
  if (state.recentlyDragged)
    event.preventDefault() && event.stopPropagation()
}

नतीजा

यह छोटा सा स्विच कॉम्पोनेंट, अब तक के सभी जीयूआई चैलेंज में सबसे मुश्किल रहा! अब आपको पता चल गया है कि मैंने यह कैसे किया. अब आप कैसे करेंगे‽ 🙂

आइए, हम अपने तरीकों में विविधता लाएं और वेब पर काॅन्टेंट पोस्ट करने के सभी तरीके जानें. डेमो बनाएं और मुझे ट्वीट करें. इसके बाद, मैं इसे यहां कम्यूनिटी रीमिक्स सेक्शन में जोड़ दूंगा!

कम्यूनिटी रीमिक्स

संसाधन

.gui-switch GitHub पर सोर्स कोड ढूंढें.