Paralaks Berperforma Tinggi

Robert Flack
Robert Flack

Suka atau tidak, paralaks akan tetap ada. Jika digunakan dengan bijak, hal ini dapat menambah kedalaman dan kehalusan aplikasi web. Namun, masalahnya, menerapkan paralaks dengan cara yang baik dapat menjadi tantangan tersendiri. Dalam artikel ini, kami akan membahas solusi yang berperforma tinggi dan, sama pentingnya, juga berfungsi lintas browser.

Ilustrasi Paralaks.

TL;DR

  • Jangan gunakan peristiwa scroll atau background-position untuk membuat animasi paralaks.
  • Gunakan transformasi 3D CSS untuk membuat efek paralaks yang lebih akurat.
  • Untuk Mobile Safari, gunakan position: sticky untuk memastikan efek paralaks disebarkan.

Jika Anda menginginkan solusi siap pakai, buka repo GitHub Contoh Elemen UI dan dapatkan JS helper Parallax. Anda dapat melihat demo langsung scroll parallax di repo GitHub.

Paralaks masalah

Untuk memulai, mari kita lihat dua cara umum untuk mendapatkan efek paralaks, dan khususnya, alasan keduanya tidak sesuai untuk tujuan kita.

Buruk: menggunakan peristiwa scroll

Persyaratan utama paralaks adalah harus dikaitkan dengan scroll; untuk setiap perubahan pada posisi scroll halaman, posisi elemen paralaks harus diperbarui. Meskipun terdengar sederhana, mekanisme penting browser modern adalah kemampuannya untuk bekerja secara asinkron. Dalam kasus tertentu, hal ini berlaku untuk peristiwa scroll. Di sebagian besar browser, peristiwa scroll dikirim sebagai "upaya terbaik" dan tidak dijamin akan dikirim di setiap frame animasi scroll.

Informasi penting ini memberi tahu kita mengapa kita perlu menghindari solusi berbasis JavaScript yang memindahkan elemen berdasarkan peristiwa scroll: JavaScript tidak menjamin bahwa paralaks akan terus selaras dengan posisi scroll halaman. Di Mobile Safari versi lama, peristiwa scroll sebenarnya dikirimkan di akhir scroll, sehingga tidak memungkinkan untuk membuat efek scroll berbasis JavaScript. Versi yang lebih baru mengirimkan peristiwa scroll selama animasi, tetapi, sama seperti Chrome, berdasarkan "upaya terbaik". Jika thread utama sibuk dengan tugas lain, peristiwa scroll tidak akan segera dikirim, yang berarti efek paralaks akan hilang.

Buruk: mengupdate background-position

Situasi lain yang ingin kita hindari adalah menggambar pada setiap frame. Banyak solusi yang mencoba mengubah background-position untuk memberikan tampilan paralaks, yang menyebabkan browser menggambar ulang bagian halaman yang terpengaruh saat men-scroll, dan hal itu dapat cukup mahal untuk menyebabkan jank secara signifikan pada animasi.

Jika ingin memenuhi janji gerakan paralaks, kita menginginkan sesuatu yang dapat diterapkan sebagai properti yang dipercepat (yang saat ini berarti tetap menggunakan transformasi dan opasitas), dan yang tidak bergantung pada peristiwa scroll.

CSS dalam 3D

Scott Kellum dan Keith Clark telah melakukan pekerjaan yang signifikan di bidang penggunaan CSS 3D untuk mencapai gerakan paralaks, dan teknik yang mereka gunakan secara efektif adalah ini:

  • Siapkan elemen penampung untuk di-scroll dengan overflow-y: scroll (dan mungkin overflow-x: hidden).
  • Untuk elemen yang sama, terapkan nilai perspective, dan perspective-origin ditetapkan ke top left, atau 0 0.
  • Untuk turunan elemen tersebut, terapkan terjemahan dalam Z, dan skalakan kembali untuk memberikan gerakan paralaks tanpa memengaruhi ukurannya di layar.

CSS untuk pendekatan ini terlihat seperti ini:

.container {
  width: 100%;
  height: 100%;
  overflow-x: hidden;
  overflow-y: scroll;
  perspective: 1px;
  perspective-origin: 0 0;
}

.parallax-child {
  transform-origin: 0 0;
  transform: translateZ(-2px) scale(3);
}

Yang mengasumsikan cuplikan HTML seperti ini:

<div class="container">
    <div class="parallax-child"></div>
</div>

Menyesuaikan skala untuk perspektif

Mendorong elemen turunan kembali akan menyebabkannya menjadi lebih kecil secara proporsional dengan nilai perspektif. Anda dapat menghitung berapa banyak yang perlu ditingkatkan skalanya dengan persamaan ini: (perspektif - jarak) / perspektif. Karena kemungkinan besar kita ingin elemen paralaks menjadi paralaks, tetapi ditampilkan sesuai ukuran yang kita tulis, elemen tersebut perlu ditingkatkan skalanya dengan cara ini, bukan dibiarkan apa adanya.

