Paralaks Berperforma Tinggi

Robert Flack
Robert Flack

Suka atau tidak, paralaks tetap ada. Ketika digunakan dengan bijak, ini dapat menambahkan kedalaman dan kehalusan ke aplikasi web. Namun, masalahnya adalah menerapkan paralaks dengan cara yang baik bisa menjadi tantangan. Dalam artikel ini, kami akan mendiskusikan solusi yang berperforma baik dan, yang sama pentingnya, lintas 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 guna memastikan bahwa efek paralaks akan disebarluaskan.

Jika Anda menginginkan solusi drop-in, buka repo GitHub Contoh Elemen UI dan ambil JS helper Paralaks. Anda dapat melihat demo langsung scroller paralaks di repositori GitHub ASL.

Paralakser soal

Sebagai permulaan, mari kita lihat dua cara umum untuk mencapai paralaks mempengaruhi, dan khususnya, mengapa mereka tidak sesuai dengan tujuan kita.

Buruk: menggunakan peristiwa scroll

Persyaratan utama dari paralaks adalah harus di-scroll dan digabungkan; untuk setiap perubahan di posisi scroll halaman, elemen seharusnya diperbarui. Meskipun kedengarannya sederhana, mekanisme penting dari browser modern adalah kemampuannya untuk bekerja secara asinkron. Hal ini berlaku, dalam kasus tertentu, untuk menggulir peristiwa. Di sebagian besar browser, peristiwa scroll ditayangkan sebagai "upaya terbaik" dan tidak dijamin akan ditayangkan pada setiap frame animasi scroll!

Informasi penting ini memberitahu 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. Dalam Mobile Safari versi lama, aktivitas scroll ditempatkan di akhir scroll, sehingga mustahil untuk Efek scroll berbasis JavaScript. Versi terbaru lainnya mengirimkan peristiwa scroll selama animasi, tetapi, sama seperti Chrome, berdasarkan "upaya terbaik" layanan. Jika thread utama sibuk dengan pekerjaan lain, peristiwa scroll tidak akan dikirimkan segera, yang berarti efek paralaks akan hilang.

Buruk: mengupdate background-position

Situasi lain yang ingin kita hindari adalah melakukan pengecatan pada setiap bingkai. Banyak solusi mencoba mengubah background-position untuk memberikan tampilan paralaks, yang menyebabkan browser mengecat ulang bagian laman yang terpengaruh saat menggulir, dan itu cukup mahal sehingga bisa menyebabkan jank pada animasi secara signifikan.

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

CSS dalam 3D

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

  • Menyiapkan elemen pemuat untuk men-scroll dengan overflow-y: scroll (dan mungkin overflow-x: hidden).
  • Untuk elemen yang sama tersebut, terapkan nilai perspective, dan perspective-origin ditetapkan ke top left, atau 0 0.
  • Untuk turunan elemen tersebut menerapkan terjemahan dalam Z, dan menurunkan skalanya hingga menyediakan gerakan paralaks tanpa mempengaruhi 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 membuatnya 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 muncul sesuai ukuran yang kita tulis, perlu ditingkatkan skalanya dengan cara seperti ini, bukan dibiarkan apa adanya.

Dalam kasus kode di atas, perspektifnya adalah 1px, dan Jarak Z parallax-child adalah -2px. Ini berarti bahwa elemen itu akan membutuhkan ditingkatkan skalanya 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. Ini berarti skalanya adalah (perspektif - 0) / perspektif, yang menghasilkan nilai 1, yang berarti telah diskalakan tidak ke atas atau pun ke bawah. Cukup berguna, sebenarnya.

Cara kerja pendekatan ini

Penting untuk menjelaskan mengapa cara ini berhasil, karena kita akan menggunakannya mengetahui informasi terbaru. Scrolling secara efektif adalah transformasi, itulah sebabnya accelerated; kemungkinan besar prosesnya melibatkan pemindahan lapisan dengan GPU. Di pengguliran biasa, yaitu gulir tanpa perspektif apa pun, menggulir terjadi secara 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.

Akan tetapi, menerapkan nilai perspektif ke elemen {i>scrolling<i} akan mengacaukan dalam proses ini; ini mengubah matriks yang mendukung transformasi scroll. Sekarang gulungan 300px hanya dapat memindahkan anak-anak sebesar 150px, tergantung pada Nilai perspective dan translateZ yang Anda pilih. Jika suatu elemen memiliki Nilai translateZ 0, ini akan di-scroll pada 1:1 (seperti sebelumnya), tetapi turunan yang didorong di Z dari titik perspektif akan di-scroll dengan rating! Hasil akhirnya: gerakan paralaks. Dan, yang sangat penting, ini ditangani sebagai bagian dari mesin scroll internal browser secara otomatis, yang berarti ada tidak perlu memproses peristiwa scroll atau mengubah background-position.

Lalat dengan salep: Mobile Safari

Ada catatan untuk setiap efek, dan satu yang penting untuk transformasi adalah mempertahankan efek 3D pada elemen turunan. Jika ada elemen pada hierarki antara elemen dengan perspektif dan turunan paralaksnya, perspektif 3D menjadi "rata", artinya efeknya akan hilang.

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

