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

रिस्पॉन्सिव और ऐक्सेस किए जा सकने वाले स्विच कॉम्पोनेंट बनाने के तरीके के बारे में खास जानकारी.

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

डेमो

अगर आप वीडियो पसंद करते हैं, तो यहां इस पोस्ट का 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 प्लगिन की मदद से, कस्टम प्रॉपर्टी में रखा जा सकता है. ऐसा, मीडिया क्वेरी के ड्राफ़्ट की इस खास जानकारी के आधार पर किया जाता है:

@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">, API और state के साथ पहले से बना होता है. ब्राउज़र, checked प्रॉपर्टी और इनपुट इवेंट, जैसे कि oninputऔर onchanged को मैनेज करता है.

लेआउट

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

.gui-switch

स्विच के लिए टॉप-लेवल लेआउट flexbox है. .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 स्टाइल, ब्राउज़र से मिले विज़ुअल सही के निशान को भी हटा देती है. यह कॉम्पोनेंट, इस विज़ुअल इंंडिकेटर को बदलने के लिए, इनपुट पर pseudo-element और :checked pseudo-class का इस्तेमाल करता है.

अंगूठा एक बदली हुई पहचान वाला चाइल्ड एलिमेंट है. इसे 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 का मालिकाना हक है और अंगूठे का pseudo एलिमेंट इसका इस्तेमाल 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);
  }
}

(RTL) दाएं से बाएं

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

.gui-switch {
  --isLTR: 1;

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

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

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

.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 बटन न सिर्फ़ विज़ुअल तौर पर अलग दिखता है, बल्कि इसे एलिमेंट को ऐसा बनाना भी चाहिए कि उसमें बदलाव न किया जा सके. ब्राउज़र पर इंटरैक्शन नहीं किया जा सकता. हालांकि, 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 पर सोर्स कोड ढूंढें.