<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
प्रॉपर्टी वगैरह को अपने हिसाब से बनाने के लिए, कस्टम ऐनिमेशन देने का सबसे मुश्किल तरीका अपनाया है.
ओपन प्रॉप की मदद से स्टाइल बनाना
ज़रूरत के हिसाब से कलर इस्तेमाल करने और डिज़ाइन को एक जैसा रखने के लिए, मैंने बेशर्म होकर अपनी सीएसएस वैरिएबल लाइब्रेरी 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 के बिना, मिनी डायलॉग, स्क्रीन के बीच में मेगा डायलॉग की तरह ही दिखता है.
अपने वीडियो को पॉप (हाइलाइट) करें
आखिर में, डायलॉग को बेहतर बनाएं, ताकि वह पेज के ऊपर मौजूद किसी नर्म सतह की तरह दिखे. डायलॉग के कोनों को गोलाकार करके इस सॉफ़्टनेस को हासिल किया जाता है. ओपन प्रॉप के सावधानी से बनाए गए शैडो प्रॉप में से किसी एक की मदद से यह गहराई तक पहुंच जाती है:
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;
}
अतिरिक्त सुविधाएं
मैं इस सेक्शन को "एक्स्ट्रा" कहता/कहती हूं, क्योंकि आम तौर पर डायलॉग एलिमेंट की तुलना में, इस सेक्शन में मेरे डायलॉग एलिमेंट डेमो की तुलना में ज़्यादा काम है.
स्क्रोल के लिए कंटेनमेंट
डायलॉग दिखाए जाने पर, उपयोगकर्ता अब भी उसके पीछे वाले पेज को स्क्रोल कर पा रहा है, जो मुझे नहीं चाहिए:
आम तौर पर,
overscroll-behavior
मेरा सामान्य समाधान होगा, लेकिन खास जानकारी के मुताबिक,
इसके डायलॉग पर कोई असर नहीं पड़ता, क्योंकि यह स्क्रोल पोर्ट नहीं है. इसका मतलब है कि यह स्क्रोलर नहीं है. इसलिए, इससे बचने की कोई ज़रूरत नहीं है. मैं इस गाइड से नए इवेंट देखने के लिए 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>
की शैली बनाई जा रही है
इस डायलॉग में लेख के एलिमेंट की खास भूमिका है: यह ऐसा स्पेस है जिसे बड़े या लंबे डायलॉग के लिए स्क्रोल किया जा सकता है.
इसे पूरा करने के लिए, पैरंट फ़ॉर्म एलिमेंट ने अपने लिए कुछ सीमाएं तय की हैं. इससे लेख के बहुत बड़े होने पर,
इस एलिमेंट तक पहुंचने में रुकावटें आती हैं. 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 Props में कई मुख्य-फ़्रेम ऐनिमेशन होते हैं, जिन्हें इस्तेमाल करना आसान और समझने में आसान होता है. ये रहे ऐनिमेशन से जुड़े लक्ष्य और कुछ लेयर वाला तरीका:
- कम मोशन डिफ़ॉल्ट ट्रांज़िशन है, जिसमें एक सामान्य अपारदर्शिता फ़ेड इन और आउट होता है.
- अगर मोशन ठीक है, तो स्लाइड और स्केल ऐनिमेशन जोड़ दिए जाते हैं.
- मेगा डायलॉग के लिए रिस्पॉन्सिव मोबाइल लेआउट को स्लाइड करने के हिसाब से अडजस्ट किया गया है.
एक सुरक्षित और आसान डिफ़ॉल्ट ट्रांज़िशन
Open Props में फ़ेड इन और आउट करने के लिए मुख्य-फ़्रेम मौजूद हैं, लेकिन मुझे ट्रांज़िशन का यह लेयर वाला तरीका पसंद है,
इसलिए मुख्य-फ़्रेम वाले ऐनिमेशन के साथ ऐसा हो सकता है कि वे नए अपग्रेड हों. पहले हम पहले ही डायलॉग दिखने की स्टाइल को ओपैसिटी के साथ बनाते थे. साथ ही, [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-इन-1 डायलॉग के साथ @GrimLink.
- @mikemai2awesome को एक अच्छा
रीमिक्स बनाएं, जिससे
display
प्रॉपर्टी में कोई बदलाव न हो. - Svelte और अच्छे Svelte FLIP पॉलिश के साथ @geoffrich_.
रिसॉर्स
- GitHub पर सोर्स कोड
- डूडल अवतार