Pengantar
Situs paralaks sedang populer akhir-akhir ini, lihat saja contoh berikut:
- Old Pulteney Row to the Pole
- Adidas Snowboarding
- BBC News - James Bond: Cars, catchphrases and kisses
Jika Anda tidak terbiasa, situs ini adalah situs yang struktur visual halamannya berubah saat Anda men-scroll. Biasanya, elemen dalam skala halaman, diputar, atau dipindahkan secara proporsional ke posisi scroll di halaman.
Anda mungkin menyukai atau tidak menyukai situs dengan efek paralaks, tetapi yang dapat Anda katakan dengan cukup yakin adalah situs tersebut adalah lubang hitam performa. Alasannya adalah browser cenderung dioptimalkan untuk kasus saat konten baru muncul di bagian atas atau bawah layar saat Anda men-scroll (bergantung pada arah scroll Anda) dan, secara umum, browser berfungsi paling baik jika sangat sedikit perubahan secara visual selama scroll. Untuk situs paralaks, hal ini jarang terjadi karena sering kali elemen visual besar di seluruh halaman berubah, sehingga menyebabkan browser melakukan pengecatan ulang seluruh halaman.
Anda dapat membuat generalisasi situs paralaks seperti ini:
- Elemen latar belakang yang, saat Anda men-scroll ke atas dan ke bawah, mengubah posisi, rotasi, dan skalanya.
- Konten halaman, seperti teks atau gambar yang lebih kecil, yang di-scroll dengan cara biasa dari atas ke bawah.
Sebelumnya, kami telah membahas performa scroll dan cara meningkatkan responsivitas aplikasi. Artikel ini dibuat berdasarkan dasar-dasar tersebut, jadi sebaiknya baca artikel tersebut jika Anda belum melakukannya.
Jadi, pertanyaannya adalah jika Anda membuat situs scroll paralaks, apakah Anda terkunci dalam proses repaint yang mahal atau apakah ada pendekatan alternatif yang dapat Anda lakukan untuk memaksimalkan performa? Mari kita lihat opsi yang tersedia.
Opsi 1: Menggunakan elemen DOM dan posisi absolut
Ini tampaknya merupakan pendekatan default yang dilakukan sebagian besar orang. Ada banyak elemen dalam halaman, dan setiap kali peristiwa scroll diaktifkan, banyak pembaruan visual dilakukan untuk mengubahnya.
Jika Anda memulai Linimasa DevTools dalam mode frame dan men-scroll, Anda akan melihat bahwa ada operasi gambar layar penuh yang mahal, dan jika Anda banyak men-scroll, Anda mungkin melihat beberapa peristiwa scroll di dalam satu frame, yang masing-masing akan memicu pekerjaan tata letak.
Hal penting yang perlu diingat adalah untuk mencapai 60 fps (yang cocok dengan kecepatan refresh monitor standar 60 Hz), kita hanya memiliki waktu lebih dari 16 md untuk menyelesaikan semuanya. Dalam versi pertama ini, kita melakukan update visual setiap kali mendapatkan peristiwa scroll, tetapi seperti yang telah kita bahas dalam artikel sebelumnya tentang animasi yang lebih ramping dan lebih efektif dengan requestAnimationFrame dan performa scroll, hal ini tidak sesuai dengan jadwal update browser, sehingga kita melewatkan frame atau melakukan terlalu banyak pekerjaan di dalamnya. Hal ini dapat dengan mudah menyebabkan situs Anda terasa tidak wajar dan janggal, yang menyebabkan pengguna kecewa dan kucing tidak senang.
Mari kita pindahkan kode pembaruan dari peristiwa scroll ke callback requestAnimationFrame
dan cukup ambil nilai scroll di callback peristiwa scroll.
Jika mengulangi pengujian scroll, Anda mungkin akan melihat sedikit peningkatan, meskipun tidak banyak. Alasannya adalah operasi tata letak yang kita picu dengan men-scroll tidak terlalu mahal, tetapi dalam kasus penggunaan lainnya, operasi tersebut benar-benar mahal. Sekarang setidaknya kita hanya melakukan operasi satu tata letak di setiap frame.
Sekarang kita dapat menangani satu atau seratus peristiwa scroll per frame, tetapi yang terpenting, kita hanya menyimpan nilai terbaru untuk digunakan setiap kali callback requestAnimationFrame
berjalan dan melakukan update visual. Intinya, Anda telah beralih dari mencoba memaksa pembaruan visual setiap kali menerima peristiwa scroll ke meminta browser memberi Anda periode yang sesuai untuk melakukannya. Kamu manis sekali.
Masalah utama dengan pendekatan ini, requestAnimationFrame
atau tidak, adalah kita pada dasarnya memiliki satu lapisan untuk seluruh halaman, dan dengan memindahkan elemen visual ini, kita memerlukan proses repaint yang besar (dan mahal). Biasanya, proses menggambar adalah operasi pemblokiran (meskipun hal itu berubah), yang berarti browser tidak dapat melakukan pekerjaan lain dan kita sering kali melampaui anggaran frame sebesar 16 md dan semuanya tetap lambat.
Opsi 2: Menggunakan elemen DOM dan transformasi 3D
Daripada menggunakan posisi absolut, pendekatan lain yang dapat kita lakukan adalah menerapkan transformasi 3D ke elemen. Dalam situasi ini, kita melihat bahwa elemen dengan transformasi 3D yang diterapkan diberi lapisan baru per elemen dan, di browser WebKit, hal ini sering kali juga menyebabkan peralihan ke kompositor hardware. Sebaliknya, dalam Opsi 1, kita memiliki satu lapisan besar untuk halaman yang perlu digambar ulang saat ada perubahan dan semua proses menggambar dan pengomposisian ditangani oleh CPU.
Artinya, dengan opsi ini, semuanya berbeda: kita berpotensi memiliki satu lapisan untuk setiap elemen yang kita terapkan transformasi 3D. Jika semua yang kita lakukan dari titik ini adalah lebih banyak transformasi pada elemen, kita tidak perlu mengecat ulang lapisan, dan GPU dapat menangani pemindahan elemen dan penggabungan halaman akhir.
Sering kali orang hanya menggunakan hack -webkit-transform: translateZ(0);
dan melihat peningkatan performa yang luar biasa, dan meskipun ini berfungsi saat ini, ada masalah:
- Fitur ini tidak kompatibel lintas browser.
- Hal ini memaksa browser dengan membuat lapisan baru untuk setiap elemen yang ditransformasi. Banyak lapisan dapat menyebabkan bottleneck performa lainnya, jadi gunakan seperlunya.
- Fitur ini telah dinonaktifkan untuk beberapa port WebKit (butir keempat dari bawah).
Jika Anda menggunakan terjemahan 3D, berhati-hatilah karena ini adalah solusi sementara untuk masalah Anda. Idealnya, kita akan melihat karakteristik rendering yang serupa dari transformasi 2D seperti yang kita lakukan dengan 3D. Browser berkembang dengan kecepatan yang fenomenal, jadi semoga sebelum itu kita akan melihatnya.
Terakhir, Anda harus berusaha menghindari paint di mana pun Anda bisa dan cukup memindahkan elemen yang ada di sekitar halaman. Sebagai contoh, pendekatan umum di situs paralaks adalah menggunakan div dengan tinggi tetap dan mengubah posisi latar belakangnya untuk memberikan efek. Sayangnya, hal ini berarti elemen perlu dicat ulang pada setiap penerusan, yang dapat merugikan Anda dalam hal performa. Sebagai gantinya, jika memungkinkan, Anda harus membuat elemen (gabungkan di dalam div dengan overflow: hidden
jika perlu) dan cukup menerjemahkannya.
Opsi 3: Menggunakan kanvas posisi tetap atau WebGL
Opsi terakhir yang akan kita pertimbangkan adalah menggunakan kanvas posisi tetap di bagian belakang halaman tempat kita akan menggambar gambar yang ditransformasi. Sekilas, hal ini mungkin tidak tampak seperti solusi dengan performa terbaik, tetapi sebenarnya ada beberapa manfaat dari pendekatan ini:
- Kita tidak lagi memerlukan banyak pekerjaan kompositor karena hanya memiliki satu elemen, kanvas.
- Kita secara efektif menangani satu bitmap yang diakselerasi hardware.
- Canvas2D API sangat cocok untuk jenis transformasi yang ingin kita lakukan, yang berarti pengembangan dan pemeliharaan lebih mudah dikelola.
Menggunakan elemen kanvas memberi kita lapisan baru, tetapi hanya satu lapisan, sedangkan dalam Opsi 2, kita sebenarnya diberi lapisan baru untuk setiap elemen dengan transformasi 3D yang diterapkan, sehingga kita memiliki peningkatan beban kerja yang menggabungkan semua lapisan tersebut. Ini juga merupakan solusi yang paling kompatibel saat ini mengingat implementasi transformasi lintas browser yang berbeda.
/**
* Updates and draws in the underlying visual elements to the canvas.
*/
function updateElements () {
var relativeY = lastScrollY / h;
// Fill the canvas up
context.fillStyle = "#1e2124";
context.fillRect(0, 0, canvas.width, canvas.height);
// Draw the background
context.drawImage(bg, 0, pos(0, -3600, relativeY, 0));
// Draw each of the blobs in turn
context.drawImage(blob1, 484, pos(254, -4400, relativeY, 0));
context.drawImage(blob2, 84, pos(954, -5400, relativeY, 0));
context.drawImage(blob3, 584, pos(1054, -3900, relativeY, 0));
context.drawImage(blob4, 44, pos(1400, -6900, relativeY, 0));
context.drawImage(blob5, -40, pos(1730, -5900, relativeY, 0));
context.drawImage(blob6, 325, pos(2860, -7900, relativeY, 0));
context.drawImage(blob7, 725, pos(2550, -4900, relativeY, 0));
context.drawImage(blob8, 570, pos(2300, -3700, relativeY, 0));
context.drawImage(blob9, 640, pos(3700, -9000, relativeY, 0));
// Allow another rAF call to be scheduled
ticking = false;
}
/**
* Calculates a relative disposition given the page's scroll
* range normalized from 0 to 1
* @param {number} base The starting value.
* @param {number} range The amount of pixels it can move.
* @param {number} relY The normalized scroll value.
* @param {number} offset A base normalized value from which to start the scroll behavior.
* @returns {number} The updated position value.
*/
function pos(base, range, relY, offset) {
return base + limit(0, 1, relY - offset) * range;
}
/**
* Clamps a number to a range.
* @param {number} min The minimum value.
* @param {number} max The maximum value.
* @param {number} value The value to limit.
* @returns {number} The clamped value.
*/
function limit(min, max, value) {
return Math.max(min, Math.min(max, value));
}
Pendekatan ini sangat cocok jika Anda menangani gambar besar (atau elemen lain yang dapat ditulis dengan mudah ke kanvas), dan tentu saja menangani blok teks besar akan lebih menantang, tetapi bergantung pada situs Anda, pendekatan ini mungkin terbukti menjadi solusi yang paling sesuai. Jika harus menangani teks di kanvas, Anda harus menggunakan metode fillText
API, tetapi dengan mengorbankan aksesibilitas (Anda baru saja merasterisasi teks menjadi bitmap!) dan sekarang Anda harus menangani penggabungan baris dan banyak masalah lainnya. Jika dapat dihindari, sebaiknya Anda tidak melakukannya, dan Anda mungkin akan lebih baik menggunakan pendekatan transformasi di atas.
Karena kita melakukan ini sejauh mungkin, tidak ada alasan untuk menganggap bahwa pekerjaan paralaks harus dilakukan di dalam elemen kanvas. Jika browser mendukungnya, kita dapat menggunakan WebGL. Kuncinya di sini adalah WebGL memiliki rute paling langsung dari semua API ke kartu grafis dan, dengan demikian, merupakan kandidat yang paling mungkin untuk mencapai 60 fps, terutama jika efek situsnya rumit.
Reaksi Anda mungkin langsung menganggap WebGL berlebihan, atau tidak tersedia di mana-mana dalam hal dukungan, tetapi jika Anda menggunakan sesuatu seperti Three.js, Anda selalu dapat kembali menggunakan elemen kanvas dan kode Anda akan di-abstrak dengan cara yang konsisten dan mudah. Yang perlu kita lakukan adalah menggunakan Modernizr untuk memeriksa dukungan API yang sesuai:
// check for WebGL support, otherwise switch to canvas
if (Modernizr.webgl) {
renderer = new THREE.WebGLRenderer();
} else if (Modernizr.canvas) {
renderer = new THREE.CanvasRenderer();
}
Sebagai pemikiran akhir tentang pendekatan ini, jika Anda tidak terlalu suka menambahkan elemen tambahan ke halaman, Anda selalu dapat menggunakan kanvas sebagai elemen latar belakang di Firefox dan browser berbasis WebKit. Tentu saja, hal ini tidak umum, jadi seperti biasa, Anda harus memperlakukannya dengan hati-hati.
Pilihan ada di tangan Anda
Alasan utama developer menggunakan elemen yang diposisikan secara absolut secara default, bukan opsi lainnya, mungkin karena dukungannya yang tersedia di mana-mana. Hal ini, sampai batas tertentu, merupakan ilusi, karena browser lama yang ditargetkan cenderung memberikan pengalaman rendering yang sangat buruk. Bahkan di browser modern saat ini, penggunaan elemen yang diposisikan secara mutlak tidak selalu menghasilkan performa yang baik.
Transformasi, terutama jenis 3D, menawarkan kemampuan untuk bekerja langsung dengan elemen DOM dan mencapai kecepatan frame yang solid. Kunci suksesnya adalah menghindari proses paint jika memungkinkan dan cukup coba pindahkan elemen. Perlu diingat bahwa cara browser WebKit membuat lapisan tidak selalu berkorelasi dengan mesin browser lainnya, jadi pastikan untuk mengujinya sebelum berkomitmen pada solusi tersebut.
Jika Anda hanya menargetkan browser tingkat atas, dan dapat merender situs menggunakan kanvas, opsi ini mungkin yang terbaik untuk Anda. Tentu saja, jika menggunakan Three.js, Anda akan dapat menukar dan mengubah antara perender dengan sangat mudah, bergantung pada dukungan yang Anda perlukan.
Kesimpulan
Kami telah menilai beberapa pendekatan untuk menangani situs paralaks, mulai dari elemen yang diposisikan secara mutlak hingga menggunakan kanvas posisi tetap. Implementasi yang Anda lakukan tentu saja akan bergantung pada hal yang ingin Anda capai dan desain spesifik yang Anda gunakan, tetapi selalu baik untuk mengetahui bahwa Anda memiliki opsi.
Dan seperti biasa, apa pun pendekatan yang Anda coba: jangan menebak, uji.