Dalam kasus kode di atas, perspektifnya adalah 1px, dan jarak Z parallax-child adalah -2px. Artinya, elemen harus disetel skalanya sebesar 3x, yang dapat Anda lihat adalah nilai yang dimasukkan ke dalam kode: scale(3).

Untuk konten apa pun yang tidak menerapkan nilai translateZ, Anda dapat mengganti nilai nol. Artinya, skalanya adalah (perspektif - 0) / perspektif, yang menghasilkan nilai 1, yang berarti skalanya tidak ditingkatkan atau diturunkan. Cukup praktis.

Cara kerja pendekatan ini

Penting untuk mengetahui alasan hal ini berhasil, karena kita akan segera menggunakan pengetahuan tersebut. Scrolling secara efektif adalah transformasi, sehingga dapat diakselerasi; sebagian besar melibatkan pergeseran lapisan dengan GPU. Dalam scroll biasa, yang merupakan scroll tanpa konsep perspektif, scroll terjadi dengan cara 1:1 saat membandingkan elemen scroll dan turunannya. Jika Anda men-scroll elemen ke bawah sebesar 300px, turunannya akan ditransformasikan ke atas dengan jumlah yang sama: 300px.

Namun, menerapkan nilai perspektif ke elemen scroll akan mengacaukan proses ini; hal ini akan mengubah matriks yang mendukung transformasi scroll. Sekarang scroll 300 px hanya dapat memindahkan turunannya 150 px, bergantung pada nilai perspective dan translateZ yang Anda pilih. Jika elemen memiliki nilai translateZ 0, elemen tersebut akan di-scroll dengan kecepatan 1:1 (seperti biasa), tetapi turunan yang didorong dalam Z dari asal perspektif akan di-scroll dengan kecepatan yang berbeda. Hasil bersih: gerakan paralaks. Dan, yang sangat penting, hal ini ditangani sebagai bagian dari mesin scroll internal browser secara otomatis, yang berarti tidak perlu memproses peristiwa scroll atau mengubah background-position.

Lalat yang tergoda: Mobile Safari

Ada batasan untuk setiap efek, dan salah satu yang penting untuk transformasi adalah pelestarian efek 3D ke elemen turunan. Jika ada elemen dalam hierarki antara elemen dengan perspektif dan turunan paralaksnya, perspektif 3D akan "diratakan", yang berarti efeknya hilang.

<div class="container">
    <div class="parallax-container">
    <div class="parallax-child"></div>
    </div>
</div>

Dalam HTML di atas, .parallax-container adalah baru, dan akan secara efektif meratakan nilai perspective dan kita kehilangan efek paralaks. Solusi, dalam sebagian besar kasus, cukup mudah: Anda menambahkan transform-style: preserve-3d ke elemen, sehingga elemen tersebut menyebarkan efek 3D apa pun (seperti nilai perspektif kami) yang telah diterapkan lebih jauh ke atas hierarki.

.parallax-container {
  transform-style: preserve-3d;
}

Namun, dalam kasus Mobile Safari, semuanya sedikit lebih rumit. Secara teknis, menerapkan overflow-y: scroll ke elemen penampung akan berfungsi, tetapi dengan biaya yang dapat digunakan untuk melemparkan elemen scroll. Solusinya adalah menambahkan -webkit-overflow-scrolling: touch, tetapi hal ini juga akan meratakan perspective dan kita tidak akan mendapatkan paralaks.

Dari sudut pandang progressive enhancement, hal ini mungkin tidak terlalu menjadi masalah. Jika kita tidak dapat menggunakan paralaks dalam setiap situasi, aplikasi kita akan tetap berfungsi, tetapi akan lebih baik jika kita menemukan solusinya.

position: sticky menolong!

Sebenarnya, ada beberapa bantuan dalam bentuk position: sticky, yang ada untuk memungkinkan elemen "melekat" ke bagian atas area pandang atau elemen induk tertentu selama scroll. Spesifikasinya, seperti sebagian besar spesifikasi, cukup berat, tetapi berisi permata kecil yang berguna:

Ini mungkin tidak tampak penting pada pandangan pertama, tetapi poin utama dalam kalimat tersebut adalah saat merujuk ke cara persis penghitungan kemelekatan elemen: "offset dihitung dengan referensi ke ancestor terdekat dengan kotak scroll". Dengan kata lain, jarak untuk memindahkan elemen melekat (agar muncul terpasang ke elemen lain atau area pandang) dihitung sebelum transformasi lain diterapkan, bukan setelah. Artinya, seperti contoh scroll sebelumnya, jika offset dihitung pada 300 piksel, ada peluang baru untuk menggunakan perspektif (atau transformasi lainnya) untuk memanipulasi nilai offset 300 piksel tersebut sebelum diterapkan ke elemen melekat.

