İletişim kutusu bileşeni oluşturma

<dialog> öğesi kullanılarak renk uyumlu, duyarlı ve erişilebilir mini ve mega modalların nasıl oluşturulacağına dair temel bir genel bakış.

Bu yayında, <dialog> öğesiyle renk uyumlu, duyarlı ve erişilebilir mini ve mega modallar oluşturma hakkındaki düşüncelerimi paylaşmak istiyorum. Demoyu deneyin ve kaynağı görüntüleyin.

Mega ve mini iletişim kutularının açık ve koyu temalardaki gösterimi.

Videoyu tercih ediyorsanız bu yayının YouTube sürümünü burada bulabilirsiniz:

Genel Bakış

<dialog> öğesi, sayfa içi bağlama dayalı bilgi veya işlemler için mükemmeldir. Kullanıcı deneyiminin, birden fazla sayfalı işlem yerine aynı sayfadaki işlemden ne zaman yararlanabileceğini düşünün. Örneğin, form küçükse veya kullanıcıdan tek gereken işlem onaylamak ya da iptal etmekse aynı sayfadaki işlemden yararlanılabilir.

<dialog> öğesi yakın zamanda tarayıcılarda kararlı hale geldi:

Tarayıcı desteği

  • Chrome: 37.
  • Edge: 79.
  • Firefox: 98.
  • Safari: 15.4.

Kaynak

Öğede birkaç şeyin eksik olduğunu fark ettim. Bu nedenle, bu GUI Challenge'da beklediğim geliştirici deneyimi öğelerini ekledim: ek etkinlikler, hafif kapatma, özel animasyonlar ve mini ve mega tür.

Brüt kar

<dialog> öğesinin temel özellikleri basittir. Öğe otomatik olarak gizlenir ve içeriğinizin üzerine yerleştirilmesi için yerleşik stilleri vardır.

<dialog>
  …
</dialog>

Bu referans değeri iyileştirilebilir.

Geleneksel olarak, iletişim kutusu öğeleri modal öğelerle ç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 kullanmayı tercih ettim. Bunları mega ve mini olarak adlandırdım. Her iki diyalog da farklı kullanım alanlarına göre biraz uyarlandı. Türü belirtmenize olanak tanımak için modal-mode özelliği ekledim:

<dialog id="MegaDialog" modal-mode="mega"></dialog>
<dialog id="MiniDialog" modal-mode="mini"></dialog>

Hem açık hem de koyu temalarda mini ve mega iletişim kutularının ekran görüntüsü.

Her zaman olmasa da genellikle bazı etkileşim bilgilerini toplamak için iletişim öğeleri kullanılır. İletişim öğelerindeki formlar birlikte kullanılmak üzere tasarlanmıştır. JavaScript'in kullanıcının girdiği verilere erişebilmesi için iletişim içeriğinizi bir form öğesiyle sarmalamak iyi bir fikirdir. Ayrıca, method="dialog" kullanan bir formdaki düğmeler, JavaScript olmadan bir iletişim kutusunu kapatabilir ve veri 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 kutusunun formunda üç öğe bulunur: <header>, <article> ve <footer>. Bunlar, anlamsal kapsayıcılar ve iletişim kutusunun sunumu için stil hedefleri olarak kullanılır. Başlık, modal pencereye başlık ekler ve bir kapat düğmesi sunar. Bu makale, form girişleri ve bilgileriyle ilgilidir. Altbilgi bölümünde bir dizi <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 vardır. İletişim kutusu açıldığında autofocus özelliğine odaklanılır. Bu özelliği onay düğmesine değil, iptal düğmesine yerleştirmenizi öneririz. Bu, onayın 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 satır içi bir resim elde edebilirsiniz.

<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 öğesi, veri ve kullanıcı etkileşimi toplayabilen tam bir görüntü alanı öğesi için güçlü bir temel sağlar. Bu temel bilgiler, sitenizde veya uygulamanızda çok ilgi çekici ve güçlü etkileşimler oluşturabilir.

Erişilebilirlik

İletişim öğesi, yerleşik olarak çok iyi erişilebilirliğe sahiptir. Genellikle yaptığım gibi bu özellikleri eklemek yerine, çoğu zaten mevcut.

