Memperkenalkan visualViewport

Jake Archibald
Jake Archibald

Bagaimana jika saya memberi tahu Anda bahwa ada lebih dari satu area pandang.

BRRRRAAAAAAAMMMMMMMMMM

Dan area pandang yang Anda gunakan saat ini, sebenarnya adalah area pandang dalam area pandang.

BRRRRAAAAAAAMMMMMMMMMM

Terkadang, data yang diberikan DOM kepada Anda merujuk ke salah satu area pandang tersebut, bukan yang lainnya.

BRRRRAAAAM… tunggu, apa?

Benar, lihat:

Area pandang tata letak vs. area pandang visual

Video di atas menunjukkan halaman web yang di-scroll dan diperbesar dengan mencubit, beserta peta mini di sebelah kanan yang menampilkan posisi area pandang dalam halaman.

Semuanya cukup mudah selama scroll biasa. Area hijau mewakili tampilan area tata letak, yang dipatuhi item position: fixed.

Hal-hal menjadi aneh saat fitur cubit untuk zoom diperkenalkan. Kotak merah mewakili area pandang visual, yang merupakan bagian dari halaman yang benar-benar dapat kita lihat. Area pandang ini dapat berpindah-pindah sementara elemen position: fixed tetap berada di tempatnya, yang terpasang ke area pandang tata letak. Jika kita menggeser pada batas area pandang tata letak, area pandang tata letak akan ikut terbawa.

Meningkatkan kompatibilitas

Sayangnya, web API tidak konsisten dalam hal area pandang yang dirujuknya, dan juga tidak konsisten di seluruh browser.

Misalnya, element.getBoundingClientRect().y menampilkan offset dalam area tampilan tata letak. Itu bagus, tetapi kita sering kali menginginkan posisi dalam halaman, jadi kita menulis:

element.getBoundingClientRect().y + window.scrollY

Namun, banyak browser menggunakan area pandang visual untuk window.scrollY, yang berarti kode di atas akan rusak saat pengguna melakukan zoom dengan mencubit.

Chrome 61 mengubah window.scrollY untuk merujuk ke area pandang tata letak, yang berarti kode di atas berfungsi bahkan saat di-pinch-zoom. Faktanya, browser secara perlahan mengubah semua properti posisi untuk merujuk ke area pandang tata letak.

Dengan pengecualian satu properti baru…

Mengekspos area pandang visual ke skrip

API baru mengekspos area pandang visual sebagai window.visualViewport. Ini adalah spesifikasi draf, dengan persetujuan lintas browser, dan diluncurkan di Chrome 61.

console.log(window.visualViewport.width);

Berikut adalah hal yang diberikan window.visualViewport kepada kita:

visualViewport properti
offsetLeft Jarak antara tepi kiri area pandang visual, dan area pandang tata letak, dalam piksel CSS.
offsetTop Jarak antara tepi atas area pandang visual, dan area pandang tata letak, dalam piksel CSS.
pageLeft Jarak antara tepi kiri area pandang visual, dan batas kiri dokumen, dalam piksel CSS.
pageTop Jarak antara tepi atas area pandang visual, dan batas atas dokumen, dalam piksel CSS.
width Lebar area pandang visual dalam piksel CSS.
height Tinggi area pandang visual dalam piksel CSS.
scale Skala yang diterapkan dengan mencubit untuk zoom. Jika konten berukuran dua kali lipat karena zoom, tindakan ini akan menampilkan 2. Hal ini tidak terpengaruh oleh devicePixelRatio.

Ada juga beberapa peristiwa:

window.visualViewport.addEventListener('resize', listener);
visualViewport peristiwa
resize Diaktifkan saat width, height, atau scale berubah.
scroll Diaktifkan saat offsetLeft atau offsetTop berubah.

Demo

Video di awal artikel ini dibuat menggunakan visualViewport, lihat di Chrome 61+. Video ini menggunakan visualViewport untuk membuat peta mini tetap berada di kanan atas area pandang visual, dan menerapkan skala terbalik sehingga selalu muncul dengan ukuran yang sama, meskipun melakukan cubit untuk zoom.

Gotcha

Peristiwa hanya diaktifkan saat area pandang visual berubah

Hal ini terasa seperti hal yang jelas untuk dinyatakan, tetapi saya tidak menyadarinya saat pertama kali bermain dengan visualViewport.

