Pop-up: Mereka muncul kembali!

Tujuan inisiatif UI Terbuka adalah untuk memudahkan developer dalam menciptakan pengalaman pengguna yang luar biasa. Untuk melakukannya, kami mencoba mengatasi pola yang lebih bermasalah yang dihadapi developer. Kami dapat melakukannya dengan menyediakan API dan komponen bawaan platform yang lebih baik.

Salah satu area masalah tersebut adalah pop-up, yang dijelaskan di UI Terbuka sebagai "Pop-up".

Pop-up memiliki reputasi yang agak terpolarisasi sejak lama. Hal ini sebagian karena cara keduanya di-build dan di-deploy. Pola ini tidak mudah dibuat dengan baik, tetapi dapat menghasilkan banyak nilai dengan mengarahkan pengguna ke hal-hal tertentu, atau membuat mereka mengetahui konten di situs Anda—terutama jika digunakan dengan cara yang menarik.

Sering kali ada dua masalah utama saat membuat popover:

  • Cara memastikannya ditempatkan di atas konten lainnya di tempat yang sesuai.
  • Cara membuatnya dapat diakses (cocok untuk keyboard, dapat difokuskan, dan sebagainya).

Popover API bawaan memiliki berbagai sasaran, semuanya dengan sasaran menyeluruh yang sama, yaitu memudahkan developer membuat pola ini. Sasaran yang penting adalah:

  • Memudahkan tampilan elemen dan turunannya di atas dokumen lainnya.
  • Buat mudah diakses.
  • Tidak memerlukan JavaScript untuk sebagian besar perilaku umum (menutup ringan, singleton, penumpukan, dan sebagainya).

Anda dapat melihat spesifikasi lengkap untuk pop-up di situs OpenUI.

Kompatibilitas browser

Di mana Anda dapat menggunakan Popover API bawaan sekarang? Fitur ini didukung di Chrome Canary di balik flag "Fitur platform web eksperimental" pada saat penulisan.

Untuk mengaktifkan tanda tersebut, buka Chrome Canary dan buka chrome://flags. Kemudian, aktifkan tanda "Fitur platform web eksperimental".

Ada juga Uji Coba Origin untuk developer yang ingin mengujinya di lingkungan produksi.

Terakhir, ada polyfill yang sedang dikembangkan untuk API. Pastikan untuk melihat repo di github.com/oddbird/popup-polyfill.

Anda dapat memeriksa dukungan pop-up dengan:

const supported = HTMLElement.prototype.hasOwnProperty("popover");

Solusi saat ini

Apa yang dapat Anda lakukan saat ini untuk mempromosikan konten Anda di atas segalanya? Jika didukung di browser, Anda dapat menggunakan elemen Dialog HTML. Anda harus menggunakannya dalam bentuk "Modal". Dan ini memerlukan JavaScript untuk digunakan.

Dialog.showModal();

Ada beberapa pertimbangan aksesibilitas. Sebaiknya gunakan a11y-dialog, misalnya jika melayani pengguna Safari di bawah versi 15.4.

Anda juga dapat menggunakan salah satu dari banyak library berbasis popover, pemberitahuan, atau tooltip yang ada. Banyak dari aplikasi ini cenderung berfungsi dengan cara yang sama.

  • Tambahkan beberapa penampung ke isi untuk menampilkan popover.
  • Tata gayanya agar berada di atas yang lainnya.
  • Buat elemen dan tambahkan ke penampung untuk menampilkan popover.
  • Sembunyikan dengan menghapus elemen pop-up dari DOM.

Hal ini memerlukan dependensi tambahan dan lebih banyak keputusan bagi developer. Anda juga perlu melakukan riset untuk menemukan penawaran yang menyediakan semua yang Anda butuhkan. Popover API bertujuan untuk memenuhi banyak skenario, termasuk tooltip. Tujuannya adalah untuk mencakup semua skenario umum tersebut, sehingga developer tidak perlu membuat keputusan lain sehingga mereka dapat berfokus untuk membangun pengalaman mereka.

Pop-up pertama Anda

Hanya itu yang Anda perlukan.

<div id="my-first-popover" popover>Popover Content!</div>
<button popovertoggletarget="my-first-popover">Toggle Popover</button>