Odağı geri yükleme

Yan gezinme menüsü bileşeni oluşturma bölümünde manuel olarak yaptığımız gibi, bir öğenin düzgün şekilde açılması ve kapatılması için ilgili açma ve kapatma düğmelerine odaklanılması önemlidir. Bu kenar çubuğu açıldığında odak kapat düğmesine yerleştirilir. Kapat düğmesine basıldığında odak, düğmeyi açan düğmeye geri yüklenir.

İletişim öğesinde bu, yerleşik varsayılan davranıştır:

Maalesef iletişim kutusunu açıp kapatmak için animasyon kullanmak istiyorsanız bu işlev kullanılamaz. JavaScript bölümünde bu işlevi geri yükleyeceğim.

Odağı sabitleme

İletişim öğesi, dokümanda inert değerini sizin için yönetir. inert'ten önce, odağın bir öğeden ayrılıp ayrılmadığını izlemek için JavaScript kullanılıyordu. Bu noktada JavaScript, odağın önüne geçerek onu geri koyuyordu.

Tarayıcı desteği

  • Chrome: 102.
  • Edge: 102.
  • Firefox: 112.
  • Safari: 15.5.

Kaynak

inert'ten sonra, belgenin herhangi bir bölümü artık odak hedefi olmayacak veya fareyle etkileşimli olmayacak şekilde "dondurulabilir". Odak, kilitlenmek yerine dokümanın tek etkileşimli bölümüne yönlendirilir.

Bir öğeyi açma ve otomatik odaklama

Varsayılan olarak iletişim öğesi, odağı iletişim işaretlemesindeki ilk odaklanılabilir öğeye atar. Kullanıcının varsayılan olarak kullanabileceği en iyi öğe bu değilse autofocus özelliğini kullanın. Daha önce de belirtildiği gibi, bu mesajı onay düğmesi yerine iptal düğmesine koymanızı öneririz. Bu sayede onay, yanlışlıkla değil kasıtlı olarak verilir.

Escape tuşuyla kapatma

Kullanıcının dikkatini dağıtabilecek bu öğenin kapatılmasını kolaylaştırmak önemlidir. Neyse ki iletişim kutusu öğesi, kaçış tuşunu sizin için yönetir ve sizi orkestrasyon yükünden kurtarır.

Stiller

İletişim kutusu öğesine stil uygulamanın kolay ve zor yolları vardır. Kolay yol, iletişim kutusunun görüntüleme özelliğini değiştirmeden ve sınırlamalarıyla çalışarak elde edilir. İletişim kutusunu açma ve kapatma, display mülkünü devralma ve daha fazlası için özel animasyonlar sağlamak üzere zor yolu tercih ediyorum.

Açık aksesuarlarla stil

Uyarlanabilir renkleri ve genel tasarım tutarlılığını hızlandırmak için CSS değişken kitaplığım Open Props'u ekledim. Ücretsiz olarak sağlanan değişkenlere ek olarak, Open Props'un isteğe bağlı içe aktarma olarak sunduğu bir normalleştirme dosyası ve bazı düğmeler de içe aktarıyorum. Bu içe aktarma işlemleri, iletişim kutusunu ve demoyu özelleştirmeye odaklanmamı sağlar. Bu sırada, iletişim kutusunu desteklemek ve iyi görünmesini sağlamak için çok fazla stil kullanmam gerekmez.

<dialog> öğesine stil uygulama

Görüntülü mülkün sahibi olmak

Bir iletişim öğesinin varsayılan gösterme ve gizleme davranışı, görüntüleme özelliğini block'ten none'e değiştirir. Bu nedenle, maalesef içeri ve dışarı doğru animasyonlu olarak gösterilemez, yalnızca içeri doğru gösterilebilir. Hem içeri hem de dışarı doğru animasyon yapmak istiyorum. İlk adım, kendi görüntüleme özelliğimi ayarlamaktır:

dialog {
  display: grid;
}

Yukarıdaki CSS snippet'inde gösterildiği gibi display mülk değerini değiştirerek ve dolayısıyla sahiplenerek, uygun kullanıcı deneyimini sağlamak 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 iletişim kutusunun aşağıdaki stillerle etkileşim almasını önleyebilirsiniz:

