Bagaimana cara Anda saat ini untuk menautkan satu elemen ke elemen lainnya? Anda dapat mencoba melacak posisinya, atau menggunakan beberapa bentuk elemen wrapper.
<!-- index.html -->
<div class="container">
<a href="/link" class="anchor">I’m the anchor</a>
<div class="anchored">I’m the anchored thing</div>
</div>
/* styles.css */
.container {
position: relative;
}
.anchored {
position: absolute;
}
Solusi ini sering kali tidak ideal. Alat ini memerlukan JavaScript atau memperkenalkan markup tambahan. API pemosisian anchor CSS bertujuan untuk mengatasi hal ini dengan menyediakan CSS API untuk elemen tethering. Ini menyediakan cara untuk memosisikan dan menentukan ukuran satu elemen berdasarkan posisi dan ukuran elemen lain.
Dukungan browser
Anda dapat mencoba API penentuan posisi anchor CSS di Chrome Canary dengan mengaktifkan flag "Fitur Platform Web Eksperimental". Untuk mengaktifkan tanda tersebut, buka Chrome Canary dan buka chrome://flags
. Kemudian, aktifkan tanda "Fitur platform web eksperimental".
Ada juga polyfill yang sedang dalam pengembangan oleh tim di Oddbird. Pastikan untuk memeriksa repo di github.com/oddbird/css-anchor-positioning.
Anda dapat memeriksa dukungan anchor dengan:
@supports(anchor-name: --foo) {
/* Styles... */
}
Perhatikan bahwa API ini masih dalam tahap eksperimental dan dapat berubah. Artikel ini membahas bagian-bagian penting secara umum. Implementasi saat ini juga tidak sepenuhnya sinkron dengan spesifikasi CSS Working Group.
Permasalahan
Mengapa Anda perlu melakukan ini? Kasus penggunaan yang penting adalah membuat tooltip atau pengalaman seperti tooltip. Dalam hal ini, Anda sering kali ingin menautkan tooltip ke konten yang dirujuknya. Sering kali diperlukan beberapa cara untuk menautkan elemen ke elemen lain. Anda juga berharap bahwa berinteraksi dengan halaman tidak akan merusak tether tersebut—misalnya, jika pengguna men-scroll atau mengubah ukuran UI.
Bagian lain dari masalah ini adalah jika Anda ingin memastikan elemen yang ditautkan tetap terlihat—misalnya, jika Anda membuka tooltip dan tooltip tersebut terpotong oleh batas area pandang. Hal ini mungkin bukan pengalaman yang baik bagi pengguna. Anda ingin tooltip beradaptasi.
Solusi saat ini
Saat ini, ada beberapa cara berbeda yang dapat Anda gunakan untuk mengatasi masalah ini.
Pertama adalah pendekatan "Gabungkan anchor" dasar. Anda mengambil kedua elemen tersebut dan menggabungkannya dalam penampung. Kemudian, Anda dapat menggunakan position
untuk memosisikan tooltip relatif terhadap anchor.
<div class="containing-block">
<div class="tooltip">Anchor me!</div>
<a class="anchor">The anchor</a>
</div>
.containing-block {
position: relative;
}
.tooltip {
position: absolute;
bottom: calc(100% + 10px);
left: 50%;
transform: translateX(-50%);
}
Anda dapat memindahkan penampung dan sebagian besar item akan tetap berada di tempat yang Anda inginkan.
Pendekatan lain mungkin jika Anda mengetahui posisi anchor atau Anda dapat melacaknya. Anda dapat meneruskannya ke tooltip dengan properti kustom.
<div class="tooltip">Anchor me!</div>
<a class="anchor">The anchor</a>
:root {
--anchor-width: 120px;
--anchor-top: 40vh;
--anchor-left: 20vmin;
}
.anchor {
position: absolute;
top: var(--anchor-top);
left: var(--anchor-left);
width: var(--anchor-width);
}
.tooltip {
position: absolute;
top: calc(var(--anchor-top));
left: calc((var(--anchor-width) * 0.5) + var(--anchor-left));
transform: translate(-50%, calc(-100% - 10px));
}
Namun, bagaimana jika Anda tidak tahu posisi anchor? Anda mungkin perlu melakukan intervensi dengan JavaScript. Anda dapat melakukan sesuatu seperti yang dilakukan kode berikut, tetapi sekarang ini berarti gaya Anda mulai bocor dari CSS dan masuk ke JavaScript.
const setAnchorPosition = (anchored, anchor) => {
const bounds = anchor.getBoundingClientRect().toJSON();
for (const [key, value] of Object.entries(bounds)) {
anchored.style.setProperty(`--${key}`, value);
}
};
const update = () => {
setAnchorPosition(
document.querySelector('.tooltip'),
document.querySelector('.anchor')
);
};
window.addEventListener('resize', update);
document.addEventListener('DOMContentLoaded', update);
Hal ini mulai menimbulkan beberapa pertanyaan:
- Kapan saya menghitung gaya?
- Bagaimana cara menghitung gaya?
- Seberapa sering saya menghitung gaya?
Apakah masalahnya sudah teratasi? Mungkin untuk kasus penggunaan Anda, tetapi ada satu masalah: solusi kami tidak beradaptasi. Tidak responsif. Bagaimana jika elemen yang ditautkan terpotong oleh area pandang?
Sekarang Anda perlu memutuskan apakah akan bereaksi terhadap hal ini dan bagaimana caranya. Jumlah pertanyaan dan keputusan yang perlu Anda buat mulai bertambah. Anda hanya perlu mengaitkan satu elemen ke elemen lainnya. Dan dalam kondisi ideal, solusi Anda akan menyesuaikan dan bereaksi terhadap lingkungannya.
Untuk meringankan beberapa masalah tersebut, Anda dapat menggunakan solusi JavaScript untuk membantu Anda. Tindakan ini akan menimbulkan biaya penambahan dependensi ke project Anda, dan dapat menyebabkan masalah performa bergantung pada cara Anda menggunakannya. Misalnya, beberapa paket menggunakan requestAnimationFrame
untuk menjaga posisi tetap benar. Artinya, Anda dan tim harus memahami paket dan opsi konfigurasinya. Akibatnya, pertanyaan dan keputusan Anda mungkin tidak berkurang, tetapi berubah. Ini adalah bagian dari "alasan" untuk pemosisian anchor CSS. Hal ini akan membuat Anda tidak perlu memikirkan masalah performa saat menghitung posisi.
Berikut adalah tampilan kode untuk menggunakan "floating-ui", paket populer untuk masalah ini:
import {computePosition, flip, offset, autoUpdate} from 'https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.2.1/+esm';
const anchor = document.querySelector('.anchor')
const tooltip = document.querySelector('.tooltip')
const updatePosition = () => {
computePosition(anchor, tooltip, {
placement: 'top',
middleware: [offset(10), flip()]
})
.then(({x, y}) => {
Object.assign(tooltip.style, {
left: `${x}px`,
top: `${y}px`
})
})
};
const clean = autoUpdate(anchor, tooltip, updatePosition);
Coba posisikan ulang anchor dalam demo ini yang menggunakan kode tersebut.
"Tooltip" mungkin tidak berperilaku seperti yang Anda harapkan. Fungsi ini bereaksi saat keluar dari area pandang pada sumbu y, tetapi tidak pada sumbu x. Pelajari dokumentasi, dan Anda mungkin akan menemukan solusi yang sesuai untuk Anda.
Namun, menemukan paket yang sesuai untuk project Anda dapat memerlukan banyak waktu. Ini adalah keputusan tambahan dan dapat membuat frustrasi jika tidak berfungsi seperti yang Anda inginkan.
Menggunakan pengaturan posisi anchor
Masukkan API CSS anchor positioning. Idenya adalah untuk mempertahankan gaya Anda di CSS dan mengurangi jumlah keputusan yang perlu Anda buat. Anda berharap untuk mencapai hasil yang sama, tetapi tujuannya adalah untuk membuat pengalaman developer menjadi lebih baik.
- JavaScript tidak diperlukan.
- Biarkan browser menentukan posisi terbaik dari panduan Anda.
- Tidak ada lagi dependensi pihak ketiga
- Tidak ada elemen wrapper.
- Berfungsi dengan elemen yang berada di lapisan atas.
Mari kita buat ulang dan tangani masalah yang kita coba pecahkan di atas. Sebagai gantinya, gunakan analogi perahu dengan jangkar. Ini mewakili elemen dan anchor yang ditautkan. Air mewakili blok yang berisi.
Pertama, Anda harus memilih cara menentukan anchor. Anda dapat melakukannya dalam CSS dengan menetapkan properti anchor-name
pada elemen anchor. Properti ini menerima nilai dashed-ident.
.anchor {
anchor-name: --my-anchor;
}
Atau, Anda dapat menentukan anchor di HTML dengan atribut anchor
. Nilai atribut adalah ID elemen anchor. Tindakan ini akan membuat anchor implisit.
<a id="my-anchor" class="anchor"></a>
<div anchor="my-anchor" class="boat">I’m a boat!</div>
Setelah menentukan anchor, Anda dapat menggunakan fungsi anchor
. Fungsi anchor
menggunakan 3 argumen:
- Elemen anchor:
anchor-name
anchor yang akan digunakan—atau, Anda dapat menghilangkan nilai untuk menggunakan anchorimplicit
. Nilai ini dapat ditentukan melalui hubungan HTML, atau dengan propertianchor-default
dengan nilaianchor-name
. - Sisi anchor: Kata kunci posisi yang ingin Anda gunakan. Ini dapat berupa
top
,right
,bottom
,left
,center
, dll. Atau, Anda dapat meneruskan persentase. Misalnya, 50% akan sama dengancenter
. - Penggantian: Ini adalah nilai penggantian opsional yang menerima panjang atau persentase.
Anda menggunakan fungsi anchor
sebagai nilai untuk properti inset (top
, right
, bottom
, left
, atau yang setara secara logis) dari elemen yang ditautkan. Anda juga dapat menggunakan fungsi anchor
di calc
:
.boat {
bottom: anchor(--my-anchor top);
left: calc(anchor(--my-anchor center) - (var(--boat-size) * 0.5));
}
/* alternative with anchor-default */
.boat {
anchor-default: --my-anchor;
bottom: anchor(top);
left: calc(anchor(center) - (var(--boat-size) * 0.5));
}
Tidak ada properti inset center
sehingga salah satu opsi adalah menggunakan calc
jika Anda mengetahui ukuran elemen yang ditautkan. Mengapa tidak menggunakan translate
? Anda dapat menggunakan ini:
.boat {
anchor-default: --my-anchor;
bottom: anchor(top);
left: anchor(center);
translate: -50% 0;
}
Namun, browser tidak mempertimbangkan posisi yang ditransformasi untuk elemen yang ditautkan. Anda akan memahami pentingnya hal ini saat mempertimbangkan penggantian posisi dan pemosisian otomatis.
Anda mungkin telah melihat penggunaan properti kustom --boat-size
di atas. Namun, jika ingin mendasarkan ukuran elemen yang ditautkan pada ukuran anchor, Anda juga dapat mengakses ukuran tersebut. Daripada menghitungnya sendiri, Anda dapat menggunakan fungsi anchor-size
. Misalnya, untuk membuat perahu empat kali lebar jangkar:
.boat {
width: calc(4 * anchor-size(--my-anchor width));
}
Anda juga memiliki akses ke tinggi dengan anchor-size(--my-anchor height)
. Anda juga dapat menggunakannya untuk menetapkan ukuran salah satu sumbu atau keduanya.
Bagaimana jika Anda ingin melakukan anchor ke elemen dengan pemosisian absolute
? Aturannya adalah elemen tidak boleh merupakan elemen yang sama. Dalam hal ini, Anda dapat menggabungkan anchor dengan penampung yang memiliki pemosisian relative
. Kemudian, Anda dapat menautkannya.
<div class="anchor-wrapper">
<a id="my-anchor" class="anchor"></a>
</div>
<div class="boat">I’m a boat!</div>
Lihat demo ini tempat Anda dapat menarik jangkar dan perahu akan mengikutinya.
Melacak posisi scroll
Dalam beberapa kasus, elemen anchor Anda mungkin berada dalam penampung scroll. Pada saat yang sama, elemen yang Anda anchor mungkin berada di luar penampung tersebut. Karena scroll terjadi di thread yang berbeda dari tata letak, Anda memerlukan cara untuk melacaknya. Properti anchor-scroll
dapat melakukannya. Anda menetapkannya pada elemen yang ditautkan dan memberinya nilai anchor yang ingin dilacak.
.boat { anchor-scroll: --my-anchor; }
Coba demo ini tempat Anda dapat mengaktifkan dan menonaktifkan anchor-scroll
dengan kotak centang di sudut.
Namun, analogi ini sedikit kurang tepat karena dalam kondisi ideal, kapal dan jangkar Anda berada di air. Selain itu, fitur seperti Popover API mendorong kemampuan untuk menjaga elemen terkait tetap dekat. Namun, pemosisian anchor akan berfungsi dengan elemen yang berada di lapisan atas. Ini adalah salah satu manfaat utama di balik API: dapat mengikat elemen dalam alur yang berbeda.
Perhatikan demo ini yang memiliki penampung scroll dengan anchor yang memiliki tooltip. Elemen tooltip yang merupakan popover mungkin tidak berada di lokasi yang sama dengan anchor:
Namun, Anda akan melihat cara popover melacak link anchor masing-masing. Anda dapat mengubah ukuran penampung scroll tersebut dan posisinya akan diperbarui untuk Anda.
Penggantian posisi dan pemosisian otomatis
Di sinilah kekuatan penentuan posisi anchor meningkat. position-fallback
dapat memosisikan elemen yang ditautkan berdasarkan serangkaian penggantian yang Anda berikan. Anda memandu browser dengan gaya Anda dan membiarkannya menentukan posisi untuk Anda.
Kasus penggunaan umum di sini adalah tooltip yang harus beralih antara ditampilkan di atas atau di bawah anchor. Dan perilaku ini didasarkan pada apakah tooltip akan terpotong oleh penampung. Penampung tersebut biasanya adalah area pandang.
Jika Anda mempelajari kode demo terakhir, Anda akan melihat bahwa ada properti position-fallback
yang digunakan. Jika men-scroll penampung, Anda mungkin melihat popover yang ditautkan tersebut melompat. Hal ini terjadi saat anchor masing-masing mendekati batas area pandang. Pada saat itu, popover mencoba menyesuaikan agar tetap berada dalam area pandang.
Sebelum membuat position-fallback
eksplisit, pemosisian anchor juga akan menawarkan pemosisian otomatis. Anda bisa mendapatkan flip tersebut secara gratis dengan menggunakan nilai auto
di fungsi anchor dan properti inset yang berlawanan. Misalnya, jika Anda menggunakan anchor
untuk bottom
, tetapkan top
ke auto
.
.tooltip {
position: absolute;
bottom: anchor(--my-anchor auto);
top: auto;
}
Alternatif untuk pemosisian otomatis adalah menggunakan position-fallback
eksplisit. Hal ini mengharuskan Anda menentukan kumpulan penggantian posisi. Browser akan memeriksanya hingga menemukan salah satu yang dapat digunakan, lalu menerapkan posisi tersebut. Jika tidak dapat menemukan yang berfungsi, defaultnya adalah yang pertama ditentukan.
position-fallback
yang mencoba menampilkan tooltip di atas lalu di bawah dapat terlihat seperti ini:
@position-fallback --top-to-bottom {
@try {
bottom: anchor(top);
left: anchor(center);
}
@try {
top: anchor(bottom);
left: anchor(center);
}
}
Penerapannya ke tooltip akan terlihat seperti ini:
.tooltip {
anchor-default: --my-anchor;
position-fallback: --top-to-bottom;
}
Penggunaan anchor-default
berarti Anda dapat menggunakan kembali position-fallback
untuk elemen lain. Anda juga dapat menggunakan properti kustom cakupan untuk menetapkan anchor-default
.
Pertimbangkan demo ini menggunakan perahu lagi. Ada position-fallback
yang ditetapkan. Saat Anda mengubah posisi anchor, perahu akan menyesuaikan agar tetap berada dalam penampung. Coba ubah juga nilai padding yang menyesuaikan padding isi. Perhatikan cara browser memperbaiki posisi. Posisi diubah dengan mengubah perataan petak penampung.
position-fallback
kali ini lebih panjang dan mencoba posisi dalam arah searah jarum jam.
.boat {
anchor-default: --my-anchor;
position-fallback: --compass;
}
@position-fallback --compass {
@try {
bottom: anchor(top);
right: anchor(left);
}
@try {
bottom: anchor(top);
left: anchor(right);
}
@try {
top: anchor(bottom);
right: anchor(left);
}
@try {
top: anchor(bottom);
left: anchor(right);
}
}
Contoh
Setelah Anda memiliki gambaran tentang fitur utama untuk pemosisian anchor, mari kita lihat beberapa contoh menarik di luar tooltip. Contoh ini bertujuan untuk memunculkan ide tentang cara menggunakan penentuan posisi anchor. Cara terbaik untuk mengembangkan spesifikasi lebih lanjut adalah dengan input dari pengguna seperti Anda.
Menu konteks
Mari kita mulai dengan menu konteks menggunakan Popover API. Idenya adalah dengan mengklik tombol yang memiliki tanda chevron, menu konteks akan muncul. Dan menu tersebut akan memiliki menu sendiri untuk diperluas.
Markup bukanlah bagian yang penting di sini. Namun, Anda memiliki tiga tombol yang masing-masing menggunakan popovertarget
. Kemudian, Anda memiliki tiga elemen yang menggunakan atribut popover
. Hal ini memberi Anda kemampuan untuk membuka menu konteks tanpa JavaScript. Tampilannya mungkin terlihat seperti ini:
<button popovertarget="context">
Toggle Menu
</button>
<div popover="auto" id="context">
<ul>
<li><button>Save to your Liked Songs</button></li>
<li>
<button popovertarget="playlist">
Add to Playlist
</button>
</li>
<li>
<button popovertarget="share">
Share
</button>
</li>
</ul>
</div>
<div popover="auto" id="share">...</div>
<div popover="auto" id="playlist">...</div>
Sekarang, Anda dapat menentukan position-fallback
dan membagikannya di antara menu konteks. Kami juga memastikan untuk menetapkan gaya inset
untuk popover.
[popovertarget="share"] {
anchor-name: --share;
}
[popovertarget="playlist"] {
anchor-name: --playlist;
}
[popovertarget="context"] {
anchor-name: --context;
}
#share {
anchor-default: --share;
position-fallback: --aligned;
}
#playlist {
anchor-default: --playlist;
position-fallback: --aligned;
}
#context {
anchor-default: --context;
position-fallback: --flip;
}
@position-fallback --aligned {
@try {
top: anchor(top);
left: anchor(right);
}
@try {
top: anchor(bottom);
left: anchor(right);
}
@try {
top: anchor(top);
right: anchor(left);
}
@try {
bottom: anchor(bottom);
left: anchor(right);
}
@try {
right: anchor(left);
bottom: anchor(bottom);
}
}
@position-fallback --flip {
@try {
bottom: anchor(top);
left: anchor(left);
}
@try {
right: anchor(right);
bottom: anchor(top);
}
@try {
top: anchor(bottom);
left: anchor(left);
}
@try {
top: anchor(bottom);
right: anchor(right);
}
}
Tindakan ini akan memberi Anda UI menu konteks bertingkat adaptif. Coba ubah posisi konten dengan memilih. Opsi yang Anda pilih akan memperbarui perataan petak. Hal ini memengaruhi cara penentuan posisi anchor menempatkan popover.
Fokus dan ikuti
Demo ini menggabungkan primitif CSS dengan memasukkan :has(). Idenya adalah untuk mentransisikan indikator visual untuk input
yang memiliki fokus.
Lakukan ini dengan menetapkan anchor baru saat runtime. Untuk demo ini, properti kustom cakupan akan diperbarui saat fokus input.
#email {
anchor-name: --email;
}
#name {
anchor-name: --name;
}
#password {
anchor-name: --password;
}
:root:has(#email:focus) {
--active-anchor: --email;
}
:root:has(#name:focus) {
--active-anchor: --name;
}
:root:has(#password:focus) {
--active-anchor: --password;
}
:root {
--active-anchor: --name;
--active-left: anchor(var(--active-anchor) right);
--active-top: calc(
anchor(var(--active-anchor) top) +
(
(
anchor(var(--active-anchor) bottom) -
anchor(var(--active-anchor) top)
) * 0.5
)
);
}
.form-indicator {
left: var(--active-left);
top: var(--active-top);
transition: all 0.2s;
}
Namun, bagaimana Anda dapat mengembangkannya lebih lanjut? Anda dapat menggunakannya untuk beberapa bentuk overlay petunjuk. Tooltip dapat berpindah di antara lokasi menarik dan memperbarui kontennya. Anda dapat melakukan crossfade pada konten. Animasi terpisah yang memungkinkan Anda mengoanimasi display
atau Transisi Tampilan dapat berfungsi di sini.
Kalkulasi diagram batang
Hal menyenangkan lainnya yang dapat Anda lakukan dengan pemosisian anchor adalah menggabungkannya dengan calc
. Bayangkan diagram yang memiliki beberapa pop-up yang menganotasi diagram.
Anda dapat melacak nilai tertinggi dan terendah menggunakan min
dan max
CSS. CSS untuk hal tersebut mungkin terlihat seperti ini:
.chart__tooltip--max {
left: anchor(--chart right);
bottom: max(
anchor(--anchor-1 top),
anchor(--anchor-2 top),
anchor(--anchor-3 top)
);
translate: 0 50%;
}
Ada beberapa JavaScript yang digunakan untuk memperbarui nilai diagram dan beberapa CSS untuk menata gaya diagram. Namun, pemosisian anchor akan menangani pembaruan tata letak untuk kita.
Mengubah Ukuran Handel
Anda tidak harus hanya mengaitkan ke satu elemen. Anda dapat menggunakan banyak anchor untuk sebuah elemen. Anda mungkin telah melihatnya dalam contoh diagram batang. Tooltip ditautkan ke diagram, lalu ke batang yang sesuai. Jika Anda mengembangkan konsep tersebut lebih lanjut, Anda dapat menggunakannya untuk mengubah ukuran elemen.
Anda dapat memperlakukan titik anchor seperti handle perubahan ukuran kustom dan menggunakan nilai inset
.
.container {
position: absolute;
inset:
anchor(--handle-1 top)
anchor(--handle-2 right)
anchor(--handle-2 bottom)
anchor(--handle-1 left);
}
Dalam demo ini, GreenSock Draggable membuat handle menjadi Draggable. Namun, elemen <img>
akan diubah ukurannya untuk mengisi penampung yang disesuaikan untuk mengisi celah di antara tuas.
SelectMenu?
Yang terakhir ini sedikit menggoda untuk apa yang akan datang. Namun, Anda dapat membuat popover yang dapat difokuskan dan sekarang Anda memiliki posisi anchor. Anda dapat membuat dasar-dasar elemen <select>
yang dapat ditata gayanya.
<div class="select-menu">
<button popovertarget="listbox">
Select option
<svg>...</svg>
</button>
<div popover="auto" id="listbox">
<option>A</option>
<option>Styled</option>
<option>Select</option>
</div>
</div>
anchor
implisit akan mempermudah hal ini. Namun, CSS untuk titik awal dasar dapat terlihat seperti ini:
[popovertarget] {
anchor-name: --select-button;
}
[popover] {
anchor-default: --select-button;
top: anchor(bottom);
width: anchor-size(width);
left: anchor(left);
}
Gabungkan fitur Popover API dengan CSS Anchor positioning dan Anda hampir selesai.
Akan lebih baik jika Anda mulai memperkenalkan hal-hal seperti :has()
. Anda dapat memutar penanda saat terbuka:
.select-menu:has(:open) svg {
rotate: 180deg;
}
Ke mana Anda dapat membawanya selanjutnya? Apa lagi yang perlu kita lakukan agar select
berfungsi? Kita akan membahasnya di artikel berikutnya. Namun, jangan khawatir, elemen pilih yang dapat ditata gayanya akan segera hadir. Nantikan kabar terbarunya.
Selesai!
Platform web terus berkembang. Pemosisi anchor CSS adalah bagian penting untuk meningkatkan cara Anda mengembangkan kontrol UI. Hal ini akan membuat Anda terhindar dari beberapa keputusan rumit tersebut. Namun, hal ini juga akan memungkinkan Anda melakukan hal-hal yang belum pernah Anda lakukan sebelumnya. Seperti menata gaya elemen <select>
. Utarakan pendapat Anda.
Foto oleh CHUTTERSNAP di Unsplash