İletişim bileşeni oluşturma

<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.

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

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:

Browser Support

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

Source

Öğ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>

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

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.

Browser Support

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

Source

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

Yüzey renklerini gösteren açık ve koyu temayı içeren mega iletişim kutusu.

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

Açıkken hem masaüstü hem de mobil mega iletişim kutusunda kenar boşluğu aralığını kaplayan geliştirici araçları yer paylaşımının ekran görüntüsü.

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:

Browser Support

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

Source

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

Renkli avatarlardan oluşan bulanık bir arka planın üzerinde yer alan mega iletişim kutusunun ekran görüntüsü.

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:

Browser Support

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

Source

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

Geliştirici araçlarının, satırların üzerine ızgara düzeni bilgilerini yerleştirdiği ekran görüntüsü.

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

Chrome Geliştirici Araçları&#39;nın, iletişim kutusu başlığında flexbox düzeni bilgilerini gösteren ekran görüntüsü.

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

Chrome Geliştirici Araçları&#39;nın, başlık kapatma düğmesi için boyutlandırma ve dolgu bilgilerini gösteren ekran görüntüsü.

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

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

Chrome Geliştirici Araçları&#39;nın, altbilgi öğesinde flexbox düzeni 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 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;
}

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

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:

  1. İndirgenmiş hareket, varsayılan geçiş olup basit bir opaklık solma efektidir.
  2. Hareket iyiyse kaydırma ve ölçeklendirme animasyonları eklenir.
  3. 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

Kaynaklar