dialog:not([open]) {
  pointer-events: none;
  opacity: 0;
}

Artık iletişim kutusu görünmez ve açık olmadığında etkileşim kurulamaz. Daha sonra, iletişim kutusundaki inert özelliğini yönetmek için bazı JavaScript kodları ekleyerek klavye ve ekran okuyucu kullanıcılarının da gizli iletişim kutusuna ulaşamamasını sağlayacağım.

İletişim kutusuna uyarlanabilir renk teması verme

Açık ve koyu temayı gösteren, yüzey renklerini gösteren mega iletişim kutusu.

color-scheme, dokümanınızı açık ve koyu sistem tercihlerine göre tarayıcı tarafından sağlanan uyarlanabilir renk temasına ayarlar. Ben ise iletişim kutusunu bundan daha fazla özelleştirmek istedim. Open Props, color-scheme'yi kullanmaya benzer şekilde açık ve koyu sistem tercihlerine otomatik olarak uyum sağlayan birkaç yüzey rengi sağlar. Bu araçlar, tasarımda katman oluşturmak için mükemmeldir. Katman yüzeylerinin bu görünümünü görsel olarak desteklemek için renkleri kullanmayı seviyorum. Arka plan rengi var(--surface-1)'tir. Bu katmanın üzerine oturmak için var(--surface-2)'u 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 fazla uyarlanabilir renk daha sonra eklenecektir. Bunları bir 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 olduklarını düşünüyorum.

Duyarlı iletişim kutusu boyutlandırması

İletişim kutusu, varsayılan olarak boyutunu içeriğine delege eder. Bu genellikle iyi bir seçenektir. Buradaki amacım, max-inline-size değerini okunabilir bir boyuta (--size-content-3 = 60ch) veya görüntü alanı genişliğinin% 90'ına sınırlamaktır. Bu sayede iletişim kutusu, mobil cihazlarda ekranı kaplamaz ve masaüstü ekranında okunamayacak kadar geniş olmaz. Ardından, iletişim kutusunun sayfanın boyunu aşmaması için bir max-block-size ekliyoruz. Bu, iletişim kutusunun yüksek bir iletişim öğesi olması durumunda, iletişim kutusunun kaydırılabilir alanının nerede olduğunu da belirtmemiz gerektiği anlamına 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 değerinin iki kez kullanıldığını fark ettiniz mi? İlkinde fiziksel bir görüntü alanı birimi olan 80vh kullanılır. Aslında, uluslararası kullanıcılar için iletişim kutusunu göreceli akış içinde tutmak istiyorum. Bu nedenle, daha kararlı hale geldiğinde ikinci beyanda mantıklı, daha yeni ve yalnızca kısmen desteklenen dvb birimini kullanıyorum.

Mega iletişim kutusu konumlandırması

İletişim öğesinin konumlandırılmasına yardımcı olmak için öğeyi iki bölüme ayırmak gerekir: tam ekran arka plan ve iletişim kapsayıcısı. Arka plan her şeyi kaplamalı ve bu iletişim kutusunun ön planda olduğunu, arkadaki içeriğe erişilemediğini desteklemek için gölge efekti sağlamalıdır. İletişim kutusu, kendisini bu arka planın üzerine yerleştirebilir ve içeriğinin gerektirdiği şekli alabilir.

Aşağıdaki stiller, iletişim öğesini pencereye sabitleyerek her köşeye doğru uzatır ve içeriği margin: auto kullanarak ortalar:

dialog {
  
  margin: auto;
  padding: 0;
  position: fixed;
  inset: 0;
  z-index: var(--layer-important);
}
Mobil mega iletişim kutusu stilleri

Küçük ekranlarda bu tam sayfa mega modala biraz farklı bir stil uyguladım. Alt kenar boşluğunu 0 olarak ayarladım. Bu, iletişim kutusu içeriğini görüntü alanının alt kısmına getirir. Birkaç stil ayarıyla 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;
  }
}

Hem masaüstü hem de mobil mega iletişim kutusu açıkken kenar boşluğu aralığını kaplayan devtools ekran görüntüsü.

Mini iletişim kutusu yerleşimi

