İletişim kutusu bileşeni oluşturma

<dialog> öğesiyle renge uyarlanan, duyarlı ve erişilebilir mini ve mega kalıcı öğelerin nasıl oluşturulacağına dair temel bir genel bakış.

Bu gönderide, <dialog> öğesini kullanarak renge uyum sağlayan, duyarlı ve erişilebilir mini ve mega kalıcı öğelerin nasıl oluşturulacağıyla ilgili düşüncelerimi paylaşmak istiyorum. Demoyu deneyin ve kaynağı görüntüleyin.

Mega ve mini diyalogların açık ve koyu temalarında gösterilmesi.

Videoyu tercih ediyorsanız bu yayının YouTube sürümünü burada 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 ne zaman yararlanabileceğini düşünün. Örneğin, form küçükse veya kullanıcıdan istenen tek işlem onaylamak ya da iptal etmekse aynı sayfa işlemini kullanabilirsiniz.

<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ç 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ğinizle yer paylaşımı için yerleşik stillere sahiptir.

<dialog>
  …
</dialog>

Bu referans değeri iyileştirebiliriz.

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>

Açık ve koyu temalardaki 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 kutusu 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 bilgiler içindir. 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 koymak en iyi uygulamadır. 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, daha küçük ve daha satır içi olmasına olanak tanır.

<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şimini toplayabilecek tam görüntü alanı öğesi için güçlü bir temel sağlar. Bu temel bilgiler, sitenizde veya uygulamanızda son derece 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.

Odak geri yükleniyor

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 döner.

İ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.
  • Kenar: 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 kutusu öğesi, odağı iletişim kutusu işaretlemesinin ilk odaklanılabilir öğesine atar. Bu, kullanıcının varsayılan olarak ayarlayabileceği en iyi öğe 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, onayın kasıtlı olarak yapılmasını ve yanlışlıkla yapılmamasını sağlar.

Escape tuşuyla kapatma

Rahatsız edici olabilecek 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çıp kapatmak, display özelliğini ve daha fazlasını devralarak özel animasyonlar sağlamak için zor bir yolda ilerliyorum.

Açık Sahnelerle 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 odaklanmama yardımcı olurken iletişim kutusunu desteklemek ve iyi görünmesini sağlamak için çok fazla stile gerek duymuyor.

<dialog> öğesine stil uygulama

Görüntülü reklam mülküne sahip olma

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. Bunun için ilk 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 gösterebilir ve iletişim kutusunun şu stillerle etkileşim almasını engelleyebilirsiniz:

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.

Diyaloğa uyarlanabilir bir renk teması ekleme

Yüzey renklerini gösteren, açık ve koyu temayı 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 kutusu öğesini 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); bu katmanın üstüne eklemek için var(--surface-2) kodunu 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);
  }
}

Üstbilgi ve altbilgi gibi alt öğeler için daha sonra daha uyarlanabilir renkler 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ırma

İ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 uçtan uca ve masaüstü ekranlarında okunması zor olacak kadar geniş kapsamlı olmaz. Ardından, iletişim kutusunun sayfanın yüksekliğini aşmaması için bir max-block-size ekliyorum. 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şimi 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 yerleştirilmesine yardımcı olmak için öğeyi iki bölüme ayırmak faydalı olacaktır: tam ekran arka plan ve iletişim kapsayıcısı. Arka plan her şeyi kaplamalı ve bu iletişim kutusunun ön planda olduğunu, arkasındaki içeriğe erişilemediğini desteklemek için gölge efekti sağlamalıdır. İletişim kapsayıcısı, kendisini bu arka plan üzerinde ortalayabilir 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;
  }
}

Geliştirici Araçları&#39;nın açık durumdayken hem masaüstü hem de mobil mega iletişim kutusunda kenar boşluğu boşluğunu yer paylaşımlı olarak kullandığı ekran görüntüsü.