Jika ukuran area pandang tata letak diubah, tetapi area pandang visual tidak diubah, Anda tidak akan mendapatkan peristiwa resize. Namun, viewport tata letak yang diubah ukurannya tanpa viewport visual juga mengubah lebar/tinggi adalah hal yang tidak biasa.

Masalah sebenarnya adalah scroll. Jika scroll terjadi, tetapi area pandang visual tetap statis secara relatif terhadap area pandang tata letak, Anda tidak akan mendapatkan peristiwa scroll di visualViewport, dan ini sangat umum. Selama scroll dokumen reguler, area pandang visual tetap terkunci ke kiri atas area pandang tata letak, sehingga scroll tidak diaktifkan di visualViewport.

Jika ingin mengetahui semua perubahan pada area pandang visual, termasuk pageTop dan pageLeft, Anda juga harus memproses peristiwa scroll jendela:

visualViewport.addEventListener('scroll', update);
visualViewport.addEventListener('resize', update);
window.addEventListener('scroll', update);

Menghindari duplikasi pekerjaan dengan beberapa pemroses

Serupa dengan memproses scroll & resize di jendela, Anda kemungkinan akan memanggil semacam fungsi "update" sebagai hasilnya. Namun, banyak peristiwa ini terjadi secara bersamaan. Jika pengguna mengubah ukuran jendela, tindakan ini akan memicu resize, tetapi sering kali juga memicu scroll. Untuk meningkatkan performa, hindari menangani perubahan beberapa kali:

// Add listeners
visualViewport.addEventListener('scroll', update);
visualViewport.addEventListener('resize', update);
addEventListener('scroll', update);

let pendingUpdate = false;

function update() {
    // If we're already going to handle an update, return
    if (pendingUpdate) return;

    pendingUpdate = true;

    // Use requestAnimationFrame so the update happens before next render
    requestAnimationFrame(() => {
    pendingUpdate = false;

    // Handle update here
    });
}

Saya telah mengajukan masalah spesifikasi untuk hal ini, karena saya pikir mungkin ada cara yang lebih baik, seperti satu peristiwa update.

Pengendali peristiwa tidak berfungsi

Karena bug Chrome, tindakan ini tidak berfungsi:

Larangan

Bermasalah – menggunakan pengendali peristiwa

visualViewport.onscroll = () => console.log('scroll!');

Sebagai gantinya:

Anjuran

Berfungsi – menggunakan pemroses peristiwa

visualViewport.addEventListener('scroll', () => console.log('scroll'));

Nilai offset dibulatkan

Saya rasa (semoga) ini adalah bug Chrome lainnya.

offsetLeft dan offsetTop dibulatkan, yang cukup tidak akurat setelah pengguna memperbesar. Anda dapat melihat masalah ini selama demo – jika pengguna melakukan zoom in dan menggeser lahan secara perlahan, peta mini akan berpindah di antara piksel yang tidak di-zoom.

Kecepatan peristiwa lambat

Seperti peristiwa resize dan scroll lainnya, peristiwa ini tidak diaktifkan setiap frame, terutama di perangkat seluler. Anda dapat melihatnya selama demo – setelah Anda melakukan zoom dengan mencubit, peta mini akan mengalami masalah untuk tetap terkunci ke area pandang.

Aksesibilitas

Dalam demo, saya menggunakan visualViewport untuk mengatasi tindakan cubit-zoom pengguna. Hal ini masuk akal untuk demo khusus ini, tetapi Anda harus berpikir dengan cermat sebelum melakukan apa pun yang mengganti keinginan pengguna untuk memperbesar.

visualViewport dapat digunakan untuk meningkatkan aksesibilitas. Misalnya, jika pengguna memperbesar, Anda dapat memilih untuk menyembunyikan item position: fixed dekoratif, agar tidak mengganggu pengguna. Namun, sekali lagi, berhati-hatilah agar Anda tidak menyembunyikan sesuatu yang ingin dilihat lebih dekat oleh pengguna.

Anda dapat mempertimbangkan untuk memposting ke layanan analisis saat pengguna memperbesar. Hal ini dapat membantu Anda mengidentifikasi halaman yang sulit diakses pengguna pada tingkat zoom default.

visualViewport.addEventListener('resize', () => {
    if (visualViewport.scale > 1) {
    // Post data to analytics service
    }
});

Selesai. visualViewport adalah API kecil yang bagus yang menyelesaikan masalah kompatibilitas di sepanjang jalan.