Paralaks Berperforma Tinggi

Suka atau tidak suka, efek paralaks akan terus digunakan. Jika digunakan dengan bijak, efek ini dapat menambah kedalaman dan kehalusan pada aplikasi web. Namun, masalahnya adalah menerapkan efek paralaks dengan cara yang berperforma tinggi bisa jadi sulit. Dalam artikel ini, kita akan membahas solusi yang berperforma tinggi dan, yang sama pentingnya, berfungsi di seluruh 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 ambil JS helper Parallax. Anda dapat melihat demo langsung penggeser paralaks di repositori GitHub.

Paralaks masalah

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

Buruk: menggunakan peristiwa scroll

Persyaratan utama paralaks adalah harus digabungkan 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 khusus kita, hal ini berlaku untuk peristiwa scroll. Di sebagian besar browser, peristiwa scroll dikirimkan sebagai "upaya terbaik" dan tidak dijamin akan dikirimkan pada 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. Pada versi Mobile Safari yang lebih lama, peristiwa scroll sebenarnya dikirimkan di akhir scroll, sehingga efek scroll berbasis JavaScript tidak dapat dibuat. Versi yang lebih baru memang mengirimkan peristiwa scroll selama animasi, tetapi, seperti Chrome, berdasarkan "upaya terbaik". Jika thread utama sedang sibuk dengan tugas lain, peristiwa scroll tidak akan dikirimkan segera, yang berarti efek paralaks akan hilang.

Salah: memperbarui background-position

Situasi lain yang ingin kita hindari adalah menggambar di 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 membuat animasi sangat tersendat.

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

CSS dalam 3D

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

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

CSS untuk pendekatan ini akan terlihat seperti berikut:

.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 ke belakang akan membuatnya menjadi lebih kecil secara proporsional dengan nilai perspektif. Anda dapat menghitung seberapa besar penskalaan yang diperlukan dengan persamaan ini: (perspektif - jarak) / perspektif. Karena kemungkinan besar kita ingin elemen paralaks bergerak secara paralaks, tetapi muncul dalam ukuran yang kita buat, elemen tersebut harus diubah ukurannya dengan cara ini, bukan dibiarkan seperti apa adanya.

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

Untuk konten apa pun yang tidak menerapkan nilai translateZ, Anda dapat mengganti nilai nol. Artinya, skala adalah (perspektif - 0) / perspektif, yang menghasilkan nilai 1, yang berarti skala tidak diperbesar atau diperkecil. Sangat berguna.

Cara kerja pendekatan ini

Penting untuk memahami dengan jelas alasan hal ini berfungsi, karena kita akan menggunakan pengetahuan tersebut sebentar lagi. Men-scroll secara efektif adalah transformasi, itulah sebabnya dapat dipercepat; sebagian besar melibatkan penggeseran lapisan dengan GPU. Dalam scroll biasa, yang tidak memiliki konsep perspektif, scrolling terjadi secara 1:1 saat membandingkan elemen scrolling 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 scrolling akan mengganggu proses ini; nilai tersebut mengubah matriks yang mendasari 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 akan di-scroll dengan rasio 1:1 (seperti sebelumnya), tetapi elemen turunan yang didorong dalam Z menjauhi asal perspektif akan di-scroll dengan kecepatan yang berbeda. Hasil bersih: gerakan paralaks. Dan, yang sangat penting, hal ini ditangani sebagai bagian dari mekanisme scroll internal browser secara otomatis, yang berarti tidak perlu memproses peristiwa scroll atau mengubah background-position.

Masalah kecil: Safari Seluler

