<dialog>
öğesiyle renge uyarlanabilir, duyarlı ve erişilebilir mini ve mega modallar oluşturma hakkında temel bilgiler.
Bu yayında, <dialog>
öğesiyle renge uyarlanabilir, duyarlı ve erişilebilir mini ve mega modallar oluşturma konusundaki düşüncelerimi paylaşmak istiyorum.
Demoyu deneyin ve kaynağı görüntüleyin.
Video tercih ediyorsanız bu yayının YouTube versiyonunu aşağıda bulabilirsiniz:
Genel Bakış
<dialog>
öğesi, sayfa içi bağlamsal bilgiler veya işlemler için idealdir. Kullanıcı deneyiminin, çok sayfalı işlem yerine aynı sayfa işleminden yararlanabileceği durumları göz önünde bulundurun. Örneğin, form küçük olabilir veya kullanıcıdan yalnızca onaylama ya da iptal etme işlemi yapması isteniyor olabilir.
<dialog>
öğesi son zamanlarda tarayıcılarda kararlı hale geldi:
Öğede birkaç şeyin eksik olduğunu fark ettim. Bu nedenle, GUI Challenge'da beklediğim geliştirici deneyimi öğelerini (ek etkinlikler, ışıkla kapatma, özel animasyonlar ve mini ile mega türü) ekliyorum.
Brüt kar
<dialog>
öğesinin temel özellikleri mütevazıdır. Öğe otomatik olarak gizlenir ve içeriğinize yer paylaşımı uygulamak için yerleşik stillere sahiptir.
<dialog>
…
</dialog>
Bu referans değerini iyileştirebiliriz.
Geleneksel olarak, bir iletişim kutusu öğesi, bir modal ile birçok ortak noktaya sahiptir ve genellikle adlar birbirinin yerine kullanılabilir. Burada hem küçük iletişim kutusu pop-up'ları (mini) hem de tam sayfa iletişim kutuları (mega) için iletişim kutusu öğesini kullandım. Bunları mega ve mini olarak adlandırdım. Her iki iletişim kutusu da farklı kullanım alanlarına göre biraz uyarlandı.
Tür belirtmenize olanak tanıyan bir modal-mode
özelliği ekledim:
<dialog id="MegaDialog" modal-mode="mega"></dialog>
<dialog id="MiniDialog" modal-mode="mini"></dialog>
Her zaman olmasa da genellikle etkileşim bilgilerini toplamak için iletişim kutusu öğeleri kullanılır. İletişim kutusu öğelerinin içindeki formlar birlikte kullanılmak üzere tasarlanmıştır.
JavaScript'in kullanıcının girdiği verilere erişebilmesi için iletişim kutusu içeriğinizi bir form öğesiyle sarmalamanız önerilir. Ayrıca, method="dialog"
kullanan bir formdaki düğmeler, JavaScript olmadan bir iletişim kutusunu kapatabilir ve verileri iletebilir.
<dialog id="MegaDialog" modal-mode="mega">
<form method="dialog">
…
<button value="cancel">Cancel</button>
<button value="confirm">Confirm</button>
</form>
</dialog>
Mega iletişim kutusu
Mega iletişim kutusunda formun içinde üç öğe bulunur: <header>
, <article>
ve <footer>
.
Bunlar, anlamsal kapsayıcıların yanı sıra iletişim kutusunun sunumu için stil hedefleri olarak da kullanılır. Başlık, modal pencereye başlık verir ve bir kapat düğmesi sunar. Bu makale, form girişleri ve bilgileriyle ilgilidir. Altbilgide bir <menu>
işlem düğmesi bulunur.
<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>
İlk menü düğmesinde autofocus
ve onclick
satır içi etkinlik işleyicisi var. İletişim kutusu açıldığında autofocus
özelliği odaklanır. Bunu onay düğmesine değil, iptal düğmesine koymak en iyi uygulamadır. Bu, onay işleminin kasıtlı olmasını ve yanlışlıkla yapılmamasını sağlar.
Mini iletişim kutusu
Mini iletişim kutusu, mega iletişim kutusuna çok benzer. Tek fark, <header>
öğesinin olmamasıdır. Bu sayede daha küçük ve daha satır içi olabilir.
<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>
İletişim kutusu öğesi, veri ve kullanıcı etkileşimi toplayabilen tam bir görünüm alanı öğesi için güçlü bir temel sağlar. Bu temel bilgiler, sitenizde veya uygulamanızda çok ilginç ve güçlü etkileşimler oluşturabilir.
Erişilebilirlik
İletişim kutusu öğesi, yerleşik erişilebilirlik açısından çok iyidir. Bu özellikleri her zamanki gibi eklemek yerine, birçoğu zaten mevcut.
Odaklanmayı geri yükleme
Building a sidenav component başlıklı makalede manuel olarak yaptığımız gibi, bir şeyi doğru şekilde açıp kapatırken ilgili açma ve kapatma düğmelerine odaklanılması önemlidir. Bu yan gezinme çubuğu açıldığında odak kapatma düğmesine yerleştirilir. Kapat düğmesine basıldığında odak, düğmeyi açan düğmeye geri döner.
İletişim kutusu öğesinde bu, yerleşik varsayılan davranıştır:
Maalesef iletişim kutusunu animasyonla açıp kapatmak isterseniz bu işlev kaybolur. JavaScript bölümünde bu işlevi geri yükleyeceğim.
Odaklanmayı yakalama
İletişim kutusu öğesi, dokümanda sizin için
inert
yönetir. inert
öncesinde, bir öğeden ayrılan odağı izlemek için JavaScript kullanılıyordu. Bu noktada, odak yakalanıp geri yerleştiriliyordu.
inert
işleminden sonra belgenin herhangi bir bölümü "dondurulabilir". Bu durumda, bu bölümler artık odak hedefi olmaz veya fareyle etkileşimli olarak kullanılamaz. Odak, yakalanmak yerine dokümanın tek etkileşimli bölümüne yönlendirilir.
Bir öğeyi açma ve otomatik olarak odaklama
Varsayılan olarak, iletişim kutusu öğesi, iletişim kutusu işaretlemesindeki ilk odaklanılabilir öğeye odak atar. Bu, kullanıcının varsayılan olarak kullanması için en iyi öğe değilse autofocus
özelliğini kullanın. Daha önce de belirttiğim gibi, bunu onay düğmesine değil iptal düğmesine koymak en iyi uygulamadır. Bu sayede onay işleminin kasıtlı olarak yapılması ve yanlışlıkla yapılmaması sağlanır.
Escape tuşuyla kapatma
Bu potansiyel olarak kesintiye neden olabilecek öğenin kolayca kapatılabilmesi önemlidir. Neyse ki iletişim kutusu öğesi, çıkış tuşunu sizin için işleyerek sizi düzenleme yükünden kurtarır.
Stiller
İletişim kutusu öğesini stilize etmenin kolay ve zor bir yolu vardır. Kolay yol, iletişim kutusunun görüntüleme özelliği değiştirilmeden ve sınırlamalarıyla çalışılarak elde edilir. İletişim kutusunu açma ve kapatma için özel animasyonlar sağlamak, display
özelliğini devralmak ve daha fazlası için zorlu yolu tercih ediyorum.
Open Props ile stil oluşturma
Uyarlanabilir renkleri ve genel tasarım tutarlılığını hızlandırmak için CSS değişken kitaplığım Open Props'u utanmadan kullandım. Ücretsiz olarak sağlanan değişkenlere ek olarak, Open Props'un isteğe bağlı içe aktarma olarak sunduğu bir normalize dosyası ve bazı düğmeler de içe aktarıyorum. Bu içe aktarmalar, desteklemek ve iyi görünmesini sağlamak için çok fazla stil gerektirmeyen diyalog ve demoyu özelleştirmeye odaklanmama yardımcı oluyor.
<dialog>
öğesini biçimlendirme
Görüntüleme mülkünün sahibi olma
Bir iletişim kutusu öğesinin varsayılan gösterme ve gizleme davranışı, display özelliğini block
ile none
arasında değiştirir. Bu nedenle, maalesef yalnızca animasyonla gösterilebilir, animasyonla gizlenemez. Hem giriş hem de çıkış animasyonu eklemek istiyorum. Bunun için ilk adım olarak kendi display özelliğimi ayarlamam gerekiyor:
dialog {
display: grid;
}
Yukarıdaki CSS snippet'inde gösterildiği gibi, display özelliği değerini değiştirerek (dolayısıyla sahiplenerek) uygun kullanıcı deneyimini kolaylaştırmak için önemli miktarda stilin yönetilmesi gerekir. İlk olarak, iletişim kutusunun varsayılan durumu kapalıdır. Bu durumu görsel olarak temsil edebilir ve aşağıdaki stilleri kullanarak iletişim kutusunun etkileşim almasını engelleyebilirsiniz:
dialog:not([open]) {
pointer-events: none;
opacity: 0;
}
Artık iletişim kutusu görünmez ve açık değilken etkileşimde bulunulamaz. Daha sonra, klavye ve ekran okuyucu kullanıcılarının da gizli iletişim kutusuna ulaşamamasını sağlamak için iletişim kutusundaki inert
özelliğini yönetmek üzere biraz JavaScript ekleyeceğim.
İletişim kutusuna uyarlanabilir renk teması verme
color-scheme
, dokümanınızın tarayıcı tarafından sağlanan, açık ve koyu sistem tercihlerine göre uyarlanabilir bir renk teması kullanmasına olanak tanırken iletişim kutusu öğesini daha fazla özelleştirmek istedim. Open Props, color-scheme
kullanmaya benzer şekilde, açık ve koyu sistem tercihlerine otomatik olarak uyum sağlayan birkaç yüzey rengi sunar. Bunlar, tasarımda katman oluşturmak için harika bir yöntemdir. Katmanlı yüzey görünümünü görsel olarak desteklemek için renk kullanmayı çok severim. Arka plan rengi var(--surface-1)
; bu katmanın üzerinde yer almak için var(--surface-2)
kullanın:
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);
}
}
Başlık ve altbilgi gibi alt öğeler için daha sonra daha uyarlanabilir renkler eklenecektir. Bunları iletişim kutusu öğesi için ekstra olarak değerlendiriyorum ancak ilgi çekici ve iyi tasarlanmış bir iletişim kutusu tasarımı oluşturmak için gerçekten önemli.
Duyarlı iletişim kutusu boyutlandırma
İletişim kutusu, boyutunu içeriğine göre ayarlayacak şekilde varsayılan olarak ayarlanır. Bu genellikle iyi bir seçenektir. Buradaki amacım, max-inline-size
öğesini okunabilir bir boyuta (--size-content-3
= 60ch
) veya görüntü alanı genişliğinin% 90'ına sınırlamaktır. Bu, iletişim kutusunun mobil cihazda kenardan kenara gitmemesini ve masaüstü ekranında okunması zor olacak kadar geniş olmamasını sağlar. Ardından, iletişim kutusunun sayfa yüksekliğini aşmaması için bir
max-block-size
ekliyorum. Bu, uzun bir iletişim kutusu öğesi olması durumunda iletişim kutusunun kaydırılabilir alanının nerede olduğunu belirtmemiz gerektiği anlamına da gelir.
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
ifadesinin iki kez kullanıldığına dikkat edin. Birincisi, fiziksel bir görünüm alanı birimi olan 80vh
kullanır. Aslında istediğim, uluslararası kullanıcılar için iletişim kutusunu göreceli akış içinde tutmak. Bu nedenle, daha kararlı hale geldiğinde kullanmak üzere ikinci bildirimde mantıksal, daha yeni ve yalnızca kısmen desteklenen dvb
birimini kullanıyorum.
Mega iletişim kutusu konumlandırma
Bir iletişim kutusu öğesinin konumlandırılmasına yardımcı olmak için iki bölümünü incelemek faydalı olacaktır: tam ekran arka plan ve iletişim kutusu kapsayıcısı. Arka plan, her şeyi kaplamalıdır. Bu iletişim kutusunun önde olduğunu ve arkasındaki içeriğe erişilemediğini desteklemek için gölge efekti sağlamalıdır. İletişim kutusu kapsayıcısı, bu arka plan üzerinde kendini serbestçe ortalayabilir ve içeriklerinin gerektirdiği şekli alabilir.
Aşağıdaki stiller, iletişim kutusu öğesini pencereye sabitler, her köşeye doğru uzatır ve içeriği ortalamak için margin: auto
öğesini kullanır:
dialog {
…
margin: auto;
padding: 0;
position: fixed;
inset: 0;
z-index: var(--layer-important);
}
Mobil mega iletişim kutusu stilleri
Küçük görünüm pencerelerinde, bu tam sayfa mega modalı biraz farklı şekilde stillendiriyorum. Alt kenar boşluğunu 0
olarak ayarladım. Bu, iletişim kutusu içeriğini görüntü alanının en altına getiriyor. Birkaç stil düzenlemesiyle iletişim kutusunu, kullanıcının başparmaklarına daha yakın bir işlem sayfasına dönüştürebilirim:
@media (max-width: 768px) {
dialog[modal-mode="mega"] {
margin-block-end: 0;
border-end-end-radius: 0;
border-end-start-radius: 0;
}
}
Mini iletişim kutusu konumlandırma
Masaüstü bilgisayarda olduğu gibi daha büyük bir görünüm alanı kullanırken mini iletişim kutularını, bunları çağıran öğenin üzerine yerleştirmeyi tercih ettim. Bunun için JavaScript'e ihtiyacım var. Kullandığım tekniği burada bulabilirsiniz ancak bu makalenin kapsamı dışında olduğunu düşünüyorum. JavaScript olmadan mini iletişim kutusu, mega iletişim kutusu gibi ekranın ortasında görünür.
Çarpıcı hale getirme
Son olarak, iletişim kutusuna biraz stil katın. Böylece, iletişim kutusu sayfadan çok daha yukarıda duran yumuşak bir yüzey gibi görünür. Yumuşaklık, iletişim kutusunun köşeleri yuvarlanarak elde edilir. Derinlik, Open Props'un özenle hazırlanmış shadow props öğelerinden biriyle sağlanır:
dialog {
…
border-radius: var(--radius-3);
box-shadow: var(--shadow-6);
}
Arka plan sözde öğesini özelleştirme
Arka planla çok hafif bir şekilde çalışmayı tercih ettim ve mega iletişim kutusuna yalnızca backdrop-filter
ile bulanıklık efekti ekledim:
dialog[modal-mode="mega"]::backdrop {
backdrop-filter: blur(25px);
}
Ayrıca, tarayıcıların gelecekte arka plan öğesinin geçişine izin vereceği umuduyla backdrop-filter
öğesine geçiş eklemeyi de tercih ettim:
dialog::backdrop {
transition: backdrop-filter .5s ease;
}
Stil ekstraları
Bu bölüme "ekstralar" adını veriyorum çünkü bu bölüm, genel olarak iletişim kutusu öğesinden ziyade iletişim kutusu öğesi demomla daha çok ilgili.
Kaydırma kapsama
İletişim kutusu gösterildiğinde kullanıcı, arkasındaki sayfayı kaydırmaya devam edebiliyor. Bunu istemiyorum:
Normalde,
overscroll-behavior
her zamanki çözümüm olurdu ancak özelliklere göre,
kaydırma bağlantı noktası olmadığı için (yani kaydırıcı olmadığı için) bu çözümün iletişim kutusu üzerinde hiçbir etkisi yok. Bu nedenle, engellenecek bir şey yok. Bu kılavuzdaki "closed" ve "opened" gibi yeni etkinlikleri izlemek için JavaScript'i kullanabilir ve dokümanda overflow: hidden
simgesini açıp kapatabilirim. Alternatif olarak, :has()
simgesinin tüm tarayıcılarda kararlı hale gelmesini bekleyebilirim:
html:has(dialog[open][modal-mode="mega"]) {
overflow: hidden;
}
Artık mega iletişim kutusu açıkken HTML dokümanında overflow: hidden
bulunuyor.
<form>
düzeni
Kullanıcıdan etkileşim bilgilerini toplamak için çok önemli bir öğe olmasının yanı sıra, burada başlık, altbilgi ve makale öğelerini düzenlemek için de kullanıyorum. Bu düzenle, makale alt öğesini kaydırılabilir bir alan olarak ifade etmek istiyorum. Bu işlemi grid-template-rows
ile yapıyorum.
Makale öğesine 1fr
verilir ve formun kendisi, iletişim kutusu öğesiyle aynı maksimum yüksekliğe sahiptir. Bu sabit yüksekliği ve sabit satır boyutunu ayarladığınızda, makale öğesi sınırlandırılabilir ve taşma durumunda kaydırılabilir:
dialog > form {
display: grid;
grid-template-rows: auto 1fr auto;
align-items: start;
max-block-size: 80vh;
max-block-size: 80dvb;
}
İletişim kutusunu stilize etme <header>
Bu öğenin rolü, iletişim kutusu içeriği için bir başlık sağlamak ve kolayca bulunabilen bir kapatma düğmesi sunmaktır. Ayrıca, iletişim kutusu makale içeriğinin arkasında görünmesi için yüzey rengi de verilir. Bu koşullar, öğelerin kenarlara yerleştirildiği ve dikey olarak hizalandığı bir flexbox kapsayıcıya, başlığa ve kapatma düğmelerine biraz alan sağlayan dolgu ve boşluklara yol açar:
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);
}
}
Başlık kapatma düğmesini stilize etme
Demoda Open Props düğmeleri kullanıldığından kapat düğmesi, aşağıdaki gibi yuvarlak bir simge düğme olarak özelleştirilmiştir:
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;
}
İletişim kutusunu stilize etme <article>
Article öğesinin bu iletişim kutusunda özel bir rolü vardır: Uzun bir iletişim kutusu olması durumunda kaydırılması amaçlanan bir alandır.
Bunu yapmak için üst form öğesi, kendisi için bazı maksimum değerler belirlemiştir. Bu değerler, çok uzun olması durumunda bu makale öğesinin ulaşması gereken kısıtlamaları sağlar. Kaydırma çubuklarının yalnızca gerektiğinde gösterilmesi için overflow-y: auto
'ı ayarlayın,
overscroll-behavior: contain
ile kaydırmayı kapsayıcı içinde tutun ve geri kalanını
özel sunum stilleri olarak ayarlayın:
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);
}
}
İletişim kutusunu stilize etme <footer>
Altbilginin rolü, işlem düğmelerinin menülerini içermektir. Flexbox, içeriği altbilgi satır içi ekseninin sonuna hizalamak ve düğmelere biraz alan vermek için aralık oluşturmak amacıyla kullanılır.
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);
}
}
İletişim kutusu altbilgi menüsünü stilize etme
menu
öğesi, iletişim kutusunun işlem düğmelerini içermek için kullanılır. Düğmeler arasında boşluk sağlamak için gap
ile sarmalama esnek kutu düzeni kullanır. Menü öğelerinde <ul>
gibi dolgu bulunur. İhtiyacım olmadığı için bu stili de kaldırıyorum.
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;
}
Animasyon
Pencereye girip çıktıkları için iletişim kutusu öğelerine genellikle animasyon eklenir. Bu giriş ve çıkış için iletişim kutularına destekleyici hareketler eklemek, kullanıcıların akışta yönlerini bulmalarına yardımcı olur.
Normalde iletişim kutusu öğesi yalnızca animasyonla gösterilebilir, animasyonla gizlenemez. Bunun nedeni, tarayıcının öğedeki display
özelliğini açıp kapatmasıdır. Daha önce kılavuz, ekranı ızgara olarak ayarlıyor ve hiçbir zaman "yok" olarak ayarlamıyordu. Bu sayede animasyonları
giriş ve çıkışta kullanabilirsiniz.
Open Props, kullanıma hazır birçok anahtar kare animasyonu içerir. Bu sayede düzenleme kolay ve anlaşılır hale gelir. Animasyon hedefleri ve katmanlı yaklaşımım:
- İndirgenmiş hareket, varsayılan geçiş olup basit bir opaklık solma efektidir.
- Hareket iyiyse kaydırma ve ölçeklendirme animasyonları eklenir.
- Büyük iletişim kutusunun duyarlı mobil düzeni, kaydırılarak açılacak şekilde ayarlanır.
Güvenli ve anlamlı bir varsayılan geçiş
Open Props, solma ve solma için anahtar karelerle birlikte gelir ancak anahtar kare animasyonlarını olası yükseltmeler olarak kullanarak bu katmanlı geçiş yaklaşımını varsayılan olarak tercih ederim. Daha önce, 1
veya 0
simgelerini [open]
özelliğine göre düzenleyerek iletişim kutusunun görünürlüğünü opaklık ile şekillendirmiştik. %0 ile %100 arasında geçiş yapmak için tarayıcıya ne kadar süre ve ne tür bir yumuşatma istediğinizi söyleyin:
dialog {
transition: opacity .5s var(--ease-3);
}
Geçişe hareket ekleme
Kullanıcı hareketli öğeleri kabul ediyorsa hem mega hem de mini iletişim kutuları giriş yaparken yukarı kaymalı, çıkış yaparken ise ölçeklendirilerek küçülmelidir. Bunu prefers-reduced-motion
medya sorgusu ve birkaç Open Props ile yapabilirsiniz:
@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;
}
}
Çıkış animasyonunu mobil cihazlara uyarlama
Stil bölümünün önceki kısımlarında, mega iletişim kutusu stili mobil cihazlara uyarlanarak işlem sayfasına benzetilmişti. Bu stilde, ekranın alt kısmından yukarı doğru kaydırılmış ve alt kısma bağlı kalmış küçük bir kağıt parçası görünümü oluşturulur. Küçülerek çıkış animasyonu bu yeni tasarıma pek uymuyor. Bu animasyonu birkaç medya sorgusu ve Open Props ile uyarlayabiliriz:
@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 ile eklenebilecek pek çok şey vardır:
// 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
}
Bu eklemeler, hafif kapatma (iletişim kutusu arka planını tıklama), animasyon ve form verilerini alma zamanlamasını iyileştirmek için bazı ek etkinlikler isteğinden kaynaklanmaktadır.
Işığa dokunarak kapatma ekleme
Bu görev basittir ve animasyon uygulanmayan bir iletişim kutusu öğesine harika bir katkı sağlar. Etkileşim, iletişim kutusu öğesindeki tıklamalar izlenerek ve tıklanan öğeyi değerlendirmek için olay kabarcıklanmasından yararlanılarak sağlanır. Yalnızca en üstteki öğe ise close()
olur:
export default async function (dialog) {
dialog.addEventListener('click', lightDismiss)
}
const lightDismiss = ({target:dialog}) => {
if (dialog.nodeName === 'DIALOG')
dialog.close('dismiss')
}
Bildirim dialog.close('dismiss')
. Etkinlik çağrılır ve bir dize sağlanır.
Bu dize, iletişim kutusunun nasıl kapatıldığı hakkında bilgi edinmek için diğer JavaScript'ler tarafından alınabilir. Ayrıca, kullanıcı etkileşimi hakkında uygulamama bağlam sağlamak için işlevi çeşitli düğmelerden her çağırdığımda yakın dizeler sağladığımı da göreceksiniz.
Kapanış ve kapalı etkinlikler ekleme
İletişim kutusu öğesi, kapatma etkinliğiyle birlikte gelir: close()
işlevi çağrıldığında hemen yayınlanır. Bu öğeyi animasyonla hareketlendirdiğimiz için, animasyondan önce ve sonraki etkinliklerin olması, verileri almak veya iletişim kutusu formunu sıfırlamak için iyi bir değişikliktir. Burada, kapalı iletişim kutusuna inert
özelliğinin eklenmesini yönetmek için kullanıyorum. Demoda ise kullanıcı yeni bir resim gönderdiyse avatar listesini değiştirmek için bunları kullanıyorum.
Bunu yapmak için closing
ve closed
adlı iki yeni etkinlik oluşturun. Ardından, iletişim kutusunda yerleşik kapatma etkinliğini dinleyin. Buradan iletişim kutusunu inert
olarak ayarlayın ve closing
etkinliğini gönderin. Bir sonraki görev, iletişim kutusunda animasyonların ve geçişlerin çalışmasının tamamlanmasını beklemek, ardından closed
etkinliğini göndermektir.
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))
Building a toast
component (Bildirim bileşeni oluşturma) bölümünde de kullanılan animationsComplete
işlevi, animasyon ve geçiş vaatlerinin tamamlanmasına dayalı bir söz döndürür. Bu nedenle dialogClose
bir async
function'dır. Ardından, döndürülen sözü await
ve kapalı etkinliğe güvenle devam edebilir.
Açılış ve açılış etkinlikleri ekleme
Yerleşik iletişim kutusu öğesi, kapatma işleminde olduğu gibi açık bir etkinlik sağlamadığından bu etkinliklerin eklenmesi kolay değildir. Değişen iletişim kutusu özellikleriyle ilgili analizler sağlamak için MutationObserver kullanıyorum. Bu gözlemcide, open özelliğindeki değişiklikleri izleyecek ve özel etkinlikleri buna göre yöneteceğim.
Kapanış ve kapanan etkinlikleri başlatma ve kapatma işlemine benzer şekilde, opening
ve opened
adlı iki yeni etkinlik oluşturun. Daha önce iletişim kutusunun kapatılması etkinliğini dinlerken bu kez iletişim kutusunun özelliklerini izlemek için oluşturulmuş bir MutationObserver kullanın.
…
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)
}
})
})
Değişiklik gözlemcisi geri çağırma işlevi, iletişim kutusu özellikleri değiştirildiğinde çağrılır ve değişikliklerin listesini dizi olarak sağlar. attributeName
öğesinin açık olup olmadığını kontrol ederek özellik değişikliklerini yineleyin. Ardından, öğenin özelliğe sahip olup olmadığını kontrol edin. Bu, iletişim kutusunun açılıp açılmadığını gösterir. Açılmışsa inert
özelliğini kaldırın, odağı autofocus
isteyen bir öğeye veya iletişim kutusunda bulunan ilk button
öğesine ayarlayın. Son olarak, kapatma ve kapatılmış olayına benzer şekilde, açma olayını hemen gönderin, animasyonların tamamlanmasını bekleyin ve ardından açılmış olayını gönderin.
Kaldırılan bir etkinliği ekleme
Tek sayfalık uygulamalarda iletişim kutuları genellikle rotalara veya diğer uygulama ihtiyaçlarına ve durumuna göre eklenir ve kaldırılır. Bir iletişim kutusu kaldırıldığında etkinlikleri veya verileri temizlemek yararlı olabilir.
Bunu başka bir Mutation Observer ile yapabilirsiniz. Bu kez, bir iletişim kutusu öğesindeki özellikleri gözlemlemek yerine gövde öğesinin alt öğelerini gözlemleyip iletişim kutusu öğelerinin kaldırılmasını izleyeceğiz.
…
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)
}
})
})
})
Belgenin gövdesine alt öğeler eklendiğinde veya kaldırıldığında mutation observer geri çağırma işlevi çağrılır. İzlenen mutasyonlar, nodeName
olan removedNodes
içindir. Bir iletişim kutusu kaldırılırsa tıklama ve kapatma etkinlikleri, belleği boşaltmak için kaldırılır ve özel kaldırılan etkinlik gönderilir.
Yükleme özelliğini kaldırma
İletişim kutusunun sayfaya eklendiğinde veya sayfa yüklendiğinde çıkış animasyonunu oynatmasını önlemek için iletişim kutusuna bir yükleme özelliği eklendi. Aşağıdaki komut dosyası, iletişim kutusu animasyonlarının çalışmasının tamamlanmasını bekler ve ardından özelliği kaldırır. Artık iletişim kutusuna animasyonla giriş ve çıkış yapılabilir. Böylece, aksi takdirde dikkat dağıtacak bir animasyonu etkili bir şekilde gizlemiş olduk.
export default async function (dialog) {
…
await animationsComplete(dialog)
dialog.removeAttribute('loading')
}
Sayfa yüklenirken anahtar kare animasyonlarının önlenmesi sorunu hakkında daha fazla bilgi edinin.
Hep birlikte
Her bölümü ayrı ayrı açıkladığımıza göre, dialog.js
'nın tamamını aşağıda görebilirsiniz:
// 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
modülünü kullanma
Modülden dışa aktarılan işlevin çağrılması ve şu yeni etkinliklerin ve işlevlerin eklenmesini isteyen bir iletişim kutusu öğesinin iletilmesi beklenir:
import GuiDialog from './dialog.js'
const MegaDialog = document.querySelector('#MegaDialog')
const MiniDialog = document.querySelector('#MiniDialog')
GuiDialog(MegaDialog)
GuiDialog(MiniDialog)
Böylece iki iletişim kutusu, kapatma animasyonu, animasyon yükleme düzeltmeleri ve üzerinde çalışılacak daha fazla etkinlik ile yükseltilir.
Yeni özel etkinlikleri dinleme
Yükseltilen her iletişim kutusu öğesi artık şu şekilde beş yeni etkinliği dinleyebilir:
MegaDialog.addEventListener('closing', dialogClosing)
MegaDialog.addEventListener('closed', dialogClosed)
MegaDialog.addEventListener('opening', dialogOpening)
MegaDialog.addEventListener('opened', dialogOpened)
MegaDialog.addEventListener('removed', dialogRemoved)
Bu etkinliklerin işlenmesiyle ilgili iki örneği aşağıda bulabilirsiniz:
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()
}
}
İletişim kutusu öğesiyle oluşturduğum demoda, listeye yeni bir avatar öğesi eklemek için bu kapalı etkinliği ve form verilerini kullanıyorum. İletişim kutusu çıkış animasyonunu tamamladıktan sonra bazı komut dosyaları yeni avatarı animasyonlu hale getirdiğinden zamanlama uygundur. Yeni etkinlikler sayesinde kullanıcı deneyimini düzenlemek daha kolay olabilir.
Uyarı dialog.returnValue
: Bu, iletişim kutusu close()
etkinliği çağrıldığında iletilen kapatma dizesini içerir. İletişim kutusunun kapatılıp kapatılmadığını, iptal edilip edilmediğini veya onaylanıp onaylanmadığını bilmek dialogClosed
etkinliğinde çok önemlidir. Onaylanırsa komut dosyası form değerlerini alır ve formu sıfırlar. Sıfırlama, iletişim kutusu tekrar gösterildiğinde boş olması ve yeni bir gönderime hazır olması için kullanışlıdır.
Sonuç
Bunu nasıl yaptığımı öğrendiğinize göre, siz nasıl yapardınız? 🙂
Yaklaşımlarımızı çeşitlendirelim ve web'de içerik oluşturmanın tüm yollarını öğrenelim.
Bir demo oluşturun, bağlantıları bana tweet atın. Ben de bu bağlantıları aşağıdaki topluluk remiksleri bölümüne ekleyeyim.
Topluluk remiksleri
- 3'ü 1 arada iletişim kutusu ile @GrimLink.
display
özelliğini değiştirmeyen güzel bir remiks ile @mikemai2awesome- @geoffrich_, Svelte ve güzel bir Svelte FLIP ile.
Kaynaklar
- Github'daki kaynak kodu
- Doodle Avatarları