Masaüstü bilgisayar gibi daha büyük bir ekran görüntüsü kullanırken mini iletişim kutularını, onları çağıran öğenin üzerine yerleştirmeyi tercih ettim. Bunu yapabilmem 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ı görüntüler oluşturun

Son olarak, sayfanın çok üzerinde duran yumuşak bir yüzey gibi görünmesi için iletişim kutusuna biraz zerafet katın. Yumuşak görünüm, iletişim kutusunun köşelerinin yuvarlanması ile elde edilir. Derinlik, Open Props'un özenle hazırlanmış gölge öğelerinden biriyle sağlanır:

dialog {
  
  border-radius: var(--radius-3);
  box-shadow: var(--shadow-6);
}

Arka plan sözde öğesini özelleştirme

Arka planda çok az işlem yapmayı tercih ettim. Yalnızca mega iletişim kutusuna backdrop-filter ile bulanıklık efekti ekledim:

Tarayıcı desteği

  • Chrome: 76.
  • Edge: 79.
  • Firefox: 103.
  • Safari: 18.

Kaynak

dialog[modal-mode="mega"]::backdrop {
  backdrop-filter: blur(25px);
}

Ayrıca, backdrop-filter öğesine geçiş eklemeyi de tercih ettim. Böylece, tarayıcıların gelecekte arka plan öğesinin geçişine izin vermesini umuyorum:

dialog::backdrop {
  transition: backdrop-filter .5s ease;
}

Renkli avatarların bulanık arka planının üzerine yerleştirilmiş mega iletişim kutusunun ekran görüntüsü.

Stil ekstraları

Bu bölümü "ekstralar" olarak adlandırıyorum çünkü genel olarak iletişim kutusu öğesiyle ilgili olmaktan ziyade iletişim kutusu öğesi demomla daha alakalı.

Kaydırma kapsamı

İletişim kutusu gösterildiğinde kullanıcı, sayfayı arkasından kaydırmaya devam edebilir. Bunu istemiyorum:

Normalde, overscroll-behavior benim genel çözümüm olurdu ancak özelliğe göre, kaydırma bağlantı noktası olmadığı için iletişim kutusunu etkilemez. Yani kaydırma çubuğu olmadığından engellenecek bir şey yoktur. Bu kılavuzda yer alan "kapalı" ve "açık" gibi yeni etkinlikleri izlemek için JavaScript'i kullanabilir ve belgede overflow: hidden'ü etkinleştirebilir ya da :has()'ün tüm tarayıcılarda kararlı hale gelmesini bekleyebilirim:

Tarayıcı desteği

  • Chrome: 105.
  • Edge: 105.
  • Firefox: 121.
  • Safari: 15.4.

Kaynak

html:has(dialog[open][modal-mode="mega"]) {
  overflow: hidden;
}

Artık mega iletişim kutusu açıkken html dokümanında overflow: hidden var.

<form> düzeni

Kullanıcıdan etkileşim bilgilerini toplamak için çok önemli bir öğe olmasının yanı sıra bu öğeyi başlık, altbilgi ve makale öğelerini düzenlemek için de burada kullanıyorum. Bu düzende, makale alt öğesini kaydırılabilir bir alan olarak belirtmek istiyorum. Bunu 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ükseklik ve sabit satır boyutunu ayarlamak, makale öğesinin sınırlandırılmasına ve taştığında kaydırılmasına olanak tanır:

dialog > form {
  display: grid;
  grid-template-rows: auto 1fr auto;
  align-items: start;
  max-block-size: 80vh;
  max-block-size: 80dvb;
}

Izgara düzeni bilgilerini satırların üzerine yerleştiren devtools&#39;in ekran görüntüsü.

<header> iletişim kutusuna stil uygulama

Bu öğenin rolü, iletişim kutusu içeriğine 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ındaymış gibi görünmesi için bir yüzey rengi de verilir. Bu koşullar, bir flexbox kapsayıcısı, kenarlarına aralıklı olarak yerleştirilmiş dikey olarak hizalanmış öğeler ve başlığa ve kapat düğmelerine yer açmak için bazı dolgu ve boşluklar elde etmenizi sağlar:

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);
  }
}

