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

Video kullanmayı tercih ederseniz bu gönderinin YouTube versiyonunu kullanabilirsiniz:

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ı sayfadaki bir işlemden yararlanabileceği durumları göz önünde bulundurun: Formun küçük olması veya kullanıcının yapması gereken tek işlemin onaylanması ya da iptal edilmesi olabilir.

<dialog> öğesi kısa süre önce tüm tarayıcılarda kararlı hale geldi:

Tarayıcı Desteği

  • 37
  • 79
  • 98
  • 15,4

Kaynak

Öğede birkaç şeyin eksik olduğunu fark ettim. Bu GUI Görevinde beklediğim geliştirici deneyimi öğelerini ekliyorum: ek etkinlikler, ışık kapatma, özel animasyonlar, mini ve mega tür.

Markup

Bir <dialog> öğesinin temel özellikleri mütevazıdır. Öğe otomatik olarak gizlenir ve içeriğinizle yer paylaşımlı olacak şekilde yerleşik stillere sahiptir.

<dialog>
  …
</dialog>

Bu temel değeri iyileştirebiliriz.

Geleneksel olarak, bir iletişim öğesi, bir kalıcı öğe ile birçok şeyi paylaşır ve genellikle adlar birbirinin yerine kullanılabilir. İletişim öğesini hem küçük iletişim kutusu pop-up'ları (mini) hem de tam sayfa iletişim kutuları (mega) için kullanma özgürlüğünü kazandım. İki diyalogu da mega ve mini olarak adlandırdım. Diyalogların her ikisi de farklı kullanım alanlarına göre uyarlandı. Türü belirtmenize olanak tanımak için bir 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 değil ancak genellikle bazı etkileşim bilgilerini toplamak için iletişim kutusu öğeleri kullanılır. İletişim kutusu öğelerinin içindeki formlar birlikte çalışacak şekilde yapılır. JavaScript'in kullanıcının girdiği verilere erişebilmesi için iletişim kutusu içeriğinizi sarmalayan bir form öğesinin olması iyi bir fikirdir. Dahası, method="dialog" kullanan bir formun içindeki 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 içinde üç öğe vardır: <header>, <article> ve <footer>. Bunlar, anlamsal kapsayıcıların yanı sıra iletişim kutusunun sunumu için stil hedefleri olarak da işlev görür. Başlık, kalıcı öğeyi adlandırır ve bir kapat düğmesi sunar. Bu makale, form girişleri ve bilgiler içindir. Altbilgide <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şleyici bulunur. İletişim kutusu açıldığında autofocus özelliğine odaklanılır. En iyi uygulama, bunu onayla düğmesine değil iptal düğmesine yerleştirmektir. Bu, onayın kasıtlı olmasını ve kazara olmadığını gösterir.

Mini iletişim kutusu

Mini iletişim kutusu, mega iletişim kutusuna çok benzer, yalnızca bir <header> öğesi eksiktir. 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 öğesinin yerleşik erişilebilirliği çok iyi. Bu özellikleri her zaman yaptığım gibi eklemek yerine, birçoğu zaten var.

Odak geri yükleniyor

Yan gezinme bileşeni oluşturma bölümünde elle yaptığımız gibi, bir öğeyi doğru şekilde açıp kapatırken ilgili açma ve kapatma düğmelerine odaklanmanız önemlidir. Yan gezinme paneli açıldığında odak, kapat düğmesine getirilir. Kapat düğmesine basıldığında odak, düğmeyi açan düğmeye geri döner.

İletişim kutusunda bu, yerleşik varsayılan davranıştır:

Ne yazık ki iletişim kutusunu içeri ve dışarı taşımak istiyorsanız bu işlev kaybolacaktır. JavaScript bölümünde bu işlevi geri yükleyeceğim.

Odak yakalama

İletişim öğesi, dokümanda inert öğesini sizin için yönetir. inert öncesinde JavaScript, odağın bir öğeden ayrılmasını izlemek için kullanılıyordu. Bu noktada devreye girerek öğeyi geri yerleştirir.

Tarayıcı Desteği

  • 102
  • 102
  • 112
  • 15,5

Kaynak

inert tarihinden sonra, dokümanın herhangi bir bölümü artık hedef olmaktan çıkacak veya fare ile etkileşimli olmayacak şekilde "dondurulabilir". Odağı yakalamaya çalışmak yerine, odak, dokümanın etkileşimli tek bölümüne yönlendirilir.

Bir öğeyi açma ve otomatik olarak odaklama

