Memperkenalkan visualViewport

Jake Archibald
Jake Archibald

Bagaimana jika saya katakan, ada lebih dari satu area pandang.

BRRRRAAAAAAAMMMMMMMMMM

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

BRRRRAAAAAAAMMMMMMMMMM

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

BRRRRAAAAM... tunggu apa?

Benar, lihat:

Area pandang tata letak vs area pandang visual

Video di atas menampilkan halaman web yang di-scroll dan di-zoom, bersama dengan peta mini di sebelah kanan yang menampilkan posisi area pandang dalam halaman.

Semuanya berjalan cukup lancar selama scrolling reguler. Area hijau mewakili area pandang tata letak, tempat item position: fixed melekat.

Keadaan menjadi aneh ketika fungsi cubit-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 melekat pada area pandang tata letak. Jika kita menggeser pada batas area pandang tata letak, kita akan menyeret area pandang tata letak bersamanya.

Meningkatkan kompatibilitas

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

Misalnya, element.getBoundingClientRect().y menampilkan offset dalam area pandang tata letak. Memang keren, tetapi kita sering menginginkan posisi di 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 rusak saat pengguna melakukan gerakan cubit-zoom.

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

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

Inilah 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 gerakan cubit. Jika ukuran konten dua kali lipat karena di-zoom, 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 agar selalu muncul ukuran yang sama, meskipun dilakukan gerakan cubit.

Gotcha

Peristiwa hanya diaktifkan saat area pandang visual berubah

Rasanya seperti hal yang sudah jelas untuk dinyatakan, tetapi hal itu tidak jelas terlihat saat saya pertama kali bermain dengan visualViewport.

Jika area pandang tata letak berubah ukuran, tetapi area pandang visual tidak, Anda tidak akan mendapatkan peristiwa resize. Namun, tidak biasa jika area pandang tata letak berubah ukuran tanpa area pandang visual yang juga mengubah lebar/tinggi.

Gotcha yang sesungguhnya adalah men-scroll. Jika scroll terjadi, tetapi area pandang visual tetap statis 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 di kiri atas area pandang tata letak, sehingga scroll tidak diaktifkan pada 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

Mirip dengan memproses scroll & resize di jendela, Anda mungkin akan memanggil semacam fungsi "update" sebagai hasilnya. Namun, hal ini umum terjadi pada waktu yang bersamaan. Jika pengguna mengubah ukuran jendela, jendela akan memicu resize, tetapi sering kali scroll juga. 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 sepertinya ada cara yang lebih baik, misalnya satu peristiwa update.

Pengendali peristiwa tidak berfungsi

Karena bug Chrome, hal berikut tidak berfungsi:

Larangan

Buggy – menggunakan pengendali peristiwa

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

Sebagai gantinya:

Anjuran

Berfungsi – menggunakan pemroses peristiwa

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

Nilai offset dibulatkan

Sepertinya (baik, semoga) ini adalah bug Chrome lainnya.

offsetLeft dan offsetTop dibulatkan, yang cukup tidak akurat setelah pengguna memperbesarnya. Anda dapat melihat masalahnya selama demo – jika pengguna memperbesar dan menggeser perlahan, peta mini akan bergeser di antara piksel yang tidak diperbesar.

Rasio peristiwa lambat

Seperti peristiwa resize dan scroll lainnya, peristiwa ini tidak mengaktifkan setiap frame, terutama di perangkat seluler. Anda dapat melihatnya selama demo – setelah Anda mencubit zoom, peta mini kesulitan untuk tetap terkunci di area pandang.

Aksesibilitas

Dalam demo saya menggunakan visualViewport untuk menentang tindakan cubit-zoom pengguna. Demo ini memang masuk akal, tetapi Anda harus berpikir dengan hati-hati sebelum melakukan apa pun yang menggantikan keinginan pengguna untuk memperbesar.

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

Anda dapat mempertimbangkan untuk memposting ke layanan analisis saat pengguna memperbesarnya. Metode 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
    }
});

Dan selesai! visualViewport adalah API kecil yang bagus yang memecahkan masalah kompatibilitas selama proses berlangsung.