Chrome Geliştirici Araçları&#39;nın, iletişim kutusu başlığına flexbox düzen bilgilerini yerleştirdiği ekran görüntüsü.

Başlık kapatma düğmesine stil uygulama

Demoda açık öğe düğmeleri kullanıldığı için kapat düğmesi, aşağıdaki gibi yuvarlak simge merkezli bir 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;
}

Başlık kapat düğmesinin boyutlandırma ve dolgu bilgilerini gösteren Chrome Geliştirici Araçları ekran görüntüsü.

<article> iletişim kutusuna stil uygulama

article öğesi bu iletişim kutusunda özel bir role sahiptir: Yüksek veya uzun bir iletişim kutusu olması durumunda kaydırılmak üzere tasarlanmış bir alandır.

Bunu yapmak için üst form öğesi, kendisi için bazı maksimum değerler belirlemiştir. Bu değerler, çok uzun olursa bu makale öğesinin ulaşacağı kısıtlamaları sağlar. overflow-y: auto'ü, kaydırma çubuklarının yalnızca gerektiğinde gösterileceği şekilde ayarlayın, overscroll-behavior: contain ile kaydırma içeriği ekleyin ve geri kalanı özel sunum stilleri olacak şekilde 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);
  }
}

Altbilginin işlevi, işlem düğmelerinin bulunduğu menüleri içermektir. İçeriği altbilginin satır içi ekseninin sonuna hizalamak için Flexbox kullanılır. Ardından, düğmelere yer açmak için biraz boşluk bırakı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);
  }
}

Chrome Geliştirici Araçları&#39;nın, altbilgi öğesine flexbox düzen bilgilerini yerleştirdiği ekran görüntüsü.

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 sarmalayıcı bir flexbox düzeni kullanır. Menü öğelerinde <ul> gibi dolgu bulunur. Ayrıca, ihtiyacı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;
}

Chrome Geliştirici Araçları&#39;nın, altbilgi menüsü öğelerine flexbox bilgilerini yerleştirdiği ekran görüntüsü.

Animasyon

İletişim kutusu öğeleri genellikle pencereye girip çıktıkları için animasyonlu olur. Bu giriş ve çıkış için iletişim kutularına destekleyici hareketler eklemek, kullanıcıların akışta kendilerini yönlendirmelerine yardımcı olur.

Normalde iletişim kutusu öğesi yalnızca içeriye doğru animasyonlu olabilir, dışarıya doğru animasyonlu olamaz. Bunun nedeni, tarayıcının öğedeki display özelliğini değiştirmiş olmasıdır. Daha önce kılavuz, ekranı ızgara olarak ayarlıyordu ve hiçbir zaman hiçbir olarak ayarlamıyordu. Bu sayede, öğeleri yakınlaştırıp uzaklaştırabilirsiniz.

Open Props, kullanımınıza sunulan birçok keyframe animasyonu içerir. Bu animasyonlar, orkestrasyonu kolay ve okunaklı hale getirir. Aşağıda, animasyon hedefleri ve benim uyguladığım katmanlı yaklaşım verilmiştir:

  1. İndirgenmiş hareket, varsayılan geçiştir. Basit bir opaklıkta kaydırmayla içeri ve dışarı geçiş yapar.
  2. Hareket uygunsa kaydırma ve ölçeklendirme animasyonları eklenir.
  3. Mega 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, anahtar kare animasyonlarıyla birlikte gelmekle birlikte, varsayılan olarak bu katmanlı geçiş yaklaşımını tercih ediyorum. Daha önce, [open] özelliğine bağlı olarak 1 veya 0'ü düzenleyip iletişim kutusunun görünürlüğünü opaklıkla biçimlendirmiştik. %0 ile %100 arasında geçiş yapmak için tarayıcıya ne kadar süre ve ne tür bir geçiş istediğinizi bildirin:

dialog {
  transition: opacity .5s var(--ease-3);
}

Geçişe hareket ekleme

Kullanıcı hareketi kabul ediyorsa hem mega hem de mini iletişim kutuları, girişte yukarı doğru kaymalı ve çıkışta küçülmelidir. Bunu prefers-reduced-motion medya sorgusu ve birkaç açık öğeyle 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 cihazlar için uyarlama