Varsayılan olarak, iletişim kutusu öğesi odağı, iletişim kutusu işaretlemesindeki ilk odaklanılabilir öğeye atar. Bu, kullanıcının varsayılan olarak ayarlayabileceği en iyi öğe değilse autofocus özelliğini kullanın. Daha önce de belirttiğim gibi bence en iyi uygulama bunu onayla düğmesine değil iptal düğmesine yerleştirmek. 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 kolayca kapatılması önemlidir. Neyse ki iletişim öğesi, escape tuşuna basarak sizi düzenleme yükünden kurtarır.

Stiller

İletişim öğesinin stilini ayarlamanın kolay bir yolu ve sabit bir yolu vardır. Bu kolay yol, iletişim kutusunun görüntüleme özelliğinin değiştirilmemesi ve sınırlamalarıyla çalışılmasıyla 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 utançsız bir şekilde ekledim. Ücretsiz sağlanan değişkenlere ek olarak, bir normalize dosyası ve bazı düğmeleri içe aktarıyorum. Her ikisi de Open Props tarafından isteğe bağlı içe aktarma işlevi sunar. 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> öğesinin stilini belirleme

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 değerinden none değerine geçirir. Ne yazık ki bu, animasyonun içeri ve dışarı çekilemeyeceği anlamına geliyor. Hem içeri hem de çıkış animasyonu uygulamak istiyorum. İlk adım, kendi display özelliğimi ayarlamak.

dialog {
  display: grid;
}

Yukarıdaki CSS snippet'inde gösterildiği gibi, görüntülü reklam mülkü değerinin değiştirilmesi ve sahip olmanız sonucunda, doğru kullanıcı deneyimini kolaylaştırmak için önemli miktarda stilin yönetilmesi gerekir. İlk olarak, bir 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 değilken etkileşimde bulunulamaz. Daha sonra, klavye ve ekran okuyucu kullanıcılarının da gizli iletişim kutusuna erişememelerini sağlamak için, iletişim kutusuna inert özelliğini yönetmek üzere biraz JavaScript ekleyeceğim.

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ızda açık ve koyu sistem tercihlerine göre tarayıcı tarafından sağlanan uyarlanabilir bir renk temasını etkinleştirir. Ancak iletişim öğesini bundan daha fazla özelleştirmek istedim. Open Props, color-scheme kullanımına benzer şekilde açık ve koyu sistem tercihlerine otomatik olarak uyum sağlayan birkaç yüzey rengi sağlar. Bunlar bir tasarımda katman oluşturmak için harika. Katman yüzeylerinin bu görünümünü görsel olarak desteklemeye yardımcı olması için renk kullanmayı seviyorum. Arka plan rengi var(--surface-1). Bu katmanın üstüne eklemek için var(--surface-2) rengini 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. Ben bunları bir diyalog öğesi olarak daha fazla görüyorum, ama ilgi çekici ve iyi tasarlanmış bir diyaloğun tasarımında da çok önemli.

Duyarlı iletişim kutusu boyutlandırma

İletişim kutusu varsayılan olarak boyutunu içeriklerine verir. Bu da genellikle çok iyidir. Buradaki amacım, max-inline-size öğesini okunabilir bir boyutla (--size-content-3 = 60ch) veya görüntü alanının% 90'ı ile sınırlandırmak. 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 aynı zamanda, uzun bir iletişim kutusu öğesi olması durumunda iletişim kutusunun kaydırılabilir alanının yerini belirtmemiz 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;
}

İki kez max-block-size aboneliğimin olduğunu fark ettiniz mi? İlki, fiziksel görüntü alanı birimi olan 80vh kullanır. Gerçekten istediğim, uluslararası kullanıcılar için iletişimi göreli bir akış içinde tutmak. Bu nedenle, daha kararlı hale geldiğinde ikinci bildirimde mantıksal, daha yeni ve yalnızca kısmen desteklenen dvb birimini kullanıyorum.

Mega iletişim kutusu konumlandırma

İletişim kutusu öğesinin konumlandırılmasına yardımcı olmak için bu öğenin iki parçasına bakalım: tam ekran arka planı ve iletişim kutusu kapsayıcısı. Arka plan her şeyi kapsamalı, bu iletişim kutusunun önde olduğunu ve arkasındaki içeriğe erişilemediğini desteklemeye yardımcı olacak bir 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 kadar 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üntü alanlarında, bu tam sayfa mega pencereyi biraz farklı şekilde biçimlendiriyorum. Alt kenar boşluğunu 0 olarak ayarladım. Böylece, iletişim kutusu içeriği görüntü alanının en altına taşınır. Birkaç stil ayarlaması yaparak iletişim kutusunu, kullanıcının baş parmaklarına daha yakın olacak şekilde bir işlem sayfasına dönüştürebiliyorum:

