Paralaks Berperforma Tinggi

Robert Flack
Robert Flack

Suka atau tidak suka, paralaks akan tetap ada. Saat digunakan dengan bijak, hal ini dapat menambahkan kedalaman dan kehalusan pada aplikasi web. Namun, penerapan paralaks dengan cara yang berperforma tinggi bisa menjadi tantangan tersendiri. Dalam artikel ini, kami akan membahas solusi yang berperforma baik dan, yang sama pentingnya, berfungsi di seluruh browser.

Ilustrasi paralaks.

TL;DR

  • Jangan gunakan peristiwa scroll atau background-position untuk membuat animasi paralaks.
  • Menggunakan transformasi 3D CSS untuk menciptakan 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 Sampel Elemen UI dan ambil JS helper Parallax. Anda dapat melihat demo langsung scroller paralaks di repo GitHub.

Paralakser permasalahan

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

Buruk: menggunakan peristiwa scroll

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

Informasi penting ini memberi tahu kita alasan kita perlu menghindari solusi berbasis JavaScript yang memindahkan elemen berdasarkan peristiwa scroll: JavaScript tidak menjamin bahwa paralaks akan tetap sejalan dengan posisi scroll halaman. Pada Mobile Safari versi lama, peristiwa scroll sebenarnya ditayangkan pada akhir scroll, sehingga tidak memungkinkan untuk membuat efek scroll berbasis JavaScript. Versi yang lebih baru memang menampilkan peristiwa scroll selama animasi, tetapi, seperti halnya Chrome, berdasarkan "upaya terbaik". Jika thread utama sibuk dengan pekerjaan lain, peristiwa scroll tidak akan segera dikirimkan, yang berarti efek paralaks akan hilang.

Buruk: memperbarui background-position

Situasi lain yang ingin kita hindari adalah mengecat di 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 menimbulkan biaya yang cukup mahal untuk mengacaukan animasi secara signifikan.

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

CSS dalam 3D

Scott Kellum dan Keith Clark telah berhasil melakukan banyak hal dalam 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.
  • Terapkan terjemahan dalam Z ke turunan elemen tersebut, dan skalakan kembali untuk menyediakan gerakan paralaks tanpa memengaruhi ukurannya di layar.

CSS untuk pendekatan ini 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 kembali akan menyebabkannya menjadi lebih kecil sebanding 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 muncul 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, skala elemen perlu ditingkatkan sebesar 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 skalanya adalah (perspektif - 0)/ perspektif, yang menghasilkan nilai 1, yang berarti skalanya tidak naik atau turun. Cukup berguna.

Cara kerja pendekatan ini

Penting untuk menjelaskan alasan keberhasilan cara ini, karena kita akan segera menggunakan pengetahuan tersebut. Scrolling pada dasarnya adalah transformasi, dan karenanya dapat dipercepat; scroll sebagian besar melibatkan pergeseran lapisan dengan GPU. Dalam scroll standar, yaitu scroll tanpa perspektif apa pun, scroll terjadi dalam cara 1:1 saat membandingkan elemen scroll dan turunannya. Jika Anda men-scroll elemen ke bawah menurut 300px, turunannya akan diubah ke atas dengan jumlah yang sama: 300px.

Namun, menerapkan nilai perspektif ke elemen scroll akan mengacaukan proses ini; hal ini akan mengubah matriks yang mendasari transformasi scroll. Sekarang, scroll 300 piksel hanya dapat memindahkan turunannya sebesar 150 piksel, bergantung pada nilai perspective dan translateZ yang Anda pilih. Jika elemen memiliki nilai translateZ 0, elemen tersebut akan di-scroll pada 1:1 (seperti biasanya), tetapi turunan yang didorong dalam Z menjauh 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 Anda tidak perlu memproses peristiwa scroll atau mengubah background-position.

Lalat: Mobile Safari