Stil bölümünde daha önce de belirtildiği gibi, mega iletişim kutusu stili mobil cihazlar için bir işlem sayfasına benzer şekilde uyarlanmıştır. Bu stil, ekranın alt kısmından yukarı doğru kayan ve alt kısma bağlı kalan küçük bir kağıt parçası gibi görünür. Çıkış animasyonunun ölçeklendirilmesi bu yeni tasarıma pek uymuyor. Bunu birkaç medya sorgusu ve bazı 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 ekleyeceğiniz birç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 arzusundan kaynaklanmaktadır.

Işığı kapatma özelliğini ekleme

Bu görev basittir ve animasyonlu olmayan bir iletişim kutusu öğesine mükemmel bir katkı sağlar. Etkileşim, iletişim öğesindeki tıklamaları izleyerek ve neyin tıklandığını değerlendirmek için etkinlik kabarcıklarından yararlanarak elde edilir. Bu işlem yalnızca en üstteki öğeyse close() gerçekleşir:

export default async function (dialog) {
  dialog.addEventListener('click', lightDismiss)
}

const lightDismiss = ({target:dialog}) => {
  if (dialog.nodeName === 'DIALOG')
    dialog.close('dismiss')
}

dialog.close('dismiss') bildirimi. Etkinlik çağrılır ve bir dize sağlanır. Bu dize, iletişim kutusunun nasıl kapatıldığıyla ilgili analizler elde etmek için diğer JavaScript'ler tarafından alınabilir. Ayrıca, işlevi çeşitli düğmelerden her çağırdığımda, uygulamama kullanıcı etkileşimi hakkında bağlam sağlamak için yakın dize de sağladığımı göreceksiniz.

Kapanış ve kapalı etkinlikler ekleme

İletişim kutusu öğesi, kapatma etkinliğiyle birlikte gelir: İletişim kutusu close() işlevi çağrıldığında hemen yayınlanır. Bu öğeyi animasyonlu hale getirdiğimiz için, verileri almak veya iletişim kutusunu sıfırlamak için animasyondan önce ve sonra etkinliklere sahip olmak iyi bir fikirdir. Burada, kapalı iletişim kutusunda inert özelliğinin eklenmesini yönetmek için kullanıyorum. Demoda ise kullanıcı yeni bir resim gönderirse avatar listesini değiştirmek için bu özellikleri 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. Sonraki görev, animasyon ve geçişlerin iletişim kutusunda çalışmasını beklemek ve 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))

Bir pop-up bileşeni oluşturma bölümünde de kullanılan animationsComplete işlevi, animasyon ve geçiş taahhütlerinin tamamlanmasına bağlı bir promise döndürür. Bu nedenle dialogClose bir asynchronize işlev olduğundan, döndürülen await vaadini alıp kapalı etkinliğe güvenle devam edebilir.

Açılış ve açılmış etkinlikler ekleme

Yerleşik iletişim öğesi, kapatma durumunda olduğu gibi bir açma etkinliği sağlamadığından bu etkinliklerin eklenmesi o kadar kolay değildir. İletişim kutusunun değişen özellikleriyle ilgili analizler sağlamak için MutationObserver kullanıyorum. Bu gözlemcide, açık özelliğindeki değişiklikleri izler ve özel etkinlikleri buna göre yönetirim.

Kapanış ve kapalı etkinlikleri başlattığımıza benzer şekilde opening ve opened adlı iki yeni etkinlik oluşturun. Daha önce iletişim kutusunun kapatılma etkinliğini dinlediğimiz yerde bu kez, iletişim kutusunun özelliklerini izlemek için oluşturulmuş bir mutasyon gözlemcisi 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)
    }
  })
})