@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 yerleştirmesinin ekran görüntüsü.

Mini iletişim kutusu konumlandırma

Masaüstü bilgisayar gibi daha büyük bir görüntü alanı kullanırken, mini iletişim kutularını onları çağıran öğenin üzerine yerleştirmeyi seçtim. Bunun 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, iletişim kutusunu sayfanın çok üstünde yumuşak bir yüzey gibi görünecek şekilde biraz zenginleştirin. 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);
}

Sözde arka plan öğesini özelleştirme

Arka plan üzerinde çok hafif çalışmayı tercih ettim. Mega iletişim kutusuna backdrop-filter ile yalnızca bulanıklaştırma efekti ekledim:

Tarayıcı Desteği

  • 76
  • 79
  • 103
  • 9

Kaynak

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

Ayrıca, tarayıcıların gelecekte arka plan öğesinin geçişine izin vereceğini umarak backdrop-filter için bir geçiş yapmayı da tercih ettim:

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

Mega iletişim kutusunun, renkli avatarlardan oluşan bulanık bir arka planla yer paylaşımlı şekilde gösterildiği ekran görüntüsü.

Stil ekstraları

İletişim öğesi demomla, genel olarak iletişim öğesi demosundan daha çok alakalı olduğu için bu bölüme "ekstralar" adını veriyorum.

Kaydırma kapsamı

İletişim kutusu gösterildiğinde kullanıcı yine de arkasındaki sayfayı kaydırabiliyor. Bunu istemiyorum:

Normalde overscroll-behavior benim her zamanki çözümümdür ancak spesifikasyona göre, kaydırma bağlantı noktası olmadığı, yani kaydırma çubuğu olmadığı için iletişim kutusu üzerinde hiçbir etkisi yoktur. Dolayısıyla da önlenmesi gereken bir şey yoktur. Bu kılavuzdaki "closed" ve "open" gibi yeni etkinlikleri izlemek için JavaScript'i kullanabilir ve dokümanda overflow: hidden ayarını açabilir veya :has() öğesinin tüm tarayıcılarda kararlı olmasını bekleyebilirim:

Tarayıcı Desteği

  • 105
  • 105
  • 121
  • 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 olur.

<form> düzeni

Kullanıcıdan etkileşim bilgilerinin toplanması açısından çok önemli bir öğe olmasının yanı sıra burada üstbilgi, altbilgi ve makale öğelerini düzenlemek için de kullanıyorum. Bu düzende, makale alt öğesini kaydırılabilir bir alan olarak ifade etmeyi amaçlıyorum. Bunu grid-template-rows ile gerçekleştiriyorum. Makale öğesine 1fr verilir ve formun kendisi, iletişim öğesiyle aynı maksimum yüksekliğe sahiptir. Bu sabit yüksekliğin ve sabit satır boyutunun ayarlanması, makale öğesinin kısıtlanmasına ve taştığında kayması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 makalesi içeriğinin arkasında görünmesini sağlamak için bir yüzey rengi de verilir. Bu gereksinimler bir flexbox container'ını, kenarlarına yerleştirilmiş dikey olarak hizalı öğeleri ve başlık ile kapat 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ığında flexbox düzen bilgilerini yer paylaşımlı olarak kullandığı ekran görüntüsü.

Başlığı kapatma düğmesinin stilini belirleme

Demoda Open Props düğmelerini kullandığından kapatma düğmesi aşağıdaki gibi yuvarlak bir simge merkezli düğme şeklinde ö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 kapat düğmesi için boyut ve dolgu bilgilerini yer paylaşımlı olarak kullandığı ekran görüntüsü.

İletişim kutusunun stilini belirleme <article>

Makale öğesinin bu iletişim kutusunda özel bir rolü vardır: Uzun veya uzun bir iletişim kutusu olduğunda kaydırılmak üzere amaçlanan bir alandır.

Bunu başarmak için üst form öğesi, bu makale öğesinin çok uzun olması durumunda ulaşması için kısıtlamalar sağlayan bazı sınırlar belirlemiştir. Kaydırma çubuklarının yalnızca gerektiğinde gösterilmesi için overflow-y: auto özelliğini ayarlayın. Kaydırma çubuklarının içinde overscroll-behavior: contain ile kaydırmayı sürdürün. Geriye kalanlar özel sunu stilleri olur:

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 sarmalama flexbox düzeni kullanılır. Menü öğeleri <ul> gibi dolgu içerir. Ayrıca, ihtiyacım olmadığı için bu stili 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ü öğelerinde flexbox bilgilerini yer paylaşımlı olarak kullandığı ekran görüntüsü.