Namun, apa yang terjadi di sini?

  • Anda tidak perlu menempatkan elemen popover ke dalam penampung atau apa pun—elemen ini disembunyikan secara default.
  • Anda tidak perlu menulis JavaScript apa pun untuk membuatnya muncul. Hal ini ditangani oleh atribut popovertoggletarget.
  • Saat muncul, lapisan akan dipromosikan ke lapisan atas. Artinya, akan dipromosikan di atas document di area pandang. Anda tidak perlu mengelola z-index atau khawatir tentang posisi popover di DOM. Elemen ini dapat disusun bertingkat dalam DOM, dengan ancestor pemotongan. Anda juga dapat melihat elemen yang saat ini berada di lapisan atas melalui DevTools. Untuk mengetahui informasi selengkapnya tentang lapisan atas, baca artikel ini.

GIF dukungan lapisan atas DevTools yang sedang ditunjukkan

  • Anda mendapatkan "Tutup Ringan" secara default. Artinya, Anda dapat menutup popover dengan sinyal tutup, seperti mengklik di luar popover, menavigasi keyboard ke elemen lain, atau menekan tombol Esc. Buka lagi dan coba.

Apa lagi yang Anda dapatkan dengan popover? Mari kita bahas contohnya lebih lanjut. Pertimbangkan demo ini dengan beberapa konten di halaman.

Tombol tindakan mengambang tersebut memiliki posisi tetap dengan z-index yang tinggi.

.fab {
  position: fixed;
  z-index: 99999;
}

Konten pop-up disusun bertingkat di DOM, tetapi saat Anda membuka pop-up, konten tersebut akan dipromosikan di atas elemen posisi tetap tersebut. Anda tidak perlu menetapkan gaya apa pun.

Anda mungkin juga melihat bahwa popover kini memiliki elemen pseudo ::backdrop. Semua elemen yang berada di lapisan atas mendapatkan elemen pseudo ::backdrop yang dapat ditata gayanya. Contoh ini menata gaya ::backdrop dengan warna latar belakang alfa yang dikurangi dan filter latar belakang, yang memburamkan konten yang mendasarinya.

Menata gaya popover

Mari kita alihkan perhatian ke gaya popover. Secara default, popover memiliki posisi tetap dan beberapa padding yang diterapkan. Ini juga memiliki display: none. Anda dapat menggantinya untuk menampilkan popover. Namun, tindakan tersebut tidak akan mempromosikannya ke lapisan atas.

[popover] { display: block; }

Terlepas dari cara Anda mempromosikan popover, setelah mempromosikan popover ke lapisan atas, Anda mungkin perlu menata atau memosisikan popover. Anda tidak dapat menargetkan lapisan atas dan melakukan hal seperti

:open {
  display: grid;
  place-items: center;
}

Secara default, popover akan ditata di tengah area pandang menggunakan margin: auto. Namun, dalam beberapa kasus, Anda mungkin ingin menentukan posisi secara eksplisit. Contoh:

[popover] {
  top: 50%;
  left: 50%;
  translate: -50%;
}

Jika Anda ingin menata letak konten di dalam popover menggunakan petak CSS atau flexbox, sebaiknya gabungkan konten ini dalam elemen. Jika tidak, Anda harus mendeklarasikan aturan terpisah yang mengubah display setelah popover berada di lapisan atas. Menetapkan setelan ini secara default akan membuatnya ditampilkan secara default, yang akan mengganti display: none.

[popover]:open {
 display: flex;
}

Jika telah mencoba demo tersebut, Anda akan melihat bahwa popover kini bertransisi masuk dan keluar. Anda dapat melakukan transisi popover masuk dan keluar menggunakan pemilih pseudo :open. Pemilih pseudo :open cocok dengan popover yang ditampilkan (dan karenanya berada di lapisan atas).

Contoh ini menggunakan properti kustom untuk mendorong transisi. Anda juga dapat menerapkan transisi ke ::backdrop popover.

[popover] {
  --hide: 1;
  transition: transform 0.2s;
  transform: translateY(calc(var(--hide) * -100vh))
            scale(calc(1 - var(--hide)));
}

