Paralaksin'

Pengantar

Baru-baru ini, situs paralaks sedang populer, lihatlah:

Jika Anda tidak familier dengannya, situs ini adalah situs tempat struktur visual halaman berubah saat Anda men-scroll. Biasanya elemen dalam skala halaman, putar atau pindahkan secara proporsional dengan posisi scroll di halaman.

Halaman paralaks demo
Halaman demo kami lengkap dengan efek paralaks

Suka atau tidak menyukai situs paralaks adalah satu hal, tetapi yang dapat Anda katakan dengan cukup yakin adalah bahwa situs tersebut adalah lubang hitam dalam kinerja. Alasannya adalah karena browser cenderung dioptimalkan apabila 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 ketika sangat sedikit perubahan secara visual selama scroll. Untuk situs paralaks yang jarang terjadi karena elemen visual yang besar di seluruh halaman berubah beberapa kali, menyebabkan browser melakukan penggambaran ulang seluruh halaman.

Masuk akal untuk menggeneralisasi situs paralaks seperti ini:

  • Elemen latar belakang yang, saat Anda men-scroll ke atas dan ke bawah, akan mengubah posisi, rotasi, dan skalanya.
  • Konten halaman, seperti teks atau gambar yang lebih kecil, yang di-scroll dengan gaya dari atas ke bawah yang biasa.

Sebelumnya kita membahas performa scroll dan cara yang dapat Anda lakukan untuk meningkatkan responsivitas aplikasi, dan artikel ini dibangun berdasarkan fondasi tersebut, jadi ada baiknya Anda membacanya jika Anda belum melakukannya.

Jadi pertanyaannya adalah jika Anda membangun situs scroll paralaks, apakah Anda terkunci dalam penggambaran ulang yang mahal atau apakah ada pendekatan alternatif yang dapat Anda ambil untuk memaksimalkan performa? Mari kita lihat opsi kami.

Opsi 1: Gunakan elemen DOM dan posisi absolut

Tampaknya ini merupakan pendekatan default yang dilakukan oleh sebagian besar orang. Ada banyak elemen dalam halaman, dan setiap kali peristiwa scroll diaktifkan, banyak pembaruan visual akan dilakukan untuk mengubahnya.

Jika Anda memulai DevTools Timeline dalam mode bingkai dan menggulir di sekitarnya, Anda akan melihat bahwa ada operasi paint layar penuh yang mahal, dan jika Anda sering men-scroll, Anda mungkin melihat beberapa peristiwa scroll di dalam satu bingkai, yang masing-masing akan memicu pekerjaan tata letak.

Chrome DevTools tanpa peristiwa scroll yang di-debounce.
DevTools menampilkan paint besar dan beberapa tata letak yang dipicu peristiwa dalam satu frame.

Hal penting yang perlu diingat adalah untuk mencapai 60 fps (cocok dengan kecepatan refresh monitor pada umumnya 60 Hz), kita memiliki lebih dari 16 md untuk menyelesaikan semuanya. Dalam versi pertama ini, kami melakukan pembaruan visual setiap kali ada peristiwa scroll, tetapi seperti yang telah kami bahas di artikel sebelumnya tentang animasi yang lebih ramping dan lebih jahat dengan requestAnimationFrame dan performa scroll, hal ini tidak bertepatan dengan jadwal update browser, sehingga kami kehilangan frame atau melakukan terlalu banyak pekerjaan di dalamnya. Hal itu bisa dengan mudah menimbulkan rasa tersendat dan tidak wajar pada situs Anda, yang menyebabkan pengguna kecewa dan anak kucing yang tidak bahagia.

Mari kita pindahkan kode update dari peristiwa scroll ke callback requestAnimationFrame dan cukup tangkap 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 semahal itu, tetapi dalam kasus penggunaan lain, bisa jadi demikian. Sekarang kita hanya melakukan operasi satu tata letak di setiap frame.

Chrome DevTools dengan peristiwa scroll debounce.
DevTools menampilkan paint besar dan beberapa tata letak yang dipicu peristiwa dalam satu frame.

Sekarang kita dapat menangani 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 adalah Anda telah beralih dari mencoba memaksa pembaruan visual setiap kali Anda menerima peristiwa scroll ke meminta agar browser memberi Anda jendela yang sesuai untuk melakukannya. Keren ya?

Masalah utama dengan pendekatan ini, requestAnimationFrame atau tidak, adalah pada dasarnya kita memiliki satu lapisan untuk seluruh halaman, dan dengan memindahkan elemen visual ini, kita memerlukan penggambaran ulang yang besar (dan mahal). Biasanya, lukisan tersebut merupakan operasi pemblokiran (meskipun berubah), artinya browser tidak dapat melakukan pekerjaan lain dan kita sering berjalan melebihi anggaran frame, yaitu 16 md, dan segalanya tetap tersendat.

Opsi 2: Gunakan elemen DOM dan transformasi 3D

Daripada menggunakan posisi absolut, kita dapat mengambil pendekatan lain adalah dengan menerapkan transformasi 3D pada elemen. Dalam situasi ini, kita melihat bahwa elemen dengan transformasi 3D yang diterapkan diberi lapisan baru per elemen dan, dalam browser WebKit, hal itu sering kali juga menyebabkan peralihan ke compositor perangkat keras. Sebaliknya, di Opsi 1, kami memiliki satu lapisan besar untuk halaman yang perlu dicat ulang ketika ada yang berubah dan semua proses menggambar dan mengomposisikan ditangani oleh CPU.

