Paralaks Berperforma Tinggi

Robert Flack
Robert Flack

Suka atau tidak, paralaks akan tetap ada. Jika digunakan dengan cermat, parallax dapat menambahkan kedalaman dan kehalusan ke aplikasi web. Namun, masalahnya adalah menerapkan parallax dengan cara yang berperforma tinggi bisa menjadi tantangan. Dalam artikel ini, kita akan membahas solusi yang berperforma tinggi dan, yang sama pentingnya, 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 tetap 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 memang mengirimkan peristiwa scroll selama animasi, tetapi, seperti halnya 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 mencoba mengubah background-position untuk memberikan tampilan paralaks, yang menyebabkan browser mengecat ulang bagian halaman yang terpengaruh saat men-scroll, dan hal itu dapat cukup mahal untuk menyebabkan jank animasi secara signifikan.

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 seberapa besar perlunya penskalaan dengan persamaan ini: (perspektif - jarak) / perspektif. Karena kita kemungkinan besar ingin elemen paralaks menjadi paralaks, tetapi muncul pada ukuran yang kita buat, elemen tersebut harus diskalakan dengan cara ini, bukan dibiarkan apa adanya.

Untuk kode di atas, perspektifnya adalah 1 piksel, dan jarak Z parallax-child adalah -2 piksel. 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 merupakan transformasi, itulah sebabnya scrolling dapat dipercepat; sebagian besar melibatkan pergeseran lapisan dengan GPU. Dalam scroll biasa, yang tidak memiliki 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 piksel hanya dapat memindahkan turunan sebesar 150 piksel, 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.

Masalah kecil: Safari Seluler

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. Menerapkan overflow-y: scroll ke elemen penampung secara teknis 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 menerapkan paralaks dalam setiap situasi, aplikasi kita akan tetap berfungsi, tetapi akan lebih baik jika kita menemukan solusinya.

position: sticky datang untuk menyelamatkan.

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 berarti pada pandangan pertama, tetapi poin utama dalam kalimat tersebut adalah saat merujuk ke cara persis penghitungan daya lekat 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, seperti 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 Safari Seluler, yang merupakan berita baik sekaligus.

Catatan pemosisian melekat

Namun, ada perbedaan di sini: position: sticky memang mengubah mekanika paralaks. Pemosisi melekat mencoba, ya, melekat elemen ke penampung scroll, sedangkan versi non-melekat tidak. Ini berarti bahwa parallax dengan sticky akan menjadi kebalikan dari parallax tanpa:

  • Dengan position: sticky, semakin dekat elemen ke z=0, elemen tersebut akan semakin sedikit bergerak.
  • Tanpa position: sticky, semakin dekat elemen ke z=0, semakin elemen tersebut bergerak.

Jika semua hal tersebut tampak sedikit abstrak, lihat demo ini oleh Robert Flack, yang menunjukkan bagaimana elemen berperilaku secara berbeda dengan dan tanpa pemosisi 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 bug dan solusi

Namun, seperti halnya hal lainnya, masih ada beberapa masalah yang perlu diperbaiki:

  • 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.
  • Efek ini tidak "langsung berfungsi" di Edge. Edge mencoba menangani scroll di tingkat OS, yang umumnya merupakan hal yang baik, tetapi dalam hal ini mencegahnya mendeteksi perubahan perspektif selama scroll. Untuk memperbaikinya, Anda dapat menambahkan elemen posisi tetap, karena tampaknya 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

Parallax 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.