Pada HTML di atas, .parallax-container masih baru dan akan secara efektif meratakan nilai perspective dan kita kehilangan efek paralaks. Solusi, Biasanya, cukup mudah: Anda menambahkan transform-style: preserve-3d ke elemen, menyebabkannya menyebarkan efek 3D apa pun (seperti perspektif kita nilai) 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 pada biaya untuk dapat mengayunkan elemen {i>scrolling<i}. Solusinya adalah dengan menambahkan -webkit-overflow-scrolling: touch, tetapi 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 performa. Jika kami tidak dapat menggunakan paralaks dalam setiap situasi, aplikasi kami akan tetap berfungsi, tetapi ada baiknya untuk mencari solusi.

position: sticky menolong!

Sebenarnya, ada beberapa bantuan dalam bentuk position: sticky, yang ada untuk izinkan elemen untuk "melekat" ke bagian atas area pandang atau elemen induk tertentu selama scroll. Spesifikasinya, seperti kebanyakan, cukup lumayan, tetapi berisi permata kecil yang sangat membantu di dalam:

Ini mungkin tidak terlihat berarti banyak sekali, tetapi merupakan poin penting dalam kalimat itu adalah ketika mengacu pada bagaimana, tepatnya, kelekatan suatu elemen dihitung: "offset dihitung dengan mengacu pada ancestor terdekat dengan kotak scroll". Dengan kata lain, jarak untuk memindahkan elemen melekat (agar tampak melekat pada elemen lain atau area pandang) adalah dihitung sebelum transformasi lainnya diterapkan, bukan setelah. Artinya sangat mirip dengan contoh scroll sebelumnya, jika offset dihitung dengan ukuran 300px, ada peluang baru untuk menggunakan perspektif (atau transformasi lainnya) untuk memanipulasi nilai offset 300 px tersebut sebelum diterapkan ke yang kurang penting.

Dengan menerapkan position: -webkit-sticky ke elemen paralaks, kita dapat yang efektif "membalikkan" efek perataan -webkit-overflow-scrolling: touch. Ini memastikan bahwa elemen paralaks mereferensikan nilai terdekat ancestor dengan kotak scroll, yang dalam hal ini adalah .container. Lalu: sama 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);
}

Ini akan memulihkan efek paralaks untuk Mobile Safari, yang merupakan berita baik bulat!

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 paralaks dengan melekat pada akhirnya menjadi kebalikan dari tanpa:

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

Jika semua itu tampak agak abstrak, lihat demo ini oleh Robert Flack, yang menunjukkan bagaimana elemen berperilaku berbeda dengan dan tanpa elemen melekat pemosisian. Untuk melihat perbedaannya, Anda memerlukan Chrome Canary (yaitu versi 56 pada saat penulisan ini) atau Safari.

Screenshot perspektif Paralaks

Demo oleh Robert Flack yang menunjukkan bagaimana position: sticky memengaruhi scroll paralaks.

Berbagai macam bug dan solusi

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

  • Dukungan melekat tidak konsisten. Dukungan masih diterapkan di Chrome, Edge tidak memiliki dukungan sepenuhnya, dan Firefox memiliki bug pengecatan saat melekat digabungkan dengan transformasi perspektif. Dengan demikian kasus tersebut, sebaiknya tambahkan sedikit kode untuk hanya menambahkan position: sticky ( versi berawalan -webkit-) jika diperlukan, yaitu untuk Mobile Safari saja.
  • Efeknya tidak "langsung berfungsi" di Edge. Edge mencoba menangani pengguliran tingkat OS, yang umumnya merupakan hal baik, tetapi dalam hal ini hal itu mencegahnya agar tidak 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 perubahan tersebut diperhitungkan oleh perubahan perspektif.
  • "Konten halaman menjadi sangat besar!" Banyak {i>browser<i} yang mempertimbangkan skala ini saat menentukan seberapa besar ukuran konten halaman, tetapi sayangnya Chrome dan Safari tidak memperhitungkan perspektif. Namun jika ada - misalnya - skala 3x yang diterapkan pada suatu elemen, Anda mungkin kita akan melihat bilah gulir dan sejenisnya, bahkan jika elemen berada pada 1x setelah perspective telah diterapkan. Kita 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 menjadi "area negatif" (biasanya kiri atas) area yang dapat di-scroll; dapat di-scroll wilayah tidak memungkinkan Anda melihat atau men-scroll ke konten di wilayah negatif.

Kesimpulan

Paralaks adalah efek yang menyenangkan jika digunakan dengan bijak. Seperti yang Anda lihat, mungkin saja untuk menerapkannya dengan cara yang berperforma tinggi, ditambah scroll, dan lintas browser. Karena cara ini membutuhkan sedikit perubahan matematika, dan sedikit perubahan boilerplate untuk mendapatkan efek yang diinginkan, kami telah menyelesaikan sebuah library helper kecil dan contoh, yang dapat Anda temukan di repo GitHub Contoh Elemen UI.

Silakan coba, dan beri tahu kami cara Anda melakukannya.