<dialog>
एलिमेंट का इस्तेमाल करके, रंग के हिसाब से अडजस्ट होने वाले, रिस्पॉन्सिव, और ऐक्सेस किए जा सकने वाले मिनी और मेगा मॉडल बनाने के बारे में बुनियादी जानकारी.
इस पोस्ट में, मुझे <dialog>
एलिमेंट का इस्तेमाल करके, रंग के हिसाब से अडजस्ट होने वाले, रिस्पॉन्सिव, और ऐक्सेस किए जा सकने वाले मिनी और मेगा मोडल बनाने के बारे में अपने विचार शेयर करने हैं.
डेमो आज़माएं और सोर्स देखें!
अगर आपको वीडियो देखना ज़्यादा पसंद है, तो इस पोस्ट का YouTube वर्शन यहां दिया गया है:
खास जानकारी
<dialog>
एलिमेंट, पेज पर मौजूद कॉन्टेक्स्ट के हिसाब से जानकारी देने या कार्रवाई करने के लिए बहुत अच्छा है. ध्यान रखें कि उपयोगकर्ता अनुभव को बेहतर बनाने के लिए, एक ही पेज पर कार्रवाई करना ज़्यादा फ़ायदेमंद हो सकता है. ऐसा तब होता है, जब कई पेजों पर कार्रवाई करने की ज़रूरत नहीं होती. जैसे, फ़ॉर्म छोटा हो या उपयोगकर्ता को सिर्फ़ पुष्टि करनी हो या रद्द करना हो.
<dialog>
एलिमेंट, हाल ही में सभी ब्राउज़र पर काम करने लगा है:
मुझे पता चला कि एलिमेंट में कुछ चीज़ें मौजूद नहीं हैं. इसलिए, इस GUI Challenge में, मैंने डेवलपर के अनुभव को बेहतर बनाने वाली चीज़ें जोड़ी हैं. जैसे, अतिरिक्त इवेंट, लाइट डिसमिस, कस्टम ऐनिमेशन, और मिनी और मेगा टाइप.
मार्कअप
<dialog>
एलिमेंट के लिए, ये कॉम्पोनेंट ज़रूरी हैं. यह एलिमेंट अपने-आप छिप जाएगा. साथ ही, इसमें आपके कॉन्टेंट को ओवरले करने के लिए स्टाइल पहले से मौजूद होती हैं.
<dialog>
…
</dialog>
हम इस बेसलाइन को बेहतर बना सकते हैं.
आम तौर पर, डायलॉग एलिमेंट और मोडल में काफ़ी समानता होती है. साथ ही, अक्सर इनके नाम एक-दूसरे की जगह इस्तेमाल किए जाते हैं. हमने यहां डायलॉग एलिमेंट का इस्तेमाल, छोटे डायलॉग पॉप-अप (मिनी) और पूरे पेज के डायलॉग (मेगा), दोनों के लिए किया है. मैंने इनका नाम मेगा और मिनी रखा है. साथ ही, दोनों डायलॉग को अलग-अलग इस्तेमाल के हिसाब से थोड़ा बदला है.
हमने modal-mode
एट्रिब्यूट जोड़ा है, ताकि आप टाइप तय कर सकें:
<dialog id="MegaDialog" modal-mode="mega"></dialog>
<dialog id="MiniDialog" modal-mode="mini"></dialog>
ऐसा हमेशा नहीं होता, लेकिन आम तौर पर डायलॉग एलिमेंट का इस्तेमाल, इंटरैक्शन से जुड़ी कुछ जानकारी इकट्ठा करने के लिए किया जाता है. डायलॉग एलिमेंट के अंदर मौजूद फ़ॉर्म, एक साथ काम करने के लिए बनाए जाते हैं.
डायलॉग के कॉन्टेंट को फ़ॉर्म एलिमेंट में रैप करना बेहतर होता है, ताकि JavaScript उस डेटा को ऐक्सेस कर सके जिसे उपयोगकर्ता ने डाला है. इसके अलावा, method="dialog"
का इस्तेमाल करने वाले फ़ॉर्म में मौजूद बटन, JavaScript के बिना डायलॉग बॉक्स को बंद कर सकते हैं और डेटा पास कर सकते हैं.
<dialog id="MegaDialog" modal-mode="mega">
<form method="dialog">
…
<button value="cancel">Cancel</button>
<button value="confirm">Confirm</button>
</form>
</dialog>
मेगा डायलॉग
मेगा डायलॉग में फ़ॉर्म के अंदर तीन एलिमेंट होते हैं:
<header>
,
<article>
,
और
<footer>
.
ये सिमैंटिक कंटेनर के साथ-साथ, डायलॉग को दिखाने के लिए स्टाइल टारगेट के तौर पर काम करते हैं. हेडर में, डायलॉग बॉक्स का टाइटल होता है और बंद करें बटन होता है. यह लेख, फ़ॉर्म में दिए गए इनपुट और जानकारी के बारे में है. फ़ुटर में ऐक्शन बटन का <menu>
होता है.
<dialog id="MegaDialog" modal-mode="mega">
<form method="dialog">
<header>
<h3>Dialog title</h3>
<button onclick="this.closest('dialog').close('close')"></button>
</header>
<article>...</article>
<footer>
<menu>
<button autofocus type="reset" onclick="this.closest('dialog').close('cancel')">Cancel</button>
<button type="submit" value="confirm">Confirm</button>
</menu>
</footer>
</form>
</dialog>
पहले मेन्यू बटन में autofocus
और onclick
इनलाइन इवेंट हैंडलर है. डायलॉग बॉक्स खुलने पर, autofocus
एट्रिब्यूट पर फ़ोकस किया जाएगा. मेरा मानना है कि इसे 'पुष्टि करें' बटन के बजाय, 'रद्द करें' बटन पर रखना सबसे सही तरीका है. इससे यह पक्का होता है कि पुष्टि जान-बूझकर की गई है, गलती से नहीं.
मिनी डायलॉग
मिनी डायलॉग, मेगा डायलॉग से काफ़ी मिलता-जुलता है. इसमें सिर्फ़ <header>
एलिमेंट मौजूद नहीं है. इससे यह छोटा और ज़्यादा इनलाइन हो जाता है.
<dialog id="MiniDialog" modal-mode="mini">
<form method="dialog">
<article>
<p>Are you sure you want to remove this user?</p>
</article>
<footer>
<menu>
<button autofocus type="reset" onclick="this.closest('dialog').close('cancel')">Cancel</button>
<button type="submit" value="confirm">Confirm</button>
</menu>
</footer>
</form>
</dialog>
डायलॉग एलिमेंट, पूरे व्यूपोर्ट एलिमेंट के लिए एक मज़बूत आधार उपलब्ध कराता है. यह एलिमेंट, डेटा और उपयोगकर्ता के इंटरैक्शन की जानकारी इकट्ठा कर सकता है. इन ज़रूरी चीज़ों से, आपकी साइट या ऐप्लिकेशन पर बहुत दिलचस्प और असरदार इंटरैक्शन हो सकते हैं.
सुलभता
डायलॉग एलिमेंट में, सुलभता की सुविधाएं पहले से मौजूद होती हैं. आम तौर पर, मैं इन सुविधाओं को जोड़ता हूं. हालांकि, अब ये सुविधाएं पहले से ही मौजूद हैं.
फ़ोकस वापस लाना
साइडनेव कॉम्पोनेंट बनाने के दौरान, हमने मैन्युअल तरीके से ऐसा किया था. इसलिए, यह ज़रूरी है कि किसी आइटम को खोलने और बंद करने के दौरान, उससे जुड़े 'खोलें' और 'बंद करें' बटन पर फ़ोकस किया जाए. साइडनेव खुलने पर, फ़ोकस बंद करें बटन पर होता है. बंद करें बटन दबाने पर, फ़ोकस उस बटन पर वापस आ जाता है जिसने इसे खोला था.
डायलॉग एलिमेंट के साथ, यह डिफ़ॉल्ट रूप से काम करता है:
माफ़ करें, अगर आपको डायलॉग को ऐनिमेट करना है, तो यह सुविधा काम नहीं करेगी. JavaScript सेक्शन में, मैं उस सुविधा को वापस लाऊंगा.
ट्रैपिंग फ़ोकस
डायलॉग एलिमेंट, दस्तावेज़ पर inert
को मैनेज करता है. inert
से पहले, JavaScript का इस्तेमाल किसी एलिमेंट से फ़ोकस हटाने पर किया जाता था. इस दौरान, JavaScript उस एलिमेंट को इंटरसेप्ट करके वापस रख देता था.
inert
के बाद, दस्तावेज़ के किसी भी हिस्से को "फ़्रीज़" किया जा सकता है. इसका मतलब है कि वे अब फ़ोकस टारगेट नहीं होंगे या माउस से इंटरैक्ट नहीं करेंगे. फ़ोकस को ट्रैप करने के बजाय, उसे दस्तावेज़ के सिर्फ़ इंटरैक्टिव हिस्से में ले जाया जाता है.
किसी एलिमेंट को खोलना और उस पर अपने-आप फ़ोकस करना
डिफ़ॉल्ट रूप से, डायलॉग एलिमेंट, डायलॉग मार्कअप में फ़ोकस किए जा सकने वाले पहले एलिमेंट पर फ़ोकस असाइन करेगा. अगर यह एलिमेंट, उपयोगकर्ता के लिए डिफ़ॉल्ट रूप से सबसे सही नहीं है, तो autofocus
एट्रिब्यूट का इस्तेमाल करें. जैसा कि ऊपर बताया गया है, इस मैसेज को 'पुष्टि करें' बटन के बजाय 'रद्द करें' बटन पर रखना सबसे सही तरीका है. इससे यह पक्का किया जाता है कि पुष्टि जान-बूझकर की गई है, गलती से नहीं.
Escape बटन दबाकर बंद करना
इस एलिमेंट को आसानी से बंद किया जा सके, यह ज़रूरी है. अच्छी बात यह है कि डायलॉग एलिमेंट, एस्केप कुंजी को मैनेज करता है. इससे आपको ऑर्केस्ट्रेशन के बोझ से मुक्ति मिलती है.
स्टाइल
डायलॉग एलिमेंट को स्टाइल करने का एक आसान तरीका है और एक मुश्किल तरीका है. डायलॉग की डिसप्ले प्रॉपर्टी में बदलाव न करके और इसकी सीमाओं के साथ काम करके, आसानी से इस समस्या को हल किया जा सकता है. मैं डायलॉग को खोलने और बंद करने के लिए, कस्टम ऐनिमेशन उपलब्ध कराने के लिए मुश्किल तरीके का इस्तेमाल करता हूं. साथ ही, display
प्रॉपर्टी और अन्य चीज़ों को भी मैनेज करता हूं.
Open Props की मदद से स्टाइलिंग करना
मैंने अडैप्टिव कलर और डिज़ाइन को एक जैसा बनाए रखने के लिए, सीएसएस वैरिएबल लाइब्रेरी Open Props का इस्तेमाल किया है. मुफ़्त में उपलब्ध कराए गए वैरिएबल के अलावा, मैंने normalize फ़ाइल और कुछ बटन भी इंपोर्ट किए हैं. Open Props, इन दोनों को इंपोर्ट करने का विकल्प देता है. इन इंपोर्ट की मदद से, मैं डायलॉग और डेमो को अपनी पसंद के मुताबिक बना पाता हूं. साथ ही, मुझे इसे सपोर्ट करने और बेहतर बनाने के लिए, ज़्यादा स्टाइल की ज़रूरत नहीं पड़ती.
<dialog>
एलिमेंट को स्टाइल करना
डिसप्ले प्रॉपर्टी का मालिकाना हक
डायलॉग एलिमेंट के डिफ़ॉल्ट रूप से दिखने और छिपने के व्यवहार से, डिसप्ले प्रॉपर्टी block
से none
पर टॉगल होती है. माफ़ करें, इसका मतलब है कि इसे सिर्फ़ ऐनिमेट किया जा सकता है. मुझे दोनों को ऐनिमेट करना है. इसके लिए, पहला चरण यह है कि मैं अपनी display प्रॉपर्टी सेट करूं:
dialog {
display: grid;
}
डिसप्ले प्रॉपर्टी की वैल्यू बदलने से, जैसा कि ऊपर दिए गए सीएसएस स्निपेट में दिखाया गया है, कई स्टाइल मैनेज करनी पड़ती हैं. इससे लोगों को बेहतर अनुभव मिलता है. डायलॉग की डिफ़ॉल्ट स्थिति बंद होती है. इस स्थिति को विज़ुअल तौर पर दिखाया जा सकता है. साथ ही, डायलॉग को इन स्टाइल के साथ इंटरैक्ट करने से रोका जा सकता है:
dialog:not([open]) {
pointer-events: none;
opacity: 0;
}
अब डायलॉग नहीं दिखता है. साथ ही, जब यह खुला नहीं होता है, तब इसके साथ इंटरैक्ट नहीं किया जा सकता. बाद में
मैं डायलॉग पर inert
एट्रिब्यूट को मैनेज करने के लिए, कुछ JavaScript जोड़ूंगा. इससे यह पक्का किया जा सकेगा कि कीबोर्ड और स्क्रीन रीडर का इस्तेमाल करने वाले लोग भी छिपे हुए डायलॉग तक न पहुंच पाएं.
डायलॉग को अडैप्टिव कलर थीम देना
color-scheme
की मदद से, आपके दस्तावेज़ को ब्राउज़र की ओर से उपलब्ध कराई गई अडैप्टिव कलर थीम में शामिल किया जाता है. इससे सिस्टम की लाइट और डार्क थीम की सेटिंग के हिसाब से, दस्तावेज़ का रंग अपने-आप बदल जाता है. हालांकि, मुझे डायलॉग एलिमेंट को इससे ज़्यादा अपनी पसंद के मुताबिक बनाना था. Open Props, कुछ सरफ़ेस कलर उपलब्ध कराता है. ये सिस्टम की लाइट और डार्क थीम की सेटिंग के हिसाब से अपने-आप अडजस्ट हो जाते हैं. ये color-scheme
का इस्तेमाल करने के जैसा ही है. ये डिज़ाइन में लेयर बनाने के लिए बेहतरीन हैं. मुझे लेयर वाली सतहों को विज़ुअल तौर पर दिखाने के लिए, रंग का इस्तेमाल करना पसंद है. बैकग्राउंड का रंग var(--surface-1)
है. इस लेयर के ऊपर दिखने के लिए, var(--surface-2)
का इस्तेमाल करें:
dialog {
…
background: var(--surface-2);
color: var(--text-1);
}
@media (prefers-color-scheme: dark) {
dialog {
border-block-start: var(--border-size-1) solid var(--surface-3);
}
}
बाद में, हेडर और फ़ुटर जैसे चाइल्ड एलिमेंट के लिए, अडैप्टिव कलर की सुविधा जोड़ी जाएगी. डायलॉग एलिमेंट के लिए, मैं इन्हें अतिरिक्त मानता/मानती हूं. हालांकि, डायलॉग डिज़ाइन को बेहतर और आकर्षक बनाने में इनकी भूमिका अहम होती है.
डायलॉग के साइज़ को रिस्पॉन्सिव बनाना
डायलॉग बॉक्स का साइज़, डिफ़ॉल्ट रूप से उसके कॉन्टेंट के हिसाब से तय होता है. आम तौर पर, यह सही होता है. मेरा मकसद यहां max-inline-size
को पढ़ने लायक साइज़ (--size-content-3
= 60ch
) या व्यूपोर्ट की चौड़ाई के 90% तक सीमित करना है. इससे यह पक्का होता है कि डायलॉग, मोबाइल डिवाइस पर एक किनारे से दूसरे किनारे तक नहीं जाएगा. साथ ही, डेस्कटॉप स्क्रीन पर इतना चौड़ा नहीं होगा कि उसे पढ़ना मुश्किल हो. इसके बाद, मैं एक max-block-size
जोड़ता हूं, ताकि डायलॉग पेज की ऊंचाई से ज़्यादा न हो. इसका यह भी मतलब है कि अगर डायलॉग बॉक्स लंबा है, तो हमें यह बताना होगा कि डायलॉग बॉक्स का कौनसा हिस्सा स्क्रोल किया जा सकता है.
dialog {
…
max-inline-size: min(90vw, var(--size-content-3));
max-block-size: min(80vh, 100%);
max-block-size: min(80dvb, 100%);
overflow: hidden;
}
क्या आपने देखा कि मैंने max-block-size
को दो बार इस्तेमाल किया है? पहले उदाहरण में 80vh
का इस्तेमाल किया गया है, जो एक फ़िज़िकल व्यूपोर्ट यूनिट है. मुझे अंतरराष्ट्रीय उपयोगकर्ताओं के लिए, डायलॉग को रिलेटिव फ़्लो में रखना है. इसलिए, मैं दूसरे एलान में लॉजिकल, नई, और सिर्फ़ कुछ हद तक काम करने वाली dvb
यूनिट का इस्तेमाल करता हूं, ताकि यह ज़्यादा स्टेबल हो जाए.
मेगा डायलॉग की पोज़िशनिंग
डायलॉग एलिमेंट को सही जगह पर रखने के लिए, इसके दो हिस्सों को अलग-अलग समझना ज़रूरी है: फ़ुल स्क्रीन बैकड्रॉप और डायलॉग कंटेनर. बैकड्रॉप में पूरी स्क्रीन दिखनी चाहिए. साथ ही, इसमें शेड इफ़ेक्ट होना चाहिए, ताकि यह पता चल सके कि डायलॉग सबसे ऊपर है और इसके पीछे मौजूद कॉन्टेंट को ऐक्सेस नहीं किया जा सकता. डायलॉग कंटेनर, इस बैकड्रॉप के बीच में अपने-आप आ जाता है. साथ ही, यह अपने कॉन्टेंट के हिसाब से कोई भी आकार ले सकता है.
नीचे दी गई स्टाइल, डायलॉग एलिमेंट को विंडो पर फ़िक्स करती हैं. साथ ही, इसे हर कोने तक फैलाती हैं. इसके अलावा, कॉन्टेंट को बीच में लाने के लिए margin: auto
का इस्तेमाल करती हैं:
dialog {
…
margin: auto;
padding: 0;
position: fixed;
inset: 0;
z-index: var(--layer-important);
}
मोबाइल मेगा डायलॉग की स्टाइल
छोटे व्यूपोर्ट पर, मैं इस पूरे पेज के मेगा मोडल को थोड़ा अलग स्टाइल देता हूं. मैंने सबसे नीचे वाले मार्जिन को 0
पर सेट किया है. इससे डायलॉग का कॉन्टेंट, व्यूपोर्ट के सबसे नीचे आ जाता है. स्टाइल में कुछ बदलाव करके, डायलॉग को ऐक्शनशीट में बदला जा सकता है. इससे यह उपयोगकर्ता के अंगूठे के ज़्यादा करीब दिखेगा:
@media (max-width: 768px) {
dialog[modal-mode="mega"] {
margin-block-end: 0;
border-end-end-radius: 0;
border-end-start-radius: 0;
}
}
मिनी डायलॉग की पोज़िशन
डेस्कटॉप कंप्यूटर जैसे बड़े व्यूपोर्ट का इस्तेमाल करते समय, मैंने मिनी डायलॉग को उस एलिमेंट के ऊपर रखने का विकल्प चुना जिसने उन्हें कॉल किया था. इसके लिए, मुझे JavaScript की ज़रूरत है. मैंने जिस तकनीक का इस्तेमाल किया है उसके बारे में यहां बताया गया है. हालांकि, मुझे लगता है कि यह इस लेख के दायरे से बाहर है. JavaScript के बिना, मिनी डायलॉग स्क्रीन के बीच में दिखता है. यह मेगा डायलॉग की तरह ही होता है.
इसे अट्रैक्टिव बनाएं
आखिर में, डायलॉग में कुछ और इफ़ेक्ट जोड़ें, ताकि यह पेज से काफ़ी ऊपर एक मुलायम सतह की तरह दिखे. डायलॉग के कोनों को गोल करके, सॉफ़्टनेस हासिल की जाती है. गहराई दिखाने के लिए, Open Props के shadow props का इस्तेमाल किया गया है. इन्हें खास तौर पर डिज़ाइन किया गया है:
dialog {
…
border-radius: var(--radius-3);
box-shadow: var(--shadow-6);
}
बैकड्रॉप स्यूडो एलिमेंट को पसंद के मुताबिक बनाना
मैंने बैकड्रॉप को बहुत हल्के तरीके से इस्तेमाल किया है. मैंने सिर्फ़ backdrop-filter
की मदद से, मेगा डायलॉग में धुंधला करने का इफ़ेक्ट जोड़ा है:
dialog[modal-mode="mega"]::backdrop {
backdrop-filter: blur(25px);
}
मैंने backdrop-filter
पर ट्रांज़िशन भी लगाया है. मुझे उम्मीद है कि ब्राउज़र, आने वाले समय में बैकड्रॉप एलिमेंट को ट्रांज़िशन करने की अनुमति देंगे:
dialog::backdrop {
transition: backdrop-filter .5s ease;
}
स्टाइलिंग से जुड़ी अतिरिक्त सुविधाएं
मैंने इस सेक्शन को "extras" नाम दिया है, क्योंकि यह सामान्य तौर पर डायलॉग एलिमेंट के बजाय, मेरे डायलॉग एलिमेंट के डेमो से ज़्यादा जुड़ा हुआ है.
स्क्रोल कंटेनमेंट
डायलॉग दिखने पर भी उपयोगकर्ता, उसके पीछे मौजूद पेज को स्क्रोल कर सकता है. मुझे ऐसा नहीं करना है:
आम तौर पर, overscroll-behavior
मेरा सामान्य समाधान होता है. हालांकि, स्पेसिफ़िकेशन के मुताबिक, इसका डायलॉग पर कोई असर नहीं पड़ता, क्योंकि यह स्क्रोल पोर्ट नहीं है. इसका मतलब है कि यह स्क्रोलर नहीं है, इसलिए कुछ भी रोकने की ज़रूरत नहीं है. मैं इस गाइड में दिए गए नए इवेंट, जैसे कि "closed" और "opened" को देखने के लिए JavaScript का इस्तेमाल कर सकता/सकती हूं. साथ ही, दस्तावेज़ पर overflow: hidden
को टॉगल कर सकता/सकती हूं. इसके अलावा, मैं सभी ब्राउज़र में :has()
के स्टेबल होने का इंतज़ार कर सकता/सकती हूं:
html:has(dialog[open][modal-mode="mega"]) {
overflow: hidden;
}
अब जब कोई बड़ा डायलॉग खुला होता है, तो एचटीएमएल दस्तावेज़ में overflow: hidden
होता है.
<form>
लेआउट
यह उपयोगकर्ता से इंटरैक्शन की जानकारी इकट्ठा करने के लिए बहुत ज़रूरी एलिमेंट है. साथ ही, इसका इस्तेमाल हेडर, फ़ुटर, और लेख के एलिमेंट को लेआउट करने के लिए किया जाता है. इस लेआउट में, लेख के चाइल्ड एलिमेंट को स्क्रोल करने लायक़ एरिया के तौर पर दिखाया गया है. मैं grid-template-rows
की मदद से ऐसा करता/करती हूं.
आर्टिकल एलिमेंट को 1fr
दिया गया है. साथ ही, फ़ॉर्म की ज़्यादा से ज़्यादा ऊंचाई, डायलॉग एलिमेंट की ऊंचाई के बराबर है. इस फ़र्म की ऊंचाई और फ़र्म की लाइन का साइज़ सेट करने से, लेख वाले एलिमेंट को सीमित किया जा सकता है. साथ ही, कॉन्टेंट ज़्यादा होने पर उसे स्क्रोल किया जा सकता है:
dialog > form {
display: grid;
grid-template-rows: auto 1fr auto;
align-items: start;
max-block-size: 80vh;
max-block-size: 80dvb;
}
डायलॉग <header>
को स्टाइल करना
इस एलिमेंट का काम, डायलॉग बॉक्स के कॉन्टेंट के लिए टाइटल देना और बंद करने का बटन उपलब्ध कराना है, जिसे आसानी से ढूंढा जा सकता है. इसे डायलॉग के लेख के कॉन्टेंट के पीछे दिखाने के लिए, एक बैकग्राउंड कलर भी दिया गया है. इन ज़रूरी शर्तों के हिसाब से, फ़्लेक्सबॉक्स कंटेनर, वर्टिकल अलाइन किए गए आइटम, और कुछ पैडिंग और गैप इस तरह से सेट किए जाते हैं कि टाइटल और बंद करने वाले बटन के लिए कुछ जगह मिल जाए:
dialog > form > header {
display: flex;
gap: var(--size-3);
justify-content: space-between;
align-items: flex-start;
background: var(--surface-2);
padding-block: var(--size-3);
padding-inline: var(--size-5);
}
@media (prefers-color-scheme: dark) {
dialog > form > header {
background: var(--surface-1);
}
}
हेडर में मौजूद 'बंद करें' बटन की स्टाइल बदलना
डेमो में Open Props बटन का इस्तेमाल किया जा रहा है. इसलिए, बंद करें बटन को इस तरह के गोल आइकॉन वाले बटन के तौर पर पसंद के मुताबिक बनाया गया है:
dialog > form > header > button {
border-radius: var(--radius-round);
padding: .75ch;
aspect-ratio: 1;
flex-shrink: 0;
place-items: center;
stroke: currentColor;
stroke-width: 3px;
}
डायलॉग <article>
को स्टाइल करना
इस डायलॉग में article एलिमेंट की खास भूमिका होती है: यह एक ऐसी जगह है जिसे लंबे डायलॉग के मामले में स्क्रोल किया जा सकता है.
इसके लिए, पैरंट फ़ॉर्म एलिमेंट ने अपने लिए कुछ सीमाएं तय की हैं. अगर यह लेख एलिमेंट बहुत लंबा हो जाता है, तो इन सीमाओं को पूरा करना होगा. overflow-y: auto
को इस तरह सेट करें कि स्क्रोलबार सिर्फ़ तब दिखें, जब उनकी ज़रूरत हो. साथ ही, overscroll-behavior: contain
की मदद से, स्क्रोलिंग को इसमें शामिल करें. बाकी को कस्टम प्रज़ेंटेशन स्टाइल में शामिल किया जाएगा:
dialog > form > article {
overflow-y: auto;
max-block-size: 100%; /* safari */
overscroll-behavior-y: contain;
display: grid;
justify-items: flex-start;
gap: var(--size-3);
box-shadow: var(--shadow-2);
z-index: var(--layer-1);
padding-inline: var(--size-5);
padding-block: var(--size-3);
}
@media (prefers-color-scheme: light) {
dialog > form > article {
background: var(--surface-1);
}
}
डायलॉग <footer>
को स्टाइल करना
फ़ुटर का काम, ऐक्शन बटन के मेन्यू दिखाना है. फ़ुटर के इनलाइन ऐक्सिस के आखिर में कॉन्टेंट को अलाइन करने के लिए, फ़्लेक्सबॉक्स का इस्तेमाल किया जाता है. इसके बाद, बटन को कुछ जगह देने के लिए कुछ स्पेसिंग दी जाती है.
dialog > form > footer {
background: var(--surface-2);
display: flex;
flex-wrap: wrap;
gap: var(--size-3);
justify-content: space-between;
align-items: flex-start;
padding-inline: var(--size-5);
padding-block: var(--size-3);
}
@media (prefers-color-scheme: dark) {
dialog > form > footer {
background: var(--surface-1);
}
}
डायलॉग फ़ुटर मेन्यू को स्टाइल करना
menu
एलिमेंट का इस्तेमाल, डायलॉग के लिए ऐक्शन बटन को शामिल करने के लिए किया जाता है. यह रैपिंग फ़्लेक्सबॉक्स लेआउट का इस्तेमाल करता है. साथ ही, बटन के बीच स्पेस देने के लिए gap
का इस्तेमाल करता है. मेन्यू एलिमेंट में पैडिंग होती है, जैसे कि <ul>
. मुझे इस स्टाइल की ज़रूरत नहीं है, इसलिए मैं इसे भी हटा देती हूँ.
dialog > form > footer > menu {
display: flex;
flex-wrap: wrap;
gap: var(--size-3);
padding-inline-start: 0;
}
dialog > form > footer > menu:only-child {
margin-inline-start: auto;
}
ऐनिमेशन
डायलॉग एलिमेंट में अक्सर ऐनिमेशन का इस्तेमाल किया जाता है, क्योंकि वे विंडो में दिखते और हटते हैं. डायलॉग बॉक्स को खोलने और बंद करने के दौरान कुछ मोशन देने से, उपयोगकर्ताओं को फ़्लो में खुद को ओरिएंट करने में मदद मिलती है.
आम तौर पर, डायलॉग एलिमेंट को सिर्फ़ ऐनिमेट किया जा सकता है. ऐसा इसलिए होता है, क्योंकि ब्राउज़र एलिमेंट पर display
प्रॉपर्टी को टॉगल करता है. पहले, गाइड, डिसप्ले को ग्रिड पर सेट करती थी और इसे कभी भी 'कोई नहीं' पर सेट नहीं करती थी. इससे, एलिमेंट को ऐनिमेट करके दिखाने और छिपाने की सुविधा मिलती है.
Open Props में इस्तेमाल के लिए कई कीफ़्रेम ऐनिमेशन होते हैं. इससे ऑर्केस्ट्रेशन आसान और समझने में आसान हो जाता है. यहां ऐनिमेशन के लक्ष्यों और लेयर वाले अप्रोच के बारे में बताया गया है:
- हलचल कम करें, डिफ़ॉल्ट ट्रांज़िशन होता है. इसमें ओपैसिटी को धीरे-धीरे कम और ज़्यादा किया जाता है.
- अगर मोशन ठीक है, तो स्लाइड और स्केल ऐनिमेशन जोड़े जाते हैं.
- मेगा डायलॉग के रिस्पॉन्सिव मोबाइल लेआउट को स्लाइड आउट करने के लिए अडजस्ट किया गया है.
डिफ़ॉल्ट रूप से सुरक्षित और काम का ट्रांज़िशन
Open Props में फ़ेड इन और फ़ेड आउट करने के लिए कीफ़्रेम होते हैं. हालांकि, मुझे ट्रांज़िशन के लिए लेयर वाला यह तरीका पसंद है. इसमें कीफ़्रेम ऐनिमेशन को संभावित अपग्रेड के तौर पर इस्तेमाल किया जाता है. इससे पहले, हम [open]
एट्रिब्यूट के आधार पर, 1
या 0
को व्यवस्थित करके, ओपैसिटी की मदद से डायलॉग की दृश्यता को स्टाइल करते थे. 0% और 100% के बीच ट्रांज़िशन करने के लिए, ब्राउज़र को बताएं कि आपको ट्रांज़िशन कितनी देर तक और किस तरह का ईज़िंग इफ़ेक्ट चाहिए:
dialog {
transition: opacity .5s var(--ease-3);
}
ट्रांज़िशन में मोशन जोड़ना
अगर उपयोगकर्ता को मोशन से कोई समस्या नहीं है, तो दोनों डायलॉग को स्लाइड अप करके दिखाया जाना चाहिए और स्लाइड डाउन करके हटाया जाना चाहिए. इसके लिए, prefers-reduced-motion
मीडिया क्वेरी और कुछ Open Props का इस्तेमाल किया जा सकता है:
@media (prefers-reduced-motion: no-preference) {
dialog {
animation: var(--animation-scale-down) forwards;
animation-timing-function: var(--ease-squish-3);
}
dialog[open] {
animation: var(--animation-slide-in-up) forwards;
}
}
मोबाइल के लिए, स्लाइड से ऑब्जेक्ट हटाते समय चलाए जाने वाले ऐनिमेशन को अडैप्ट करना
स्टाइलिंग सेक्शन में, मेगा डायलॉग स्टाइल को मोबाइल डिवाइसों के लिए अडैप्ट किया गया है, ताकि यह ऐक्शन शीट की तरह दिखे. ऐसा लगता है कि कागज़ का एक छोटा टुकड़ा स्क्रीन के सबसे नीचे से ऊपर की ओर स्लाइड हुआ है और अब भी सबसे नीचे जुड़ा हुआ है. स्केल आउट वाला एक्ज़िट ऐनिमेशन, इस नए डिज़ाइन के हिसाब से सही नहीं है. हालांकि, हम इसे कुछ मीडिया क्वेरी और Open Props के साथ अडैप्ट कर सकते हैं:
@media (prefers-reduced-motion: no-preference) and @media (max-width: 768px) {
dialog[modal-mode="mega"] {
animation: var(--animation-slide-out-down) forwards;
animation-timing-function: var(--ease-squish-2);
}
}
JavaScript
JavaScript की मदद से कई चीज़ें जोड़ी जा सकती हैं:
// dialog.js
export default async function (dialog) {
// add light dismiss
// add closing and closed events
// add opening and opened events
// add removed event
// removing loading attribute
}
ये बदलाव, डायलॉग बैकड्रॉप पर क्लिक करके उसे बंद करने, ऐनिमेशन, और फ़ॉर्म का डेटा पाने के लिए कुछ अतिरिक्त इवेंट की वजह से किए गए हैं, ताकि सही समय पर डेटा मिल सके.
लाइट डिसमिस की सुविधा जोड़ी गई
यह टास्क आसान है. साथ ही, यह ऐसे डायलॉग एलिमेंट के लिए बहुत अच्छा है जिसमें ऐनिमेशन नहीं है. डायलॉग एलिमेंट पर क्लिक को ट्रैक करके इंटरैक्शन किया जाता है. साथ ही, इवेंट बब्लिंग का इस्तेमाल करके यह पता लगाया जाता है कि किस एलिमेंट पर क्लिक किया गया है. यह सिर्फ़ तब close()
होगा, जब यह सबसे ऊपर वाला एलिमेंट होगा:
export default async function (dialog) {
dialog.addEventListener('click', lightDismiss)
}
const lightDismiss = ({target:dialog}) => {
if (dialog.nodeName === 'DIALOG')
dialog.close('dismiss')
}
सूचना dialog.close('dismiss')
. इवेंट को कॉल किया जाता है और एक स्ट्रिंग दी जाती है.
इस स्ट्रिंग को अन्य JavaScript से वापस पाया जा सकता है, ताकि यह पता चल सके कि डायलॉग बॉक्स को कैसे बंद किया गया था. आपको दिखेगा कि मैंने हर बार अलग-अलग बटन से फ़ंक्शन को कॉल करते समय, मिलती-जुलती स्ट्रिंग भी दी हैं. इससे मेरे ऐप्लिकेशन को उपयोगकर्ता के इंटरैक्शन के बारे में जानकारी मिलती है.
बंद होने वाले और बंद हो चुके इवेंट जोड़ना
डायलॉग एलिमेंट में क्लोज़ इवेंट होता है: यह तब तुरंत चालू हो जाता है, जब dialog close()
फ़ंक्शन को कॉल किया जाता है. इस एलिमेंट को ऐनिमेट किया जा रहा है. इसलिए, ऐनिमेशन से पहले और बाद में इवेंट होने चाहिए, ताकि डेटा को बदला जा सके या डायलॉग फ़ॉर्म को रीसेट किया जा सके. मैंने इसका इस्तेमाल यहां बंद किए गए डायलॉग पर inert
एट्रिब्यूट को मैनेज करने के लिए किया है. साथ ही, डेमो में मैंने इनका इस्तेमाल अवतार की सूची में बदलाव करने के लिए किया है. ऐसा तब होता है, जब उपयोगकर्ता ने नई इमेज सबमिट की हो.
इसके लिए, closing
और closed
नाम के दो नए इवेंट बनाएं. इसके बाद, डायलॉग पर पहले से मौजूद क्लोज़ इवेंट को सुनें. यहां से, डायलॉग को inert
पर सेट करें और closing
इवेंट को डिसपैच करें. अगला टास्क यह है कि डायलॉग पर ऐनिमेशन और ट्रांज़िशन के पूरा होने का इंतज़ार किया जाए. इसके बाद, closed
इवेंट को डिसपैच किया जाए.
const dialogClosingEvent = new Event('closing')
const dialogClosedEvent = new Event('closed')
export default async function (dialog) {
…
dialog.addEventListener('close', dialogClose)
}
const dialogClose = async ({target:dialog}) => {
dialog.setAttribute('inert', '')
dialog.dispatchEvent(dialogClosingEvent)
await animationsComplete(dialog)
dialog.dispatchEvent(dialogClosedEvent)
}
const animationsComplete = element =>
Promise.allSettled(
element.getAnimations().map(animation =>
animation.finished))
animationsComplete
फ़ंक्शन का इस्तेमाल बिल्डिंग ए टोस्ट कॉम्पोनेंट में भी किया जाता है. यह फ़ंक्शन, एनिमेशन और ट्रांज़िशन के पूरे होने के आधार पर प्रॉमिस दिखाता है. इसलिए, dialogClose
एक async
function है. इसके बाद, यह await
वापस किए गए प्रॉमिस को पूरा कर सकता है और बंद किए गए इवेंट पर बेझिझक आगे बढ़ सकता है.
बदलाव करने के लिए खोले गए इवेंट जोड़ना
इन इवेंट को जोड़ना उतना आसान नहीं है, क्योंकि बिल्ट-इन डायलॉग एलिमेंट, क्लोज़ इवेंट की तरह ओपन इवेंट उपलब्ध नहीं कराता. मैं डायलॉग के एट्रिब्यूट में होने वाले बदलावों के बारे में अहम जानकारी देने के लिए, MutationObserver का इस्तेमाल करता/करती हूं. इस ऑब्ज़र्वर में, मैं open एट्रिब्यूट में होने वाले बदलावों को देखूंगा और कस्टम इवेंट को मैनेज करूंगा.
जिस तरह हमने क्लोज़िंग और क्लोज़्ड इवेंट बनाए थे उसी तरह दो नए इवेंट बनाएं. इनके नाम opening
और opened
रखें. पहले हम डायलॉग बंद होने के इवेंट को सुनते थे. अब हम बनाए गए म्यूटेशन ऑब्ज़र्वर का इस्तेमाल करके, डायलॉग के एट्रिब्यूट पर नज़र रखेंगे.
…
const dialogOpeningEvent = new Event('opening')
const dialogOpenedEvent = new Event('opened')
export default async function (dialog) {
…
dialogAttrObserver.observe(dialog, {
attributes: true,
})
}
const dialogAttrObserver = new MutationObserver((mutations, observer) => {
mutations.forEach(async mutation => {
if (mutation.attributeName === 'open') {
const dialog = mutation.target
const isOpen = dialog.hasAttribute('open')
if (!isOpen) return
dialog.removeAttribute('inert')
// set focus
const focusTarget = dialog.querySelector('[autofocus]')
focusTarget
? focusTarget.focus()
: dialog.querySelector('button').focus()
dialog.dispatchEvent(dialogOpeningEvent)
await animationsComplete(dialog)
dialog.dispatchEvent(dialogOpenedEvent)
}
})
})
डायलॉग के एट्रिब्यूट बदलने पर, म्यूटेशन ऑब्ज़र्वर कॉलबैक फ़ंक्शन को कॉल किया जाएगा. साथ ही, बदलावों की सूची को एक ऐरे के तौर पर दिखाया जाएगा. एट्रिब्यूट में हुए बदलावों को दोहराएं और attributeName
को खुला रखें. इसके बाद, यह देखें कि एलिमेंट में एट्रिब्यूट है या नहीं: इससे पता चलता है कि डायलॉग खुला है या नहीं. अगर यह खुला है, तो inert
एट्रिब्यूट हटाएं. इसके बाद, फ़ोकस को autofocus
का अनुरोध करने वाले किसी एलिमेंट पर या डायलॉग में मिले पहले button
एलिमेंट पर सेट करें. आखिर में, बंद करने और बंद किए गए इवेंट की तरह ही, खोलने वाले इवेंट को तुरंत डिस्पैच करें. ऐनिमेशन के खत्म होने का इंतज़ार करें. इसके बाद, खोले गए इवेंट को डिस्पैच करें.
हटाए गए इवेंट को जोड़ना
सिंगल पेज वाले ऐप्लिकेशन में, डायलॉग को अक्सर रास्तों या ऐप्लिकेशन की अन्य ज़रूरतों और स्थिति के आधार पर जोड़ा और हटाया जाता है. डायलॉग हटाए जाने पर, इवेंट या डेटा को साफ़ करने के लिए इसका इस्तेमाल किया जा सकता है.
इसके लिए, किसी दूसरे म्यूटेशन ऑब्ज़र्वर का इस्तेमाल किया जा सकता है. इस बार, हम डायलॉग एलिमेंट के एट्रिब्यूट पर नज़र रखने के बजाय, बॉडी एलिमेंट के चाइल्ड पर नज़र रखेंगे. साथ ही, डायलॉग एलिमेंट को हटाए जाने पर नज़र रखेंगे.
…
const dialogRemovedEvent = new Event('removed')
export default async function (dialog) {
…
dialogDeleteObserver.observe(document.body, {
attributes: false,
subtree: false,
childList: true,
})
}
const dialogDeleteObserver = new MutationObserver((mutations, observer) => {
mutations.forEach(mutation => {
mutation.removedNodes.forEach(removedNode => {
if (removedNode.nodeName === 'DIALOG') {
removedNode.removeEventListener('click', lightDismiss)
removedNode.removeEventListener('close', dialogClose)
removedNode.dispatchEvent(dialogRemovedEvent)
}
})
})
})
जब भी दस्तावेज़ के मुख्य हिस्से में चाइल्ड एलिमेंट जोड़े या हटाए जाते हैं, तब म्यूटेशन ऑब्ज़र्वर कॉलबैक को कॉल किया जाता है. जिन म्यूटेशन पर नज़र रखी जा रही है वे removedNodes
के लिए हैं. इनमें डायलॉग का nodeName
होता है. अगर कोई डायलॉग हटा दिया गया है, तो क्लिक और बंद करने वाले इवेंट हटा दिए जाते हैं, ताकि मेमोरी खाली हो जाए. साथ ही, कस्टम इवेंट को डिसपैच किया जाता है.
लोडिंग एट्रिब्यूट हटाना
डायलॉग को पेज में जोड़ने या पेज लोड होने पर, डायलॉग के बंद होने का ऐनिमेशन न चले, इसके लिए डायलॉग में लोडिंग एट्रिब्यूट जोड़ा गया है. नीचे दी गई स्क्रिप्ट, डायलॉग ऐनिमेशन के खत्म होने का इंतज़ार करती है. इसके बाद, एट्रिब्यूट हटा देती है. अब डायलॉग को आसानी से ऐनिमेट किया जा सकता है. साथ ही, हमने ध्यान भटकाने वाले ऐनिमेशन को छिपा दिया है.
export default async function (dialog) {
…
await animationsComplete(dialog)
dialog.removeAttribute('loading')
}
पेज लोड होने पर कीफ़्रेम ऐनिमेशन को रोकने की समस्या के बारे में ज़्यादा जानें.
सब साथ मिलकर
यहां पूरा dialog.js
दिया गया है. अब हमने हर सेक्शन के बारे में अलग-अलग जानकारी दे दी है:
// custom events to be added to <dialog>
const dialogClosingEvent = new Event('closing')
const dialogClosedEvent = new Event('closed')
const dialogOpeningEvent = new Event('opening')
const dialogOpenedEvent = new Event('opened')
const dialogRemovedEvent = new Event('removed')
// track opening
const dialogAttrObserver = new MutationObserver((mutations, observer) => {
mutations.forEach(async mutation => {
if (mutation.attributeName === 'open') {
const dialog = mutation.target
const isOpen = dialog.hasAttribute('open')
if (!isOpen) return
dialog.removeAttribute('inert')
// set focus
const focusTarget = dialog.querySelector('[autofocus]')
focusTarget
? focusTarget.focus()
: dialog.querySelector('button').focus()
dialog.dispatchEvent(dialogOpeningEvent)
await animationsComplete(dialog)
dialog.dispatchEvent(dialogOpenedEvent)
}
})
})
// track deletion
const dialogDeleteObserver = new MutationObserver((mutations, observer) => {
mutations.forEach(mutation => {
mutation.removedNodes.forEach(removedNode => {
if (removedNode.nodeName === 'DIALOG') {
removedNode.removeEventListener('click', lightDismiss)
removedNode.removeEventListener('close', dialogClose)
removedNode.dispatchEvent(dialogRemovedEvent)
}
})
})
})
// wait for all dialog animations to complete their promises
const animationsComplete = element =>
Promise.allSettled(
element.getAnimations().map(animation =>
animation.finished))
// click outside the dialog handler
const lightDismiss = ({target:dialog}) => {
if (dialog.nodeName === 'DIALOG')
dialog.close('dismiss')
}
const dialogClose = async ({target:dialog}) => {
dialog.setAttribute('inert', '')
dialog.dispatchEvent(dialogClosingEvent)
await animationsComplete(dialog)
dialog.dispatchEvent(dialogClosedEvent)
}
// page load dialogs setup
export default async function (dialog) {
dialog.addEventListener('click', lightDismiss)
dialog.addEventListener('close', dialogClose)
dialogAttrObserver.observe(dialog, {
attributes: true,
})
dialogDeleteObserver.observe(document.body, {
attributes: false,
subtree: false,
childList: true,
})
// remove loading attribute
// prevent page load @keyframes playing
await animationsComplete(dialog)
dialog.removeAttribute('loading')
}
dialog.js
मॉड्यूल का इस्तेमाल करना
मॉड्यूल से एक्सपोर्ट किए गए फ़ंक्शन को कॉल किया जाना चाहिए. साथ ही, उसे एक ऐसा डायलॉग एलिमेंट पास किया जाना चाहिए जिसमें ये नए इवेंट और फ़ंक्शन जोड़े जाने हैं:
import GuiDialog from './dialog.js'
const MegaDialog = document.querySelector('#MegaDialog')
const MiniDialog = document.querySelector('#MiniDialog')
GuiDialog(MegaDialog)
GuiDialog(MiniDialog)
ठीक इसी तरह, दोनों डायलॉग को लाइट डिसमिस, ऐनिमेशन लोडिंग से जुड़ी समस्याओं को ठीक करने, और काम करने के लिए ज़्यादा इवेंट के साथ अपग्रेड किया गया है.
नए कस्टम इवेंट सुने जा रहे हैं
अपग्रेड किए गए हर डायलॉग एलिमेंट में अब पांच नए इवेंट सुने जा सकते हैं. जैसे:
MegaDialog.addEventListener('closing', dialogClosing)
MegaDialog.addEventListener('closed', dialogClosed)
MegaDialog.addEventListener('opening', dialogOpening)
MegaDialog.addEventListener('opened', dialogOpened)
MegaDialog.addEventListener('removed', dialogRemoved)
उन इवेंट को मैनेज करने के दो उदाहरण यहां दिए गए हैं:
const dialogOpening = ({target:dialog}) => {
console.log('Dialog opening', dialog)
}
const dialogClosed = ({target:dialog}) => {
console.log('Dialog closed', dialog)
console.info('Dialog user action:', dialog.returnValue)
if (dialog.returnValue === 'confirm') {
// do stuff with the form values
const dialogFormData = new FormData(dialog.querySelector('form'))
console.info('Dialog form data', Object.fromEntries(dialogFormData.entries()))
// then reset the form
dialog.querySelector('form')?.reset()
}
}
डायलॉग एलिमेंट का इस्तेमाल करके बनाए गए डेमो में, मैंने बंद किए गए इवेंट और फ़ॉर्म डेटा का इस्तेमाल किया है. इससे सूची में नया अवतार एलिमेंट जोड़ा जा सकता है. टाइमिंग अच्छी है, क्योंकि डायलॉग का ऐनिमेशन खत्म हो गया है. इसके बाद, कुछ स्क्रिप्ट नए अवतार में ऐनिमेट होती हैं. नए इवेंट की मदद से, उपयोगकर्ता अनुभव को बेहतर बनाया जा सकता है.
सूचना dialog.returnValue
: इसमें वह क्लोज़ स्ट्रिंग होती है जिसे dialog close()
इवेंट को कॉल करते समय पास किया जाता है. dialogClosed
इवेंट में यह जानना ज़रूरी है कि डायलॉग बॉक्स बंद किया गया, रद्द किया गया या पुष्टि की गई. पुष्टि होने के बाद, स्क्रिप्ट फ़ॉर्म की वैल्यू को सेव करती है और फ़ॉर्म को रीसेट करती है. रीसेट करने की सुविधा इसलिए काम की है, ताकि डायलॉग बॉक्स को फिर से दिखाए जाने पर, वह खाली हो और नए सबमिशन के लिए तैयार हो.
नतीजा
अब आपको पता चल गया है कि मैंने यह कैसे किया. अब आप कैसे करेंगे‽ 🙂
आइए, हम अपने तरीकों में विविधता लाएं और वेब पर काॅन्टेंट पोस्ट करने के सभी तरीके जानें.
डेमो बनाएं और मुझे ट्वीट करें. इसके बाद, मैं इसे यहां कम्यूनिटी रीमिक्स सेक्शन में जोड़ दूंगा!
कम्यूनिटी रीमिक्स
- तीन तरह के डायलॉग के साथ @GrimLink.
- @mikemai2awesome ने एक बेहतरीन रीमिक्स बनाया है, जिसमें
display
प्रॉपर्टी में कोई बदलाव नहीं किया गया है. - @geoffrich_ ने Svelte और Svelte FLIP का इस्तेमाल करके, शानदार एनिमेशन बनाया है.
संसाधन
- Github पर सोर्स कोड
- डूडल अवतार