Paralaks Berperforma Tinggi

Robert Flack
Robert Flack

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

Ilustrasi Paralaks.

TL;DR

  • Jangan gunakan peristiwa scroll atau background-position untuk membuat animasi paralaks.
  • Menggunakan 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 drop-in, buka repo GitHub Contoh Elemen UI dan pilih Parallax helper JS. Anda dapat melihat demo langsung scroller paralaks di repo GitHub.

Paralakser soal

Untuk memulainya, mari kita lihat dua cara umum untuk mencapai efek paralaks, dan secara khusus, mengapa mereka tidak sesuai untuk tujuan kita.

Buruk: menggunakan peristiwa scroll

Persyaratan utama paralaks adalah harus digabungkan dengan scroll; untuk setiap perubahan dalam posisi scroll halaman, posisi elemen paralaks harus diperbarui. Meskipun terdengar sederhana, mekanisme penting browser modern adalah kemampuannya untuk bekerja secara asinkron. Hal ini berlaku, dalam kasus khusus kami, untuk peristiwa scroll. Di sebagian besar browser, peristiwa scroll dijalankan sebagai "upaya terbaik" dan tidak dijamin akan ditayangkan pada setiap frame animasi scroll.

Informasi penting ini memberi tahu kita alasan mengapa kita harus menghindari solusi berbasis JavaScript yang memindahkan elemen berdasarkan peristiwa scroll: JavaScript tidak menjamin bahwa paralaks akan tetap sesuai dengan posisi scroll halaman. Dalam Mobile Safari versi lama, peristiwa scroll sebenarnya ditayangkan di akhir scroll, yang membuat efek scroll berbasis JavaScript tidak dapat diberikan. Versi yang lebih baru mengirimkan peristiwa scroll selama animasi, tetapi, sama seperti Chrome, atas dasar "upaya terbaik". Jika thread utama sibuk dengan pekerjaan lain, peristiwa scroll tidak akan segera dikirim, yang berarti efek paralaks akan hilang.

Buruk: mengupdate background-position

Situasi lain yang ingin kita hindari adalah melakukan pengecatan pada setiap bingkai. 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 berpegang pada 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:

  • Siapkan elemen pemuat untuk men-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, menerapkan terjemahan di Z, dan menskalakannya 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 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 ini 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 tersebut harus diskalakan hingga 3x, yang dapat Anda lihat adalah nilai yang dimasukkan ke dalam kode: scale(3).

Untuk konten yang tidak menerapkan nilai translateZ, Anda dapat mengganti nilai nol. Artinya, skalanya adalah (perspektif - 0) / perspektif, yang menghasilkan nilai 1, yang berarti skalanya telah ditingkatkan, bukan naik atau turun. Cukup berguna, sebenarnya.

Cara kerja pendekatan ini

Penting untuk menjelaskan mengapa cara 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, yaitu tanpa gagasan perspektif apa pun, scroll terjadi 1:1 saat membandingkan elemen scroll dan turunannya. Jika Anda men-scroll elemen ke bawah sebesar 300px, turunannya akan diubah ke atas dengan jumlah yang sama: 300px.

Namun, menerapkan nilai perspektif ke elemen scroll akan mengacaukan proses ini; tindakan 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 memiliki nilai translateZ 0, elemen akan di-scroll pada rasio 1:1 (seperti sebelumnya), tetapi turunan yang didorong dari Z jauh dari asal perspektif akan di-scroll dengan kecepatan yang berbeda. Hasil akhirnya: 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 dengan salep: Mobile Safari

Ada peringatan untuk setiap efek, dan salah satu hal penting untuk transformasi adalah tentang pelestarian efek 3D pada elemen turunan. Jika ada elemen dalam hierarki antara elemen dengan perspektif dan turunannya, perspektif 3D akan "diratakan", yang berarti efeknya akan hilang.

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

Dalam HTML di atas, .parallax-container bersifat baru, dan akan secara efektif meratakan nilai perspective dan kita akan kehilangan efek paralaks. Solusinya, pada umumnya, cukup mudah: Anda menambahkan transform-style: preserve-3d ke elemen, yang menyebabkannya menyebarkan efek 3D (seperti nilai perspektif kita) yang telah diterapkan lebih jauh ke atas pohon.

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