[popover]::backdrop {
  transition: opacity 0.2s;
  opacity: calc(1 - var(--hide, 1));
}


[popover]:open::backdrop  {
  --hide: 0;
}

Tipsnya adalah mengelompokkan transisi dan animasi dalam kueri media untuk gerakan. Hal ini juga dapat membantu mempertahankan pengaturan waktu Anda. Hal ini karena Anda tidak dapat membagikan nilai antara popover dan ::backdrop melalui properti kustom.

@media(prefers-reduced-motion: no-preference) {
  [popover] { transition: transform 0.2s; }
  [popover]::backdrop { transition: opacity 0.2s; }
}

Hingga saat ini, Anda telah melihat penggunaan popovertoggletarget untuk menampilkan popover. Untuk menutupnya, kita menggunakan "Tutup ringan". Namun, Anda juga mendapatkan atribut popovershowtarget dan popoverhidetarget yang dapat digunakan. Mari kita tambahkan tombol ke popover yang menyembunyikannya dan ubah tombol tombol untuk menggunakan popovershowtarget.

<div id="code-popover" popover>
  <button popoverhidetarget="code-popover">Hide Code</button>
</div>
<button popovershowtarget="code-popover">Reveal Code</button>

Seperti yang disebutkan sebelumnya, Popover API mencakup lebih dari sekadar konsep pop-up historis kami. Anda dapat mem-build untuk semua jenis skenario seperti notifikasi, menu, tooltip, dll.

Beberapa skenario tersebut memerlukan pola interaksi yang berbeda. Interaksi seperti pengarahan kursor. Penggunaan atribut popoverhovertarget telah diuji coba, tetapi saat ini belum diterapkan.

<div popoverhovertarget="hover-popover">Hover for Code</div>

Idenya adalah Anda mengarahkan kursor ke elemen untuk menampilkan target. Perilaku ini dapat dikonfigurasi melalui properti CSS. Properti CSS ini akan menentukan periode waktu untuk mengarahkan kursor ke dan dari elemen yang bereaksi terhadap popover. Perilaku default yang diuji coba memiliki popover yang ditampilkan setelah 0.5s eksplisit dari :hover. Kemudian, popover tersebut akan memerlukan penutupan ringan atau pembukaan popover lain untuk ditutup (Selengkapnya akan dibahas nanti). Hal ini disebabkan karena durasi tersembunyi popover disetel ke Infinity.

Sementara itu, Anda dapat menggunakan JavaScript untuk melakukan polyfill pada fungsi tersebut.

let hoverTimer;
const HOVER_TRIGGERS = document.querySelectorAll("[popoverhovertarget]");
const tearDown = () => {
  if (hoverTimer) clearTimeout(hoverTimer);
};
HOVER_TRIGGERS.forEach((trigger) => {
  const popover = document.querySelector(
    `#${trigger.getAttribute("popoverhovertarget")}`
  );
  trigger.addEventListener("pointerenter", () => {
    hoverTimer = setTimeout(() => {
      if (!popover.matches(":open")) popover.showPopOver();
    }, 500);
    trigger.addEventListener("pointerleave", tearDown);
  });
});

Manfaat menetapkan sesuatu sebagai periode pengarahan kursor yang eksplisit adalah memastikan tindakan pengguna disengaja (misalnya, pengguna mengarahkan kursor ke target). Kita tidak ingin menampilkan pop-up kecuali jika itu adalah niat mereka.

Coba demo ini tempat Anda dapat mengarahkan kursor ke target dengan jendela yang disetel ke 0.5s.


Sebelum menjelajahi beberapa kasus penggunaan dan contoh umum, mari kita bahas beberapa hal.


Jenis popover

Kita telah membahas perilaku interaksi non-JavaScript. Namun, bagaimana dengan perilaku popover secara keseluruhan. Bagaimana jika Anda tidak ingin "Tutup ringan"? Atau, Anda ingin menerapkan pola singleton ke popover?

Popover API memungkinkan Anda menentukan tiga jenis popover yang berbeda perilakunya.

