रिस्पॉन्सिव और ऐक्सेस किया जा सकने वाला स्विच कॉम्पोनेंट बनाने के बारे में बुनियादी जानकारी.
इस पोस्ट में, मैं स्विच कॉम्पोनेंट बनाने के तरीके के बारे में अपनी राय शेयर करना चाहता हूं. डेमो आज़माएं.
अगर आपको वीडियो देखना ज़्यादा पसंद है, तो इस पोस्ट का 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
में, निजी और सार्वजनिक कस्टम प्रॉपर्टी होती हैं. इनका इस्तेमाल बच्चे अपने लेआउट का हिसाब लगाने के लिए करते हैं.
.gui-switch {
display: flex;
align-items: center;
gap: 2ch;
justify-content: space-between;
}
फ़्लेक्सबॉक्स लेआउट को बढ़ाना और उसमें बदलाव करना, किसी भी फ़्लेक्सबॉक्स लेआउट को बदलने जैसा होता है.
उदाहरण के लिए, किसी स्विच के ऊपर या नीचे लेबल लगाने के लिए या flex-direction
को बदलने के लिए:
<label for="light-switch" class="gui-switch" style="flex-direction: column">
Default
<input type="checkbox" role="switch" id="light-switch">
</label>
ट्रैक
चेकबॉक्स इनपुट को स्विच ट्रैक के तौर पर स्टाइल किया गया है. इसके लिए, इसके सामान्य appearance: checkbox
को हटाकर, इसका साइज़ दिया गया है:
.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
का इस्तेमाल करता है:
.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%;
}
बातचीत
कस्टम प्रॉपर्टी का इस्तेमाल करके, इंटरैक्शन के लिए तैयारी करें. इससे, होवर करने पर हाइलाइट दिखने और थंब की पोज़िशन में बदलाव होने की सुविधा मिलेगी. मोशन या होवर हाइलाइट स्टाइल में बदलाव करने से पहले, उपयोगकर्ता की प्राथमिकता की भी जांच की जाती है.
.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 पर अच्छी दिख रही थी:
सही का निशान लगाया गया
<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()
}
नतीजा
यह छोटा सा स्विच कॉम्पोनेंट, अब तक के सभी जीयूआई चैलेंज में सबसे मुश्किल रहा! अब आपको पता चल गया है कि मैंने यह कैसे किया. अब आप कैसे करेंगे‽ 🙂
आइए, हम अपने तरीकों में विविधता लाएं और वेब पर काॅन्टेंट पोस्ट करने के सभी तरीके जानें. डेमो बनाएं और मुझे ट्वीट करें. इसके बाद, मैं इसे यहां कम्यूनिटी रीमिक्स सेक्शन में जोड़ दूंगा!
कम्यूनिटी रीमिक्स
- @KonstantinRouda के लिए, कस्टम एलिमेंट: demo और code.
- @jhvanderschee का बटन: Codepen.
संसाधन
.gui-switch
GitHub पर सोर्स कोड ढूंढें.