Artinya, dengan opsi ini, semuanya akan berbeda: kita berpotensi memiliki satu lapisan untuk setiap elemen tempat kita menerapkan transformasi 3D. Jika yang kita lakukan dari titik ini adalah lebih banyak transformasi pada elemen, kita tidak perlu melakukan penggambaran ulang lapisan, dan GPU dapat menangani pemindahan elemen dan pengomposisian bersama laman akhir.

Sering kali orang hanya menggunakan peretasan -webkit-transform: translateZ(0); dan melihat peningkatan performa yang ajaib, dan meskipun cara ini berhasil saat ini, terdapat masalah:

  1. Tidak kompatibel dengan lintas browser.
  2. Ini memaksa tangan browser dengan membuat lapisan baru untuk setiap elemen yang diubah. Banyak lapisan dapat menyebabkan bottleneck kinerja lainnya, jadi gunakanlah dengan hemat!
  3. Layanan ini telah dinonaktifkan untuk beberapa port WebKit (butir keempat dari bawah).

Jika Anda menggunakan rute terjemahan 3D dengan hati-hati, ini adalah solusi sementara untuk masalah Anda! Idealnya, kita akan melihat karakteristik rendering yang serupa dari transformasi 2D seperti halnya dengan 3D. Browser mengalami kemajuan dengan tingkat yang fenomenal, jadi semoga sebelum hal itu yang akan kita lihat.

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, ini berarti elemen tersebut perlu di-rekonstruksi pada setiap pass, yang dapat merugikan performa Anda. Sebagai gantinya, jika bisa, Anda harus membuat elemen (menggabungkannya 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 dengan posisi tetap di bagian belakang halaman tempat kita akan menggambar gambar yang telah diubah. Secara sekilas, hal itu mungkin tidak tampak seperti solusi yang paling efektif, tetapi sebenarnya ada beberapa manfaat dari pendekatan ini:

  • Kita tidak lagi memerlukan sebanyak mungkin pekerjaan compositor karena hanya memiliki satu elemen, kanvas.
  • Kita secara efektif menangani satu bitmap yang diakselerasi oleh hardware.
  • Canvas2D API sangat cocok untuk jenis transformasi yang ingin kami lakukan, yang berarti pengembangan dan pemeliharaan lebih mudah dikelola.

Menggunakan elemen kanvas memberi kita lapisan baru, tetapi hanya satu lapisan, sedangkan di Opsi 2 kami sebenarnya diberi lapisan baru untuk setiap elemen dengan transformasi 3D, jadi kami mengalami peningkatan beban kerja yang menggabungkan semua lapisan tersebut bersama-sama. Solusi ini juga merupakan solusi yang paling kompatibel saat ini sehubungan dengan 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 benar-benar berhasil ketika Anda menangani gambar besar (atau elemen lain yang dapat dengan mudah ditulis ke dalam kanvas), dan tentu saja berurusan dengan blok teks yang besar akan lebih sulit, tetapi bergantung pada situs Anda, cara ini mungkin menjadi solusi yang paling tepat. Jika Anda harus berurusan dengan teks di kanvas, Anda harus menggunakan metode API fillText, tetapi hal ini membebani aksesibilitas (Anda cukup meraster teks menjadi bitmap!) dan Anda sekarang harus berurusan dengan pengemasan baris dan banyak masalah lainnya. Jika Anda dapat menghindarinya, Anda harus melakukannya, dan kemungkinan akan lebih baik jika Anda menggunakan pendekatan transformasi di atas.

Melihat kita melakukan ini sejauh mungkin, tidak ada alasan untuk menganggap bahwa pekerjaan paralaks harus dilakukan di dalam elemen kanvas. Jika browser mendukungnya, kita bisa 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 situs tersebut kompleks.

Reaksi langsung Anda mungkin berupa WebGL berlebihan, atau dukungannya tidak ada di mana-mana, tetapi jika Anda menggunakan sesuatu seperti Three.js, Anda selalu dapat kembali menggunakan elemen kanvas dan kode Anda diabstraksi secara 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 pertimbangan terakhir untuk pendekatan ini, jika Anda tidak begitu suka menambahkan elemen ekstra ke halaman, Anda selalu dapat menggunakan kanvas sebagai elemen latar belakang di browser berbasis Firefox dan WebKit. Hal ini jelas terjadi di mana-mana, jadi seperti biasa, Anda harus menanganinya dengan hati-hati.

Anda bebas untuk memilih

Alasan utama developer menetapkan default ke elemen yang benar-benar diposisikan daripada opsi lain mungkin hanya dukungan di mana-mana. Hal ini, pada tingkatan tertentu, merupakan ilusi, karena browser lama yang ditargetkan cenderung memberikan pengalaman rendering yang sangat buruk. Bahkan, browser modern masa kini yang menggunakan elemen yang diposisikan secara mutlak tidak selalu menghasilkan performa yang baik.

Transformasi, tentu saja jenis 3D, menawarkan kemampuan untuk bekerja secara langsung dengan elemen DOM dan mencapai kecepatan frame yang solid. Kunci keberhasilan dalam hal ini adalah menghindari mengecat di mana pun Anda bisa dan cukup coba pindahkan elemennya. Perlu diingat bahwa cara browser WebKit membuat layer tidak selalu berkorelasi dengan mesin browser lainnya, jadi pastikan untuk mengujinya sebelum menggunakan solusi tersebut.

Jika Anda hanya menargetkan browser tingkat teratas, dan dapat merender situs menggunakan kanvas, opsi ini mungkin opsi terbaik untuk Anda. Tentu saja, jika Anda menggunakan Three.js, Anda akan dapat beralih dan berganti antar-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 apa yang ingin Anda capai dan desain spesifik yang Anda kerjakan, tetapi ada baiknya jika Anda memiliki banyak opsi.

Dan seperti biasa, pendekatan mana pun yang Anda coba: jangan menebaknya, ujilah.