Namun, dalam kasus Mobile Safari, segalanya menjadi sedikit lebih rumit. Menerapkan overflow-y: scroll ke elemen container secara teknis berfungsi, tetapi harus dapat mengayunkan elemen scroll. Solusinya adalah menambahkan -webkit-overflow-scrolling: touch, tetapi juga akan meratakan perspective dan kita tidak akan mendapatkan paralaks apa pun.

Dari sudut pandang progressive enhancement, hal ini mungkin bukan masalah yang biasa. Jika kami tidak dapat menggunakan paralaks dalam setiap situasi, aplikasi kami akan tetap berfungsi, tetapi akan lebih baik untuk mencari solusinya.

position: sticky menolong!

Sebenarnya, ada beberapa bantuan dalam bentuk position: sticky, yang ada untuk memungkinkan elemen "menempel" di bagian atas area pandang atau elemen induk tertentu selama scroll. Spesifikasinya, seperti kebanyakan dari mereka, cukup lumayan, tetapi berisi permata kecil yang berguna di dalamnya:

Sekilas mungkin tidak terlihat berarti, tetapi poin penting dalam kalimat tersebut adalah ketika mengacu pada bagaimana tepatnya, tingkat kelekatan suatu elemen dihitung: "offset dihitung dengan mengacu pada ancestor terdekat dengan kotak scroll". Dengan kata lain, jarak untuk memindahkan elemen melekat (agar elemen tersebut tampak melekat pada elemen lain atau area tampilan) dihitung sebelum transformasi lainnya 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 secara efektif "membalikkan" efek perataan -webkit-overflow-scrolling: touch. 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 mekanisme paralaks. Pemosisian melekat mencoba menempelkan elemen ke penampung scroll, sedangkan versi yang tidak melekat tidak. Ini berarti bahwa paralaks dengan melekat pada akhirnya menjadi kebalikan dari paralaks tanpa:

  • Dengan position: sticky, semakin dekat elemen ke z=0 less yang dipindahkannya.
  • Tanpa position: sticky, semakin dekat elemen ke z=0, semakin banyak gerakannya dilakukan.

Jika semua tampak agak abstrak, lihat demo ini oleh Robert Flack, yang menunjukkan perilaku berbeda dengan dan tanpa posisi yang melekat. 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 macam bug dan solusi

Namun, seperti biasa, masih ada benjolan yang perlu dihaluskan:

  • Dukungan melekat tidak konsisten. Dukungan masih diterapkan di Chrome, Edge tidak memiliki dukungan sepenuhnya, dan Firefox memiliki cat bug saat sticky digabungkan dengan transformasi perspektif. Dalam kasus seperti ini, ada baiknya menambahkan sedikit kode untuk hanya menambahkan position: sticky (versi berawalan -webkit-) saat diperlukan, yang khusus untuk Mobile Safari.
  • 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 ini memperhitungkan perubahan perspektif.
  • "Konten halaman menjadi sangat besar!" Banyak browser yang memperhitungkan skala saat menentukan seberapa besar konten halaman, tetapi sayangnya Chrome dan Safari tidak memperhitungkan perspektif. Jadi, jika ada - misalnya - skala 3x yang diterapkan pada suatu elemen, Anda mungkin akan melihat scroll bar dan sejenisnya, meskipun elemen tersebut 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 "area negatif" (biasanya kiri atas) area yang dapat di-scroll; wilayah yang dapat di-scroll tidak pernah mengizinkan Anda melihat atau men-scroll ke konten di area negatif.

Kesimpulan

Paralaks adalah efek yang menyenangkan jika digunakan dengan bijak. Seperti yang Anda lihat, Anda dapat menerapkannya dengan cara yang berperforma tinggi, terkait scroll, dan lintas browser. Karena memerlukan sedikit repo secara matematis, serta sejumlah kecil boilerplate untuk mendapatkan efek yang diinginkan, kami telah menggabungkan library helper dan contoh yang kecil, yang dapat Anda temukan di repo GitHub Contoh Elemen UI kami.

Silakan coba, dan beri tahu kami cara Anda melakukannya.