Dengan menerapkan position: -webkit-sticky ke elemen paralaks, kita dapat "membalikan" efek perataan -webkit-overflow-scrolling: touch secara efektif. Hal ini memastikan bahwa elemen paralaks mereferensikan ancestor terdekat dengan kotak scroll, yang dalam hal ini adalah .container. Kemudian, mirip dengan sebelumnya, .parallax-container menerapkan nilai perspective, yang mengubah offset scroll yang dihitung dan membuat efek paralaks.

<div class="container">
    <div class="parallax-container">
    <div class="parallax-child"></div>
    </div>
</div>
.container {
  overflow-y: scroll;
  -webkit-overflow-scrolling: touch;
}

.parallax-container {
  perspective: 1px;
}

.parallax-child {
  position: -webkit-sticky;
  top: 0px;
  transform: translate(-2px) scale(3);
}

Tindakan ini akan memulihkan efek paralaks untuk Mobile Safari, yang tentu saja merupakan berita yang sangat baik!

Peringatan penempatan melekat

Namun, ada perbedaan di sini: position: sticky memang mengubah mekanika paralaks. Pemosisian melekat mencoba menempelkan elemen ke container scroll, sedangkan versi yang tidak melekat tidak. Ini berarti bahwa parallax dengan sticky akan menjadi kebalikan dari parallax tanpa:

  • Dengan position: sticky, semakin dekat elemen ke z=0 less yang dipindahkannya.
  • Tanpa position: sticky, semakin dekat elemen ke z=0, semakin elemen tersebut bergerak.

Jika semua tampak agak abstrak, lihat demo ini oleh Robert Flack, yang menunjukkan perilaku berbeda dengan dan tanpa penentuan posisi yang melekat. Untuk melihat perbedaannya, Anda memerlukan Chrome Canary (yang merupakan versi 56 saat artikel ini ditulis) atau Safari.

Screenshot perspektif paralaks

Demo oleh Robert Flack yang menunjukkan pengaruh position: sticky terhadap scroll paralaks.

Berbagai macam bug dan solusi

Namun, seperti halnya hal lainnya, masih ada benjolan dan tonjolan yang perlu dihaluskan:

  • Dukungan sticky tidak konsisten. Dukungan masih diimplementasikan di Chrome, Edge tidak memiliki dukungan sama sekali, dan Firefox memiliki bug gambar saat sticky digabungkan dengan transformasi perspektif. Dalam kasus tersebut, sebaiknya tambahkan sedikit kode untuk hanya menambahkan position: sticky (versi dengan awalan -webkit-) jika diperlukan, yang hanya untuk Safari Seluler.
  • Efeknya tidak "langsung berfungsi" di Edge. Edge mencoba menangani scroll di tingkat OS, yang umumnya merupakan hal yang baik, tetapi dalam hal ini, Edge mencegahnya mendeteksi perubahan perspektif selama scroll. Untuk memperbaikinya, Anda dapat menambahkan elemen posisi tetap, karena tindakan ini akan mengalihkan Edge ke metode scroll non-OS, dan memastikan bahwa elemen tersebut memperhitungkan perubahan perspektif.
  • "Konten halaman menjadi sangat besar!" Banyak browser yang memperhitungkan skala saat menentukan ukuran konten halaman, tetapi sayangnya Chrome dan Safari tidak memperhitungkan perspektif. Jadi, jika ada - misalnya - skala 3x yang diterapkan ke elemen, Anda mungkin melihat scrollbar dan sejenisnya, meskipun elemen berada pada 1x setelah perspective diterapkan. Anda dapat mengatasi masalah ini dengan menskalakan elemen dari sudut kanan bawah (dengan transform-origin: bottom right), yang berfungsi karena akan menyebabkan elemen yang terlalu besar tumbuh ke dalam "wilayah negatif" (biasanya kiri atas) area yang dapat di-scroll; wilayah yang dapat di-scroll tidak akan pernah memungkinkan Anda melihat atau men-scroll ke konten di wilayah negatif.

Kesimpulan

Paralaks adalah efek yang menyenangkan jika digunakan dengan cermat. Seperti yang dapat Anda lihat, Anda dapat menerapkan dengan cara yang berperforma tinggi, dikaitkan dengan scroll, dan lintas browser. Karena memerlukan sedikit manipulasi matematika, dan sedikit boilerplate untuk mendapatkan efek yang diinginkan, kami telah menggabungkan library dan contoh helper kecil, yang dapat Anda temukan di repositori GitHub Contoh Elemen UI kami.

Coba gunakan, dan beri tahu kami hasilnya.