[popover=auto]/[popover]:

  • Dukungan bertingkat. Ini juga tidak hanya berarti bertingkat dalam DOM. Definisi popover ancestor adalah popover yang:
    • terkait dengan posisi DOM (turunan).
    • terkait dengan memicu atribut pada elemen turunan seperti popovertoggletarget, popovershowtarget, dan sebagainya.
    • terkait dengan atribut anchor (CSS Anchoring API sedang dalam pengembangan).
  • Menutup ringan.
  • Membuka akan menutup popover lain yang bukan popover leluhur. Coba demo di bawah yang menyoroti cara kerja pembuatan bertingkat dengan popover leluhur. Lihat bagaimana perubahan beberapa instance popoverhidetarget/popovershowtarget menjadi popovertoggletarget akan mengubah hal-hal.
  • Menghapus satu dengan ringan akan menghapus semuanya, tetapi menghapus satu di kelompok hanya akan menghapus yang berada di atasnya dalam kelompok.

[popover=manual]:

  • Tidak menutup popover lainnya.
  • Tidak ada lampu yang dimatikan.
  • Memerlukan penutupan eksplisit melalui elemen pemicu atau JavaScript.

JavaScript API

Jika memerlukan lebih banyak kontrol atas popover, Anda dapat menggunakan JavaScript. Anda mendapatkan metode showPopover dan hidePopover. Anda juga memiliki peristiwa popovershow dan popoverhide yang akan diproses:

Menampilkan popover js popoverElement.showPopover() Menyembunyikan popover:

popoverElement.hidePopover()

Memproses popover yang ditampilkan:

popoverElement.addEventListener('popovershow', doSomethingWhenPopoverShows)

Memproses popover yang ditampilkan dan membatalkan tampilannya:

popoverElement.addEventListener('popovershow',event => {
  event.preventDefault();
  console.warn(We blocked a popover from being shown);
})

Memproses popover yang disembunyikan:

popoverElement.addEventListener('popoverhide', doSomethingWhenPopoverHides)

Anda tidak dapat membatalkan popover yang disembunyikan:

popoverElement.addEventListener('popoverhide',event => {
  event.preventDefault();
  console.warn("You aren't allowed to cancel the hiding of a popover");
})

Periksa apakah popover berada di lapisan atas:

popoverElement.matches(':open')

Hal ini memberikan daya ekstra untuk beberapa skenario yang kurang umum. Misalnya, tampilkan popover setelah periode tidak ada aktivitas.

Demo ini memiliki pop-up dengan pop yang terdengar, jadi kita memerlukan JavaScript untuk memutar audio. Saat diklik, kita akan menyembunyikan popover, memutar audio, lalu menampilkannya lagi.

Aksesibilitas

Aksesibilitas adalah hal yang paling penting dalam pemikiran dengan Popover API. Pemetaan aksesibilitas mengaitkan popover dengan elemen pemicunya, sesuai kebutuhan. Artinya, Anda tidak perlu mendeklarasikan atribut aria-* seperti aria-haspopup, dengan asumsi Anda menggunakan salah satu atribut pemicu seperti popovertoggletarget.

Untuk pengelolaan fokus, Anda dapat menggunakan atribut autofocus untuk memindahkan fokus ke elemen di dalam popover. Hal ini sama dengan Dialog, tetapi perbedaannya muncul saat mengembalikan fokus, dan itu karena penutupan ringan. Pada umumnya, menutup popover akan mengembalikan fokus ke elemen yang sebelumnya difokuskan. Namun, fokus akan dipindahkan ke elemen yang diklik saat menutup ringan, jika elemen tersebut dapat mendapatkan fokus. Lihat bagian tentang pengelolaan fokus dalam penjelasan.

Anda harus membuka "versi layar penuh" demo ini untuk melihat cara kerjanya.

Dalam demo ini, elemen yang difokuskan akan mendapatkan garis batas hijau. Coba gunakan tombol tab di antarmuka dengan keyboard. Perhatikan tempat fokus ditampilkan saat popover ditutup. Anda mungkin juga melihat bahwa jika Anda beralih tab, popover akan tertutup. Hal ini sesuai dengan desain. Meskipun memiliki pengelolaan fokus, popover tidak menjebak fokus. Selain itu, navigasi keyboard mengidentifikasi sinyal tutup saat fokus berpindah dari popover.

Penjangkaran (sedang dalam pengembangan)

