रिस्पॉन्सिव और ऐक्सेस किए जा सकने वाले स्विच कॉम्पोनेंट बनाने का बुनियादी तरीका.
इस पोस्ट में, मैं स्विच कॉम्पोनेंट बनाने के तरीके के बारे में बताना चाहता हूं. डेमो आज़माएं.
अगर आपको वीडियो देखना पसंद है, तो यहां इस पोस्ट का 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
में, ऐसी प्राइवेट और सार्वजनिक कस्टम प्रॉपर्टी होती हैं जिनका इस्तेमाल बच्चे अपने लेआउट का हिसाब लगाने के लिए करते हैं.
.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);
}
}
(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 पर फ़ोकस रिंग बहुत अच्छी दिख रही थी:
सही का निशान लगाया गया
<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()
}
नतीजा
अब तक के सभी जीयूआई चैलेंज में, इस छोटे स्विच कॉम्पोनेंट को बनाने में सबसे ज़्यादा समय लगा! अब आपको पता है कि मैंने यह कैसे किया, तो आप कैसे करेंगे‽ 🙂
आइए, अलग-अलग तरीकों का इस्तेमाल करके, वेब पर कॉन्टेंट बनाने के सभी तरीके जानें. डेमो बनाएं और मुझे ट्वीट करें लिंक भेजें. हम इसे कम्यूनिटी रीमिक्स सेक्शन में जोड़ देंगे!
कम्यूनिटी रीमिक्स
- @KonstantinRouda, जिसमें कस्टम एलिमेंट: डेमो और कोड है.
- @jhvanderschee के साथ एक बटन: Codepen.
संसाधन
.gui-switch
का सोर्स कोड, GitHub पर ढूंढें.