<dialog>
एलिमेंट की मदद से, कलर-अडैप्टिव, रिस्पॉन्सिव, और ऐक्सेस किए जा सकने वाले मिनी और मेगा मॉडल बनाने के तरीके के बारे में बुनियादी जानकारी.
मैं इस पोस्ट में <dialog>
एलिमेंट की मदद से कलर-अडैप्टिव, रिस्पॉन्सिव, और ऐक्सेस किए जा सकने वाले मिनी और मेगा मॉडल बनाने के बारे में अपने विचार बताना चाहती हूँ.
डेमो देखें और सोर्स देखें!
अगर आपको वीडियो देखना है, तो इस पोस्ट का YouTube वर्शन यहां देखें:
खास जानकारी
<dialog>
एलिमेंट, पेज में खोज से जुड़ी जानकारी या कार्रवाई के लिए बेहतरीन है. यह सोचें कि कब उपयोगकर्ता अनुभव को कई पेज वाली कार्रवाई के बजाय एक ही पेज पर की जाने वाली कार्रवाई से फ़ायदा हो सकता है. ऐसा तब हो सकता है, जब फ़ॉर्म छोटा हो या उपयोगकर्ता सिर्फ़ उसकी पुष्टि करे या उसे रद्द करे.
<dialog>
एलिमेंट की सेटिंग, हाल ही में सभी ब्राउज़र पर ठीक से काम कर रही है:
मुझे पता चला कि एलिमेंट में कुछ चीज़ें छूट रही थीं, इसलिए इस जीयूआई चैलेंज में मैं डेवलपर एक्सपीरियंस आइटम जोड़ता हूं, जिनकी मुझे उम्मीद थी: अतिरिक्त इवेंट, लाइट खारिज करना, कस्टम ऐनिमेशन, और एक मिनी और मेगा टाइप.
मार्कअप
<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 का इस्तेमाल किसी एलिमेंट को छोड़ने पर फ़ोकस करने के लिए किया जाता था.
इसके बाद, यह एलिमेंट को इंटरसेप्ट करके वापस रखता है.
inert
के बाद, दस्तावेज़ के किसी भी हिस्से को "फ़्रीज़ किया" जा सकता है. ऐसा हो सकता है कि उस हिस्से पर अब फ़ोकस न किया जा सके या वह माउस के साथ इंटरैक्टिव हो. फ़ोकस को फंसाने के बजाय, फ़ोकस को दस्तावेज़ के सिर्फ़ इंटरैक्टिव हिस्से पर ले जाया जाता है.
किसी एलिमेंट को खोलना और अपने-आप फ़ोकस करना
डिफ़ॉल्ट रूप से, डायलॉग एलिमेंट, डायलॉग मार्कअप में फ़ोकस किए जा सकने वाले पहले एलिमेंट पर फ़ोकस असाइन करेगा. अगर यह एलिमेंट उपयोगकर्ता के लिए डिफ़ॉल्ट रूप से सबसे सही नहीं है, तो autofocus
एट्रिब्यूट का इस्तेमाल करें. जैसा कि पहले बताया गया है, इसे 'रद्द करें' बटन के बजाय 'पुष्टि करें' बटन पर डालना सबसे सही तरीका है. इससे यह पक्का होता है कि
पुष्टि को जान-बूझकर किया गया है, न कि गलती से.
Escape कुंजी से बंद करना
रुकावट डालने वाले इस एलिमेंट को आसानी से बंद करना ज़रूरी है. अच्छी बात यह है कि डायलॉग एलिमेंट आपके लिए एस्केप पासकोड संभाल लेगा, जिससे आपको ऑर्केस्ट्रा के बोझ से छुटकारा मिल जाएगा.
स्टाइल
डायलॉग एलिमेंट और हार्ड पाथ को स्टाइल करने का एक आसान पाथ है. डायलॉग की डिसप्ले प्रॉपर्टी में बदलाव किए बिना और इसकी सीमाओं के साथ काम करके, इस प्रोजेक्ट को आसानी से पूरा किया जा सकता है. डायलॉग को खोलने और बंद करने, display
प्रॉपर्टी का मालिकाना हक पाने वगैरह के लिए, कस्टम ऐनिमेशन उपलब्ध कराने के लिए मुझे काफ़ी मुश्किलों का सामना करना पड़ा.
ओपन प्रॉप्स की मदद से स्टाइल करना
हर जगह के हिसाब से रंग और डिज़ाइन को एक जैसा बनाए रखने के लिए, मैंने बेशर्मी से अपनी सीएसएस वैरिएबल लाइब्रेरी ओपन प्रॉप्स को इंपोर्ट कर लिया है. मुफ़्त में दिए गए वैरिएबल के अलावा, मैं एक नॉर्मलाइज़ फ़ाइल और कुछ बटन भी इंपोर्ट करता/करती हूं. ओपन प्रॉप्स, दोनों को इंपोर्ट करने के विकल्प के तौर पर उपलब्ध कराया जाता है. इस तरह के इंपोर्ट से मुझे डायलॉग और डेमो को पसंद के मुताबिक बनाने पर ध्यान देने में मदद मिलती है. साथ ही, उन्हें सपोर्ट करने और अच्छा दिखाने के लिए कई स्टाइल की ज़रूरत नहीं पड़ती.
<dialog>
एलिमेंट का लुक तय करना
डिसप्ले प्रॉपर्टी का मालिकाना हक
किसी डायलॉग एलिमेंट के दिखने और छिपाने का डिफ़ॉल्ट तरीका, डिसप्ले प्रॉपर्टी को block
से none
में टॉगल कर देता है. इसका मतलब है कि इसे सिर्फ़ अंदर या बाहर
ऐनिमेट नहीं किया जा सकता. मैं इन-आउट और आउट दोनों को ऐनिमेट करना चाहता हूँ और इसके लिए सबसे पहले
अपनी खुद की डिसप्ले प्रॉपर्टी सेट करें:
dialog {
display: grid;
}
ऊपर दिए गए सीएसएस स्निपेट में दिखाई गई डिसप्ले प्रॉपर्टी की वैल्यू में बदलाव करने और उस पर मालिकाना हक होने से, सही उपयोगकर्ता अनुभव देने के लिए काफ़ी स्टाइल को मैनेज करने की ज़रूरत पड़ती है. सबसे पहले, डायलॉग बॉक्स की डिफ़ॉल्ट स्थिति बंद होती है. इस स्थिति को विज़ुअल तौर पर दिखाया जा सकता है और डायलॉग को नीचे दी गई स्टाइल के साथ इंटरैक्शन होने से रोका जा सकता है:
dialog:not([open]) {
pointer-events: none;
opacity: 0;
}
अब डायलॉग नहीं दिखता है. साथ ही, न खुलने पर उससे इंटरैक्ट नहीं किया जा सकता. बाद में
मैं डायलॉग पर inert
एट्रिब्यूट को मैनेज करने के लिए, कुछ JavaScript जोड़ूँगी. इससे यह पक्का किया जा सकेगा कि कीबोर्ड और स्क्रीन रीडर
का इस्तेमाल करने वाले लोग भी छिपे हुए डायलॉग को ऐक्सेस न कर पाएँ.
डायलॉग को अडैप्टिव कलर थीम देता है
color-scheme
आपके दस्तावेज़ को ब्राउज़र से उपलब्ध कराई गई,
हल्के और गहरे रंग वाली सिस्टम की प्राथमिकताओं के हिसाब से, पसंद के मुताबिक रंग वाली थीम में ऑप्ट इन करता है. हालांकि, मुझे डायलॉग एलिमेंट में
ज़रूरत से ज़्यादा बदलाव करना था. ओपन प्रॉप्स में कुछ सर्फ़ेस कलर उपलब्ध हैं, जो लाइट और गहरे रंग वाले सिस्टम की प्राथमिकताओं के हिसाब से अपने-आप बदल जाते हैं. यह बिलकुल वैसा ही है जैसा 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 के बिना, मिनी डायलॉग स्क्रीन के बीच में मेगा डायलॉग की तरह दिखता है.
शानदार बनाएं
आखिर में, डायलॉग बॉक्स की क्वालिटी को बेहतर बनाएं, ताकि यह पेज के ऊपर, एक मुलायम सतह की तरह दिखे. सॉफ़्टनेस को डायलॉग के कोनों को गोल करके बनाया जाता है. ओपन प्रॉप्स के सावधानी से बनाए गए शैडो प्रॉप्स में से किसी एक की मदद से, आपको गहराई तक पहुंचने में मदद मिलेगी:
dialog {
…
border-radius: var(--radius-3);
box-shadow: var(--shadow-6);
}
बैकग्राउंड pseudo एलिमेंट को पसंद के मुताबिक बनाना
मैंने बैकड्रॉप के साथ बहुत हल्के-फुल्के काम करना चुना है. मेगा डायलॉग में सिर्फ़ backdrop-filter
का इस्तेमाल करके ब्लर इफ़ेक्ट लगाया है:
dialog[modal-mode="mega"]::backdrop {
backdrop-filter: blur(25px);
}
मैंने इस उम्मीद में backdrop-filter
पर एक ट्रांज़िशन भी रखा कि ब्राउज़र आने वाले समय में बैकड्रॉप एलिमेंट को ट्रांज़िशन करने की अनुमति देंगे:
dialog::backdrop {
transition: backdrop-filter .5s ease;
}
अतिरिक्त स्टाइलिंग
मैं इस सेक्शन को "अतिरिक्त" कहता हूं, क्योंकि इसका काम डायलॉग एलिमेंट के मुकाबले मेरे डायलॉग एलिमेंट के डेमो से ज़्यादा है.
स्क्रोल करने की जगह
डायलॉग दिखने पर भी, उपयोगकर्ता इसके पीछे के पेज को स्क्रोल कर सकता है. मुझे ऐसा नहीं करना चाहिए:
आम तौर पर, overscroll-behavior
मेरा सामान्य समाधान होगा. हालांकि, खास जानकारी के मुताबिक, डायलॉग पर कोई असर नहीं पड़ता, क्योंकि यह स्क्रोल पोर्ट नहीं है. इसका मतलब है कि यह स्क्रोलर नहीं है. इसलिए, इसे रोकने की कोई ज़रूरत नहीं है. मैं इस गाइड के नए इवेंट, जैसे कि "बंद" और "खोला गया" और दस्तावेज़ पर overflow: hidden
को टॉगल करने के लिए JavaScript का इस्तेमाल कर सकती थी. इसके अलावा, मैं सभी ब्राउज़र में :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);
}
}
हेडर 'बंद करें' बटन के लुक को बेहतर बनाना
डेमो में प्रॉप्स के बटन का इस्तेमाल किया गया है, इसलिए 'बंद करें' बटन को इस तरह से कस्टमाइज़ किया गया है कि वह गोल आइकॉन पर फ़ोकस कर सके. जैसे:
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>
का लुक तय किया जा रहा है
इस डायलॉग में लेख के एलिमेंट की एक खास भूमिका होती है: यह एक स्पेस होता है, जिसे बड़े या लंबे डायलॉग के मामले में स्क्रोल किया जा सकता है.
यह पूरा करने के लिए, पैरंट फ़ॉर्म एलिमेंट ने अपने लिए कुछ तय सीमा तय की है.
इससे लेख के एलिमेंट के साइज़ को बहुत बड़ा होने पर, इस पर उसे
पहुंचने में मदद मिलती है. 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>
का लुक तय किया जा रहा है
फ़ुटर की भूमिका में, ऐक्शन बटन के मेन्यू शामिल होते हैं. Flexbox का इस्तेमाल, फ़ुटर इनलाइन ऐक्सिस के आखिर में कॉन्टेंट को अलाइन करने के लिए किया जाता है. इसके बाद, बटन के लिए कुछ जगह तय करने के लिए कुछ स्पेस दिया जाता है.
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;
}
Animation
डायलॉग एलिमेंट अक्सर ऐनिमेट होते हैं, क्योंकि वे विंडो में आते हैं और उससे बाहर निकलते हैं. इस एंट्रेंस और एग्ज़िट के लिए डायलॉग को कुछ सहायक मोशन देने से, उपयोगकर्ताओं को खुद को फ़्लो में अंदाज़ करने में मदद मिलती है.
आम तौर पर डायलॉग एलिमेंट को सिर्फ़ अंदर ऐनिमेट किया जा सकता है, बाहर नहीं. ऐसा इसलिए होता है, क्योंकि
ब्राउज़र, एलिमेंट पर display
प्रॉपर्टी को टॉगल करता है. पहले, गाइड में डिसप्ले को ग्रिड पर सेट किया जाता था और कभी भी इसे किसी के लिए सेट नहीं किया जाता था. इससे अंदर और बाहर ऐनिमेट
करने की क्षमता आती है.
ओपन प्रॉप्स में इस्तेमाल के लिए कई मुख्य-फ़्रेम ऐनिमेशन दिए गए हैं. इससे ऑर्कस्ट्रैशन आसान हो जाता है और इसे पढ़ा जा सकता है. मैंने ऐनिमेशन के ये लक्ष्य और कई स्तर वाली रणनीति अपनाई:
- कम की गई गति डिफ़ॉल्ट ट्रांज़िशन है, जिसमें सामान्य ओपैसिटी फ़ेड इन और आउट होती है.
- अगर गति ठीक है, तो स्लाइड और स्केल ऐनिमेशन जोड़े जाते हैं.
- मेगा डायलॉग के लिए रिस्पॉन्सिव मोबाइल लेआउट को स्लाइड आउट में अडजस्ट किया गया है.
सुरक्षित और बेहतर डिफ़ॉल्ट ट्रांज़िशन
वैसे तो ओपन प्रॉप्स में फ़ेडिंग इन और आउटिंग के लिए कीफ़्रेम दिए जाते हैं, लेकिन मैं डिफ़ॉल्ट रूप से ट्रांज़िशन के इस लेयर वाले तरीके को पसंद करती हूं और मुख्य-फ़्रेम ऐनिमेशन को संभावित अपग्रेड के तौर पर पसंद करती हूं. पहले हमने डायलॉग के दिखने की स्टाइल को पहले से ही, अपारदर्शिता के हिसाब से सेट किया हुआ था और [open]
एट्रिब्यूट के हिसाब से 1
या 0
को व्यवस्थित किया था. 0% और 100% के बीच
ट्रांज़िशन करने के लिए, ब्राउज़र को बताएं कि कितनी देर तक और किस प्रकार की
आप ईज़िंग चाहते हैं:
dialog {
transition: opacity .5s var(--ease-3);
}
ट्रांज़िशन में मोशन जोड़ना
अगर उपयोगकर्ता को हिलने-डुलने में दिक्कत नहीं है, तो मेगा और मिनी डायलॉग, दोनों को अंदर जाने के रास्ते में ऊपर की ओर स्लाइड करना चाहिए और बाहर निकलने पर बड़ा होना चाहिए. prefers-reduced-motion
मीडिया क्वेरी और कुछ ओपन प्रॉप्स की मदद से ऐसा किया जा सकता है:
@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;
}
}
मोबाइल के लिए एग्ज़िट ऐनिमेशन अपनाना
स्टाइलिंग सेक्शन में पहले, मोबाइल डिवाइस के लिए मेगा डायलॉग स्टाइल का इस्तेमाल किया जाता है, ताकि इसे ऐक्शन शीट की तरह बनाया जा सके. जैसे, किसी काग़ज़ का कोई छोटा टुकड़ा स्क्रीन के नीचे से ऊपर की ओर कट जाता हो और अब भी नीचे लगा हो. स्केल आउट से बाहर निकलने का ऐनिमेशन इस नए डिज़ाइन के हिसाब से ठीक नहीं है और हम इसे कुछ मीडिया क्वेरी और कुछ ओपन प्रॉप्स की मदद से बदल सकते हैं:
@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 की मदद से वापस लाया जा सकता है. हर बार अलग-अलग बटन से फ़ंक्शन को कॉल करने पर, मैंने आपको क्लोज़ स्ट्रिंग भी दी होंगी, ताकि यूज़र इंटरैक्शन के बारे में अपने ऐप्लिकेशन के बारे में जानकारी दी जा सके.
क्लोज़िंग और बंद इवेंट जोड़े जा रहे हैं
डायलॉग एलिमेंट, क्लोज़ इवेंट के साथ आता है: यह डायलॉग 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
एक एक साथ काम नहीं करने वाला
फ़ंक्शन है.
इसके बाद, यह प्रॉमिस वापस आ गया और पूरे भरोसे के साथ क्लोज़्ड इवेंट में शामिल हो सकता है.await
उद्घाटन और खोले गए इवेंट जोड़े जा रहे हैं
इन इवेंट को जोड़ना उतना आसान नहीं होता, क्योंकि बिल्ट-इन डायलॉग एलिमेंट में करीब-करीब वैसा इवेंट नहीं दिखता है जो खुला हुआ है. डायलॉग की विशेषताओं में बदलाव के बारे में अहम जानकारी देने के लिए, मैंने MutationObserver का इस्तेमाल किया है. इस ऑब्ज़र्वर में, मैं ओपन एट्रिब्यूट में हुए बदलावों पर नज़र रखूँगी और उसके हिसाब से कस्टम इवेंट मैनेज करूँगी.
जिस तरह हमने क्लोज़िंग और क्लोज़्ड इवेंट को शुरू किया, उसी तरह 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
: इसमें डायलॉग close()
इवेंट को कॉल करते समय पास की गई क्लोज़ स्ट्रिंग शामिल होती है. dialogClosed
इवेंट में, यह जानना ज़रूरी है कि डायलॉग बॉक्स बंद है, रद्द किया गया है या उसकी पुष्टि की गई है. अगर इसकी पुष्टि हो जाती है, तो
स्क्रिप्ट, फ़ॉर्म की वैल्यू इकट्ठा करके फ़ॉर्म को रीसेट कर देती है. रीसेट करना उपयोगी होता है, ताकि
जब डायलॉग फिर से दिखाया जाए, तो वह खाली हो और नए सबमिशन के लिए तैयार हो.
नतीजा
अब जब आपको पता है कि मैंने इसे कैसे किया, तो आप कैसे करेंगे ‽ 🙂
आइए, हम अलग-अलग तरह के काम करते हैं और वेब पर काम करने के सभी तरीके सीखते हैं.
एक डेमो बनाएं, मुझे ट्वीट करें लिंक, और नीचे दिए कम्यूनिटी रीमिक्स सेक्शन में जोड़ दिया जाएगा!
कम्यूनिटी रीमिक्स
- 3-in-1 डायलॉग के साथ @GrimLink.
- @mikemai2awesome का
एक बढ़िया रीमिक्स होना, जो
display
प्रॉपर्टी में बदलाव नहीं करता. - Svelte के साथ @geoffrich_ और Svelte FLIP की अच्छी क्वालिटी के साथ.
संसाधन
- GitHub पर सोर्स कोड
- डूडल के अवतार