Ada peringatan untuk setiap efek, dan satu hal penting untuk transformasi adalah pemeliharaan 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 adalah baru, dan akan secara efektif meratakan nilai perspective dan kita kehilangan efek paralaks. Solusinya, dalam sebagian besar kasus, cukup sederhana: Anda menambahkan transform-style: preserve-3d ke elemen, yang menyebabkannya menyebarkan efek 3D (seperti nilai perspektif kita) yang telah diterapkan ke hierarki lebih jauh.

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

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

Dari sudut pandang {i>progressive enhancement<i}, hal ini mungkin tidak terlalu masalah. Jika kita tidak dapat membuat paralaks dalam setiap situasi, aplikasi kita akan tetap berfungsi, tetapi akan lebih baik jika kita mencari solusinya.

position: sticky siap 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 spesifikasinya, cukup lumayan, tetapi berisi permata kecil yang berguna di dalamnya:

Secara sekilas, hal ini mungkin tidak memiliki arti yang besar, tetapi poin penting dalam kalimat tersebut adalah ketika mengacu pada bagaimana tepatnya, kelekatan elemen dihitung: "offset dihitung dengan mengacu pada ancestor terdekat dengan kotak scroll". Dengan kata lain, jarak untuk memindahkan elemen melekat (agar terlihat melekat ke elemen lain atau area pandang) dihitung sebelum transformasi lain diterapkan, bukan setelah. Ini berarti bahwa, sangat mirip dengan 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 paralaksa mereferensikan ancestor terdekat dengan kotak scroll, yang dalam hal ini adalah .container. Kemudian, sama 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);
}

Tindakan ini akan memulihkan efek paralaks untuk Mobile Safari, yang merupakan berita bagus secara keseluruhan.

Peringatan penempatan yang melekat

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

  • Dengan position: sticky, semakin dekat elemen dengan z=0 kurang yang bergerak.
  • Tanpa position: sticky, semakin dekat elemen ke z=0, semakin banyak elemennya bergerak.

Jika semua itu tampak agak abstrak, lihat demo ini oleh Robert Flack, yang menunjukkan bagaimana elemen berperilaku berbeda dengan dan tanpa positioning 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 bagaimana position: sticky memengaruhi scroll paralaks.

Berbagai bug dan solusi

Namun, seperti halnya apa pun, masih ada benjolan dan benjolan yang perlu dihaluskan:

  • Dukungan melekat tidak konsisten. Dukungan masih diterapkan di Chrome, Edge tidak memiliki dukungan sepenuhnya, dan Firefox memiliki bug yang terdeteksi saat elemen melekat digabungkan dengan transformasi perspektif. Dalam kasus tersebut, sebaiknya tambahkan sedikit kode untuk hanya menambahkan position: sticky (versi awalan -webkit-) saat diperlukan, yaitu khusus untuk Mobile Safari.
  • Efeknya tidak "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 hal ini tampaknya mengalihkan Edge ke metode scroll non-OS, dan memastikan bahwa elemen tersebut memperhitungkan perubahan perspektif.
  • "Konten halaman menjadi sangat besar!" Banyak browser memperhitungkan skala ini saat menentukan seberapa besar konten halaman, tetapi sayangnya Chrome dan Safari tidak memperhitungkan perspektif. Jadi, misalnya, skala 3x yang diterapkan ke suatu elemen, Anda mungkin melihat scroll bar dan semacamnya, meskipun elemen berada pada posisi 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 berukuran besar berkembang ke "wilayah negatif" (biasanya kiri atas) dari area yang dapat di-scroll; region yang dapat di-scroll tidak pernah mengizinkan Anda melihat atau men-scroll ke konten di wilayah negatif.

Kesimpulan

Paralaks adalah efek yang menyenangkan jika digunakan dengan cermat. Seperti yang Anda lihat, Anda dapat menerapkannya dengan cara yang berperforma tinggi, digabungkan dengan scroll, dan lintas browser. Karena diperlukan sedikit penulisan matematis, dan sedikit boilerplate untuk mendapatkan efek yang diinginkan, kami telah menyusun beberapa library dan contoh bantuan kecil, yang dapat Anda temukan di repo GitHub Contoh Elemen UI.

Silakan coba dan beri tahu kami pendapat Anda.