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

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

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

डेमो

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

लेआउट

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

.gui-switch

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

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

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

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

फ़्लेक्सबॉक्स DevTools, वर्टिकल लेबल और स्विच को ओवरले कर रहा है.

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

ट्रैक

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

स्विच ट्रैक पर ओवरले किए गए Grid 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);
  }
}

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

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

.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 को जान-बूझकर वैसा ही रखा गया था, सिर्फ़ इसके ऑफ़सेट में बदलाव किया गया था; फ़ायरफ़ॉक्स और 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 का इस्तेमाल करके यह तय करें कि ड्रैग करने की कार्रवाई बंद थी या चालू. यह समाधान का मुख्य हिस्सा है; सीएसएस कस्टम प्रॉपर्टी में बदलाव करने के लिए, पॉइंटर इवेंट, पॉइंटर की पोज़िशन को कंडीशन के हिसाब से ट्रैक करते हैं.

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