Animasyonlar

İletişim kutusu öğeleri, pencereye girip çıktıkları için genellikle canlandırmalı olur. Diyaloglarda bu giriş ve çıkış için destekleyici hareketler sunmak, kullanıcıların kendilerini akışa adapte etmesine yardımcı olur.

Normalde iletişim kutusu öğesi yalnızca animasyon içine eklenebilir, dışarı çıkarılamaz. Bunun nedeni, tarayıcının öğedeki display özelliğini açıp kapatmasıdır. Daha önce, kılavuz ekranı ızgaraya ayarlıyordu ve hiçbir zaman "hiçbiri" olarak ayarlamıyordu. Bu şekilde içeri ve dışarıda animasyon yapılabilir.

Open Props, düzenlemeyi kolay ve okunabilir hale getiren birçok animasyon animasyonu ile birlikte sunulur. Benim olduğum animasyon hedefleri ve katmanlı yaklaşım şöyledir:

  1. Azaltılmış hareket, varsayılan geçiştir. Basit bir opaklık giderek azalır.
  2. Hareket yeterliyse kaydırma ve ölçek animasyonları eklenir.
  3. Mega iletişim için duyarlı mobil düzen, dışarı kayacak şekilde ayarlandı.

Güvenli ve anlamlı bir varsayılan geçiş

Open Props ekran görüntüsü alma ve karartma için animasyon kareleriyle birlikte gelir. Bununla birlikte, varsayılan olarak animasyon karesi animasyonlarını içeren bu katmanlı geçiş yaklaşımını olası yükseltmeler olarak tercih ediyorum. Daha önce opaklık ile iletişim kutusunun görünürlüğünü [open] özelliğine bağlı olarak 1 veya 0 düzenleyerek 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ı harekete izin veriyorsa hem mega hem de mini iletişim kutuları giriş olarak yukarı doğru kaymalı ve çıkış olarak ölçeklendirilmelidir. 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 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. Genişletme animasyonu, bu yeni tasarıma pek uymuyor ve 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'e eklenecek 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, ışı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şık kapatma ekleniyor

Bu görev oldukça basittir ve animasyonlu olmayan iletişim kutusu öğesine mükemmel bir katkı sağlar. Etkileşim, iletişim kutusu öğesinin tıklanmasının izlenmesi ve tıklanan öğenin değerlendirilmesi için etkinlik köprüleme özelliğinden yararlanılarak gerçekleştirilir ve yalnızca en üstteki öğe olması durumunda close() gerçekleşir:

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

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

Uyarı dialog.close('dismiss'). Etkinlik çağrılır ve bir dize sağlanır. İletişim kutusunun nasıl kapatıldığı hakkında bilgi edinmek için bu dize diğer JavaScript tarafından alınabilir. Ayrıca uygulamama kullanıcı etkileşimi hakkında bağlam sağlamak için, işlevi çeşitli düğmelerden her çağırdığımda kapat dizeleri de verdiğimi göreceksiniz.

Kapanış ve kapanış etkinlikleri ekleme

İletişim öğesi bir kapatma etkinliğiyle birlikte gelir: İletişim kutusu close() işlevi çağrıldığında hemen yayınlanır. Bu öğeyi canlandırdığımız için animasyondan önce ve sonra etkinlikler olması iyidir. Böylece, verileri almak veya iletişim formunu sıfırlamak için bir değişiklik yapılabilir. Burada, kapalı iletişim kutusuna inert özelliğinin eklenmesini yönetmek için kullanıyorum. Demoda da kullanıcı yeni bir resim göndermişse avatar listesinde değişiklik yapmak için bu kodları kullanıyorum.

Bunu elde etmek için closing ve closed adında 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. Sıradaki görev, animasyonların ve geçişlerin iletişim kutusunda çalışmasının bitmesini beklemek ve ardından closed etkinliğini göndermek.

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

Durum bilgisi oluşturma bileşeni oluşturma bölümünde de kullanılan animationsComplete işlevi, animasyonun tamamlanmasına ve geçiş vaatlerine dayalı bir söz döndürür. Bu nedenle dialogClose, eş zamansız işlevdir; daha sonra döndürülen söz await ve kapalı etkinliğe güvenle devam edebilir.

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