Mini iletişim kutusu konumlandırma

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 bunun bu makalenin kapsamı dışında olduğunu düşünüyorum. JavaScript olmadan, mini iletişim kutusu tıpkı mega iletişim kutusu gibi, ekranın ortasında görünür.

Çarpıcı hale getirin

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şaklık, iletişim kutusunun köşeleri yuvarlanarak elde edilir. Derinlik, Open Props'un özenle hazırlanmış gölge öğelerinden biriyle elde edilir:

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.
  • Kenar: 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 öğesiyle ilgili olmaktan ziyade iletişim öğesi demomla ilgili.

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

Geliştirici Araçları&#39;nın satırların üzerinde ızgara düzeni bilgileriyle yer paylaşımlı olarak gösterildiği ekran görüntüsü.

İletişim kutusunun stilini belirleme <header>

Bu öğenin rolü, iletişim kutusu içeriği için bir başlık sağlamak ve bulunması kolay bir kapat 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 gereksinimler bir flexbox container'ı, kenarlarına yerleştirilmiş dikey olarak hizalanan öğeler ve başlık ile kapatma düğmelerine biraz alan bırakmak için bazı dolgu ve boşluklar 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 Open Props düğmeleri kullanıldığı için kapat düğmesi, aşağıdaki gibi yuvarlak simge odaklı 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ü.

İletişim kutusunun stilini belirleme <article>

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 rolü, işlem düğmelerinin menüleri içermesidir. Flexbox, içeriği altbilgi satır içi ekseninin sonuna hizalamak ve ardından düğmelere alan açmak için biraz boşluk bırakmak için 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);
  }
}

Chrome Geliştirici Araçları&#39;nın altbilgi öğesinde flexbox düzen bilgilerini yer paylaşımlı olarak kullandığı 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ırım.

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 öğeleri genellikle pencereye girip çıktıkları için animasyonlu olur. 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 olarak gösterilebilir, dışarıya doğru animasyonlu olarak gösterilemez. 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, öğelerin yakınlaştırma ve uzaklaştırma animasyonlarını kullanabilirsiniz.

Open Props, kullanımınıza sunulan birçok keyframe animasyonuyla 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 yeterliyse kaydırma ve ölçek 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 geçişler için bu katmanlı yaklaşımı 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 yumuşatma 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 özelliklerinin başlarında mega iletişim stili, mobil cihazlar için ekranın altından kayan ve alta sabitlenmiş küçük bir kağıt parçası gibi daha çok bir işlem sayfası gibi uyarlanmıştır. Ölçeklendirilmiş çıkış animasyonu 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, ışık kapatma (iletişim kutusu arka planını tıklama), animasyon ve form verilerini almada daha iyi zamanlama için bazı ek etkinlikler istemeden 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 kapanış etkinlikleri 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çık 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 özellikleriyle ilgili analizler sunmak 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ız gibi 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 öğesinin açık olup olmadığını kontrol ederek özellik değişikliklerini tekrarlayın. Daha sonra, öğenin özelliğe sahip olup olmadığını kontrol edin. Bu özellik, iletişim kutusunun açık olup olmadığı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 kapanış etkinliğine benzer şekilde, açılış etkinliğini hemen gönderin, animasyonların bitmesini 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 içeri ve dışarı doğru 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 anahtar kare animasyonlarını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)

Aşağıda, bu etkinliklerin işlenmesiyle ilgili iki örnek 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 kapalı etkinliği ve form verilerini kullanarak listeye yeni bir avatar öğesi ekliyorum. 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? 🙂

Gelin, yaklaşımlarımızı çeşitlendirelim ve web'de içerik geliştirmenin tüm yollarını öğrenelim.

Demo oluşturup beni tweet'le bağlantıları oluşturduğumda bunu aşağıdaki topluluk remiksleri bölümüne ekleyeceğim.

Topluluk remiksleri

Kaynaklar