Mutasyon gözlemci geri çağırma işlevi, iletişim kutusu özellikleri değiştirildiğinde çağrılır ve değişikliklerin listesini bir dizi olarak sağlar. attributeName'ün açık olup olmadığını kontrol ederek özellik değişikliklerini iteratif olarak gözden geçirin. Ardından, öğenin bu özelliğe sahip olup olmadığını kontrol edin: Bu, iletişim kutusunun açılıp açılmadığını bildirir. Açıldıysa inert özelliğini kaldırın, odağı autofocus isteyen bir öğeye veya iletişim kutusunda bulunan ilk button öğesine ayarlayın. Son olarak, kapanış ve kapalı etkinliğine benzer şekilde, açılış etkinliğini hemen gönderin, animasyonların tamamlanmasını bekleyin ve ardından açılan etkinliği 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 eklenip kaldırılır. Bir iletişim kutusu kaldırıldığında etkinlikleri veya verileri temizlemek yararlı olabilir.

Bunu başka bir mutasyon gözlemciyle yapabilirsiniz. Bu kez, iletişim kutusu öğesindeki özellikleri gözlemlemek yerine body öğesinin alt öğelerini gözlemleyeceğiz ve iletişim kutusu öğelerinin kaldırılıp kaldırılmadığı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)
      }
    })
  })
})

Mutasyon gözlemci geri çağırma işlevi, belgenin gövdesine alt öğe eklendiğinde veya gövdeden alt öğe kaldırıldığında çağrılır. İzlenen belirli mutasyonlar, bir iletişim kutusunun nodeName özelliğine sahip removedNodes içindir. Bir iletişim kutusu kaldırıldıysa bellek alanı açmak için tıklama ve kapatma etkinlikleri kaldırılır ve özel kaldırılan etkinlik gönderilir.

Yükleme özelliğini kaldırma

İletişim kutusu animasyonunun 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ı bekledikten sonra özelliği kaldırır. Artık iletişim kutusunun giriş ve çıkış animasyonu serbesttir ve dikkat dağıtıcı olabilecek bir animasyonu etkili bir şekilde gizledik.

export default async function (dialog) {
  
  await animationsComplete(dialog)
  dialog.removeAttribute('loading')
}

Sayfa yüklenirken keyframe animasyonların engellenmesi sorunu hakkında daha fazla bilgi edinin.

Tümünü bir araya getirme

Her bölümü ayrı ayrı açıkladığımıza göre dialog.js'ün tamamını aşağıda bulabilirsiniz:

// 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şlev, çağrılmayı ve aşağıdaki yeni etkinliklerin ve işlevlerin eklenmesini isteyen bir iletişim öğesi ile iletilmeyi bekler:

import GuiDialog from './dialog.js'

const MegaDialog = document.querySelector('#MegaDialog')
const MiniDialog = document.querySelector('#MiniDialog')

GuiDialog(MegaDialog)
GuiDialog(MiniDialog)

Bu şekilde, iki iletişim kutusu da hafif kapatma, animasyon yükleme düzeltmeleri ve daha fazla çalışılacak etkinlikle yükseltildi.

Yeni özel etkinlikleri dinleme

Yükseltilen her iletişim kutusu öğesi artık aşağıdaki gibi 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 etkinlikleri işlemeyle ilgili iki örnek aşağıda verilmiştir:

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 öğesiyle oluşturduğum demoda, listeye yeni bir avatar öğesi eklemek için bu kapalı etkinliği ve form verilerini kullanıyorum. Zamanlama iyi. İletişim kutusu çıkış animasyonunu tamamladıktan sonra bazı komut dosyaları yeni avatarda animasyonlu olarak gösteriliyor. Yeni etkinlikler sayesinde kullanıcı deneyimini koordine etmek daha kolay olabilir.

dialog.returnValue değerine dikkat edin: Bu değer, dialog close() etkinliği çağrıldığında iletilen kapatma dizesini içerir. dialogClosed etkinliğinde, iletişim kutusunun kapatılıp kapatılmadığını, iptal edilip edilmediğini veya onaylanıp onaylanmadığını bilmek önemlidir. Onaylanırsa komut dosyası, form değerlerini alır ve formu sıfırlar. Sıfırlama işlemi, iletişim kutusu tekrar gösterildiğinde boş 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 ne yapardınız? 🙂

Yaklaşımlarımızı çeşitlendirelim ve web'de uygulama geliştirmenin tüm yollarını öğrenelim.

Bir demo oluşturun, bağlantılarını bana tweetleyin. Ardından, aşağıdaki topluluk remiksleri bölümüne ekleyeceğim.

Topluluk remiksleri

Kaynaklar