Dalam hal popover, pola yang sulit untuk dipenuhi adalah mengaitkan elemen ke pemicunya. Misalnya, jika tooltip disetel untuk ditampilkan di atas pemicunya, tetapi dokumen di-scroll. Tooltip tersebut dapat terpotong oleh area pandang. Saat ini ada penawaran JavaScript untuk mengatasi hal ini seperti "Floating UI". Alat ini akan memosisikan ulang tooltip untuk menghentikan hal ini dan mengandalkan urutan posisi yang diinginkan.

Namun, kami ingin Anda dapat menentukannya dengan gaya Anda. Ada API pendamping yang sedang dikembangkan bersama Popover API untuk mengatasi hal ini. API "CSS Anchor Positioning" akan memungkinkan Anda untuk menautkan elemen ke elemen lain, dan API ini akan melakukannya dengan cara memosisikan ulang elemen sehingga tidak terpotong oleh area pandang.

Demo ini menggunakan Anchoring API dalam statusnya saat ini. Posisi perahu merespons posisi anchor di area pandang.

Berikut adalah cuplikan CSS yang membuat demo ini berfungsi. JavaScript tidak diperlukan.

.anchor {
  --anchor-name: --anchor;
}
.anchored {
  position: absolute;
  position-fallback: --compass;
}
@position-fallback --compass {
  @try {
    bottom: anchor(--anchor top);
    left: anchor(--anchor right);
  }
  @try {
    top: anchor(--anchor bottom);
    left: anchor(--anchor right);
  }
}

Anda dapat melihat spesifikasinya di sini. Akan ada juga polyfill untuk API ini.

Contoh

Setelah Anda memahami apa yang ditawarkan popover dan caranya, mari kita pelajari beberapa contohnya.

Notifikasi

Demo ini menampilkan notifikasi "Salin ke papan klip".

  • Menggunakan [popover=manual].
  • Pada tindakan, tampilkan pop-up dengan showPopover.
  • Setelah waktu tunggu 2000ms, sembunyikan dengan hidePopover.

Toast

Demo ini menggunakan lapisan atas untuk menampilkan notifikasi gaya toast.

  • Satu popover dengan jenis manual bertindak sebagai penampung.
  • Notifikasi baru ditambahkan ke popover dan popover ditampilkan.
  • Elemen ini dihapus dengan API animasi web saat diklik dan dihapus dari DOM.
  • Jika tidak ada toast yang akan ditampilkan, popover akan disembunyikan.

Menu bertingkat

Demo ini menunjukkan cara kerja menu navigasi bertingkat.

  • Gunakan [popover=auto] karena memungkinkan popover bertingkat.
  • Gunakan autofocus pada link pertama setiap dropdown untuk menavigasi dengan keyboard.
  • Ini adalah kandidat yang sempurna untuk CSS Anchoring API. Namun, untuk demo ini, Anda dapat menggunakan sedikit JavaScript untuk memperbarui posisi menggunakan properti kustom.
const ANCHOR = (anchor, anchored) => () => {
  const { top, bottom, left, right } = anchor.getBoundingClientRect();
  anchored.style.setProperty("--top", top);
  anchored.style.setProperty("--right", right);
  anchored.style.setProperty("--bottom", bottom);
  anchored.style.setProperty("--left", left);
};

PRODUCTS_MENU.addEventListener("popovershow", ANCHOR(PRODUCT_TARGET, PRODUCTS_MENU));

Ingat, karena demo ini menggunakan autofocus, demo ini harus dibuka dalam "tampilan layar penuh" untuk navigasi keyboard.

Popover media

Demo ini menunjukkan cara menampilkan media.

  • Menggunakan [popover=auto] untuk menutup ringan.
  • JavaScript memproses peristiwa play video dan memunculkan video.
  • Peristiwa popoverhide pop-up menjeda video.

Popover gaya wiki

Demo ini menunjukkan cara membuat tooltip konten inline yang berisi media.

  • Menggunakan [popover=auto]. Menampilkan salah satunya akan menyembunyikan yang lain karena tidak bersifat leluhur.
  • Ditampilkan di pointerenter dengan JavaScript.
  • Kandidat lain yang sempurna untuk CSS Anchoring API.