Yerleşik iletişim öğesi, kapatmada olduğu gibi açık bir etkinlik sağlamadığından bu etkinlikleri eklemek kolay değildir. İletişim kutusunun özellikleriyle ilgili analizler sunmak için MutationObserver kullanıyorum. Bu gözlemcide, açık özellikteki değişiklikleri izleyeceğim ve özel etkinlikleri buna göre yöneteceğim.

Kapanış ve kapanış etkinliklerimize benzer şekilde, opening ve opened adında iki yeni etkinlik oluşturun. Daha önce diyalog kapatma etkinliğini dinlediğimiz yerde, bu kez iletişim kutusunun özelliklerini izlemek için oluşturulan bir mutasyon gözlemleyicisini 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)
    }
  })
})

İletişim kutusu özellikleri değiştirildiğinde mutasyon gözlemleyici geri çağırma işlevi çağrılır ve değişikliklerin listesi bir dizi olarak sağlanır. 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çılmışsa inert özelliğini kaldırın, odağı autofocus isteyen bir öğeye veya iletişim kutusundaki 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 bağlı olarak eklenip kaldırılır. Bir iletişim kutusu kaldırıldığında etkinlikleri veya verileri temizlemek faydalı olabilir.

Bunu başka bir mutasyon gözlemcisiyle başarabilirsiniz. Bu kez, bir iletişim öğesindeki özellikleri gözlemlemek yerine, body öğesinin alt öğelerini gözlemleyecek ve iletişim öğelerinin kaldırılmasına dikkat edeceğ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)
      }
    })
  })
})

Belge gövdesine alt öğe eklendiğinde veya belgeden alt öğe kaldırıldığında mutasyon gözlemleyici geri çağırması çağrılır. İzlenmekte olan belirli mutasyonlar, iletişim kutusunun nodeName öğesine sahip removedNodes içindir. İletişim kutusu kaldırıldıysa bellekte yer açmak için tıklama ve kapatma etkinlikleri kaldırılır ve kaldırılan özel etkinlik gönderilir.

Yükleme özelliği kaldırılıyor

İletişim kutusu animasyonunun sayfaya eklendiğinde veya sayfa yüklenirken çıkış animasyonunu oynatmasını önlemek için iletişim kutusuna yükleme özelliği eklendi. Aşağıdaki komut dosyası, iletişim kutusu animasyonlarının çalışmasının bitmesini bekler, ardından özelliği kaldırır. Artık iletişim kutusu canlandırma ve çığ gibi animasyonlarla serbest bırakılabiliyor. Ayrıca, dikkat dağıtan bir animasyonu etkili bir şekilde gizledik.

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

Sayfanın yüklenmesinde animasyon karesi animasyonlarının engellenmesiyle ilgili sorun hakkında daha fazla bilgiyi burada bulabilirsiniz.

Hepsi bir arada

Her bölümü ayrı ayrı açıkladığımıza göre dialog.js burada özetlenmektedir:

// 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şağıdaki yeni etkinliklerin ve işlevlerin eklenmesini isteyen bir iletişim öğesi öğesi çağrılmasını ve iletilmesini bekler:

import GuiDialog from './dialog.js'

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

GuiDialog(MegaDialog)
GuiDialog(MiniDialog)

Aynı şekilde iki iletişim kutusu da ışık kapatma, animasyon yükleme düzeltmeleri ve daha fazla etkinlikle yeni sürüme geçirildi.

Yeni özel etkinlikleri dinleme

Yükseltilen her iletişim öğ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. İletişim kutusunun çıkış animasyonunu tamamlaması ve ardından bazı komut dosyaları yeni avatarda etkinleşmesi açısından zamanlama iyidir. Yeni etkinlikler sayesinde, kullanıcı deneyimini düzenlemek daha sorunsuz olabiliyor.

dialog.returnValue dikkatine: Bu sütun, close() iletişim kutusu etkinliği çağrıldığında iletilen kapatma dizesini içerir. dialogClosed etkinliğinde iletişimin kapatılıp kapatılmadığını, iptal edilip edilmediğini veya onaylanıp onaylanmadığını bilmek çok önemlidir. Onaylanırsa komut dosyası, form değerlerini alır ve formu sıfırlar. İletişim kutusu tekrar gösterildiğinde boş ve yeni gönderim için hazır olması açısından sıfırlama işlemi yararlıdır.

Sonuç

Şimdi bunu nasıl yaptığımı öğrendiğinize göre siz nasıl ‽ 🙂

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