Ada peringatan untuk setiap efek, dan salah satu peringatan penting untuk transformasi adalah tentang pelestarian efek 3D pada 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 baru, dan akan secara efektif meratakan nilai perspective dan kita kehilangan efek paralaks. Solusinya, dalam sebagian besar kasus, cukup mudah: Anda menambahkan transform-style: preserve-3d ke elemen, sehingga elemen tersebut menyebarkan efek 3D (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 mengorbankan kemampuan untuk menggeser elemen scrolling. Solusinya adalah menambahkan -webkit-overflow-scrolling: touch, tetapi perspective juga akan diratakan dan kita tidak akan mendapatkan efek paralaks.

Dari sudut pandang peningkatan progresif, 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 datang untuk menyelamatkan!

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

Pada pandangan pertama, hal ini mungkin tidak tampak terlalu penting, tetapi poin penting dalam kalimat tersebut adalah saat merujuk pada cara menghitung kelekatan elemen: "offset dihitung dengan merujuk pada ancestor terdekat dengan kotak scroll". Dengan kata lain, jarak untuk memindahkan elemen lengket (agar muncul terlampir ke elemen lain atau area tampilan) dihitung sebelum transformasi lainnya diterapkan, bukan setelah. Artinya, seperti contoh scrolling 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 lengket.

Dengan menerapkan position: -webkit-sticky ke elemen paralaks, kita dapat secara efektif "membalikkan" efek perataan -webkit-overflow-scrolling: touch. Hal ini memastikan bahwa elemen paralaks mereferensikan ancestor terdekat dengan kotak scrolling, yang dalam hal ini adalah .container. Kemudian, mirip seperti sebelumnya, .parallax-container menerapkan nilai perspective, yang mengubah offset scroll yang dihitung dan menciptakan 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);
}

Hal ini memulihkan efek paralaks untuk Mobile Safari, yang merupakan kabar baik bagi semua orang.

Peringatan pemosisian tetap

Namun, ada perbedaan di sini: position: sticky mengubah mekanisme paralaks. Posisi tetap mencoba, ya, menempelkan elemen ke penampung scroll, sedangkan versi non-tetap tidak. Ini berarti bahwa paralaks dengan sticky akan menjadi kebalikan dari yang tanpa sticky:

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

Jika semuanya tampak sedikit abstrak, lihat demo ini oleh Robert Flack, yang menunjukkan bagaimana elemen berperilaku berbeda dengan dan tanpa penempatan tetap. Untuk melihat perbedaannya, Anda memerlukan Chrome Canary (yang merupakan versi 56 pada saat penulisan) atau Safari.

Screenshot perspektif paralaks

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

Berbagai bug dan solusinya

Namun, seperti halnya sesuatu, masih ada beberapa hal yang perlu diselesaikan:

  • Dukungan lengket tidak konsisten. Dukungan masih diterapkan di Chrome, Edge tidak memiliki dukungan sama sekali, dan Firefox memiliki bug saat menggambar ketika sticky digabungkan dengan transformasi perspektif. Dalam kasus seperti itu, sebaiknya tambahkan sedikit kode untuk menambahkan position: sticky (versi dengan awalan -webkit-) hanya jika diperlukan, yaitu hanya untuk Mobile Safari.
  • Efeknya tidak "berfungsi begitu saja" di Edge. Edge mencoba menangani scrolling di tingkat OS, yang umumnya merupakan hal yang baik, tetapi dalam kasus ini, Edge mencegahnya mendeteksi perubahan perspektif selama scroll. Untuk memperbaikinya, Anda dapat menambahkan elemen posisi tetap, karena hal ini tampaknya mengalihkan Edge ke metode pen-scroll non-OS, dan memastikan bahwa elemen tersebut memperhitungkan perubahan perspektif.
  • "Konten halaman ini menjadi sangat besar!" Banyak browser memperhitungkan skala saat memutuskan seberapa besar konten halaman, tetapi sayangnya Chrome dan Safari tidak memperhitungkan perspektif. Jadi, jika - misalnya - skala 3x diterapkan ke elemen, Anda mungkin melihat scroll bar dan sejenisnya, meskipun elemen berada pada 1x setelah perspective diterapkan. Masalah ini dapat diatasi 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 pernah memungkinkan Anda melihat atau men-scroll konten di wilayah negatif.

Kesimpulan

Paralaks adalah efek menarik jika digunakan dengan cermat. Seperti yang dapat Anda lihat, Anda dapat mengimplementasikannya dengan cara yang berperforma tinggi, terhubung dengan scroll, dan kompatibel dengan berbagai browser. Karena memerlukan sedikit manipulasi matematika, dan sejumlah kecil boilerplate untuk mendapatkan efek yang diinginkan, kami telah membungkus library dan sampel kecil yang dapat membantu, yang dapat Anda temukan di repositori GitHub Sampel Elemen UI kami.

Coba mainkan, dan beri tahu kami hasilnya.