Demo ini membuat panel navigasi menggunakan popover.

  • Menggunakan [popover=auto] untuk menutup ringan.
  • Menggunakan autofocus untuk memfokuskan item navigasi pertama.

Mengelola latar belakang

Demo ini menunjukkan cara mengelola latar belakang untuk beberapa popover yang hanya ingin menampilkan satu ::backdrop.

  • Gunakan JavaScript untuk mempertahankan daftar pop-up yang terlihat.
  • Terapkan nama class ke popover terendah di lapisan atas.

Popover kursor kustom

Demo ini menunjukkan cara menggunakan popover untuk mempromosikan canvas ke lapisan atas dan menggunakannya untuk menampilkan kursor kustom.

  • Mempromosikan canvas ke lapisan atas dengan showPopover dan [popover=manual].
  • Saat popover lain dibuka, sembunyikan dan tampilkan popover canvas untuk memastikan popover tersebut berada di atas.

Popover sheet tindakan

Demo ini menunjukkan cara menggunakan popover sebagai actionsheet.

  • Menampilkan popover secara default yang menggantikan display.
  • Actionsheet dibuka dengan pemicu popover.
  • Saat ditampilkan, popover akan dipromosikan ke lapisan atas dan diterjemahkan ke dalam tampilan.
  • Penutupan ringan dapat digunakan untuk menampilkannya kembali.

Popover yang diaktifkan keyboard

Demo ini menunjukkan cara menggunakan popover untuk UI gaya palet perintah.

  • Gunakan cmd + j untuk menampilkan popover.
  • input difokuskan dengan autofocus.
  • Kotak kombinasi adalah popover kedua yang diposisikan di bawah input utama.
  • Menutup ringan akan menutup palet jika dropdown tidak ada.
  • Kandidat lain untuk Anchoring API

Popover berjangka waktu

Demo ini menampilkan popover tidak ada aktivitas setelah empat detik. Pola UI yang sering digunakan di aplikasi yang menyimpan informasi aman tentang pengguna untuk menampilkan modal logout.

  • Gunakan JavaScript untuk menampilkan popover setelah periode tidak ada aktivitas.
  • Saat popover ditampilkan, reset timer.

Screensaver

Serupa dengan demo sebelumnya, Anda dapat menambahkan sedikit keceriaan ke situs dan menambahkan screensaver.

  • Gunakan JavaScript untuk menampilkan popover setelah periode tidak ada aktivitas.
  • Menutup dengan ringan untuk menyembunyikan dan mereset timer.

Ikuti tanda caret

Demo ini menunjukkan cara membuat popover mengikuti kursor input.

  • Menampilkan popover berdasarkan pilihan, peristiwa utama, atau input karakter khusus.
  • Gunakan JavaScript untuk memperbarui posisi pop-up dengan properti kustom cakupan.
  • Pola ini akan memerlukan pemikiran yang cermat terhadap konten yang ditampilkan dan aksesibilitas.
  • Ikon ini sering terlihat di UI pengeditan teks dan aplikasi tempat Anda dapat memberi tag.

Menu tombol tindakan mengambang

Demo ini menunjukkan cara menggunakan popover untuk menerapkan menu tombol tindakan mengambang tanpa JavaScript.

  • Promosikan popover jenis manual dengan metode showPopover. Ini adalah tombol utama.
  • Menu adalah popover lain yang merupakan target tombol utama.
  • Menu dibuka dengan popovertoggletarget.
  • Gunakan autofocus untuk memfokuskan item menu pertama saat ditampilkan.
  • Menutup ringan akan menutup menu.
  • Putaran ikon menggunakan :has(). Anda dapat membaca selengkapnya tentang :has() di artikel ini.

Selesai.

Jadi, itulah pengantar popover, yang akan hadir sebagai bagian dari inisiatif Open UI. Jika digunakan dengan bijak, ini akan menjadi tambahan yang luar biasa untuk platform web.

Pastikan untuk melihat UI Terbuka. Penjelasan pop-up selalu diperbarui seiring perkembangan API. Berikut adalah koleksi untuk semua demo.

Terima kasih telah mampir.


Foto oleh Madison Oren di Unsplash