TL;DR (Ringkasan)
Gunakan transformasi skala saat menganimasikan klip. Anda dapat mencegah turunan melebar dan miring selama animasi dengan melakukan penskalaan konter.
Sebelumnya, kami telah memposting pembaruan tentang cara membuat efek paralaks dan scroller tanpa batas yang berperforma tinggi. Dalam postingan ini, kita akan melihat apa saja yang diperlukan jika Anda menginginkan animasi klip yang berperforma tinggi. Jika Anda ingin melihat demo, lihat Contoh repositori GitHub Elemen UI.
Misalnya, menu yang dapat diluaskan:
Beberapa opsi untuk membuat API ini memiliki performa yang lebih baik daripada yang lain.
Buruk: Menganimasikan lebar dan tinggi pada elemen penampung
Anda dapat membayangkan menggunakan sedikit CSS untuk menganimasikan lebar dan tinggi di elemen container.
.menu {
overflow: hidden;
width: 350px;
height: 600px;
transition: width 600ms ease-out, height 600ms ease-out;
}
.menu--collapsed {
width: 200px;
height: 60px;
}
Masalah langsung dengan pendekatan ini adalah pendekatan ini memerlukan animasi width
dan height
.
Properti ini mengharuskan penghitungan tata letak dan menggambar hasilnya di setiap frame animasi,
yang bisa sangat mahal, dan biasanya akan menyebabkan Anda kehilangan 60 fps. Jika ini adalah kabar Anda, baca panduan Performa Rendering kami yang berisi informasi selengkapnya tentang cara kerja proses rendering.
Buruk: Menggunakan properti klip CSS atau jalur klip
Alternatif untuk menganimasikan width
dan height
mungkin adalah dengan menggunakan properti clip
(kini tidak digunakan lagi) untuk menganimasikan efek luaskan dan ciutkan. Atau, jika mau, Anda dapat menggunakan clip-path
. Namun, penggunaan clip-path
kurang didukung dengan baik daripada clip
. Namun, clip
tidak digunakan lagi. Oke. Tapi jangan putus asa, ini bukanlah solusi yang Anda inginkan.
.menu {
position: absolute;
clip: rect(0px 112px 175px 0px);
transition: clip 600ms ease-out;
}
.menu--collapsed {
clip: rect(0px 70px 34px 0px);
}
Meskipun lebih baik daripada menganimasikan width
dan height
elemen menu, kekurangan pendekatan
ini adalah masih memicu paint. Selain itu, jika Anda menempuh rute tersebut, properti clip
mengharuskan elemen yang dioperasikannya diposisikan secara mutlak atau tetap, sehingga dapat
memerlukan sedikit wrangling tambahan.
Bagus: menganimasikan skala
Karena efek ini melibatkan sesuatu yang semakin besar dan kecil, Anda dapat menggunakan transformasi skala. Ini adalah kabar baik karena mengubah transformasi adalah sesuatu yang tidak memerlukan layout atau paint, dan dapat diserahkan oleh browser ke GPU, yang berarti efeknya dipercepat dan jauh lebih mungkin mencapai 60 fps.
Kelemahan dari pendekatan ini, seperti kebanyakan hal dalam performa rendering, adalah bahwa pendekatan ini memerlukan sedikit penyiapan. Namun, hal ini benar-benar sepadan.
Langkah 1: Hitung status awal dan akhir
Dengan pendekatan yang menggunakan animasi skala, langkah pertama adalah membaca elemen yang memberi tahu Anda
ukuran menu yang harus diciutkan, maupun saat diluaskan. Mungkin untuk beberapa
situasi, Anda tidak bisa mendapatkan kedua bit informasi ini sekaligus, dan Anda harus — misalnya — mengganti beberapa class agar dapat membaca berbagai status komponen.
Namun, jika Anda perlu melakukannya, berhati-hatilah: getBoundingClientRect()
(atau offsetWidth
dan offsetHeight
) memaksa browser menjalankan gaya dan tata letak diteruskan jika gaya telah berubah
sejak terakhir kali dijalankan.
function calculateCollapsedScale () {
// The menu title can act as the marker for the collapsed state.
const collapsed = menuTitle.getBoundingClientRect();
// Whereas the menu as a whole (title plus items) can act as
// a proxy for the expanded state.
const expanded = menu.getBoundingClientRect();
return {
x: collapsed.width / expanded.width,
y: collapsed.height / expanded.height
};
}
Dalam kasus seperti menu, kita dapat membuat asumsi yang masuk akal bahwa menu tersebut akan mulai berada dalam skala alaminya (1, 1). Skala alami ini mewakili status yang diperluas, yang berarti Anda harus melakukan animasi dari versi yang diperkecil (yang dihitung di atas) kembali ke skala alami tersebut.
Tapi tunggu! Tentu ini akan meningkatkan skala isi menu, bukan? Nah, seperti yang bisa Anda lihat di bawah ini, ya.
Jadi, apa yang dapat Anda lakukan? Anda dapat menerapkan transformasi counter-ke konten, jadi misalnya jika container diperkecil hingga 1/5 dari ukuran normalnya, Anda dapat menskalakan konten naik sebesar 5x agar konten tidak terpotong. Ada dua hal yang perlu diperhatikan tentang hal ini:
Transformasi penghitung juga merupakan operasi skala. Ini bagus karena juga dapat diakselerasi, seperti animasi di container. Anda mungkin perlu memastikan bahwa elemen yang dianimasikan mendapatkan lapisan compositor-nya sendiri (memungkinkan GPU untuk membantu), dan untuk itu Anda dapat menambahkan
will-change: transform
ke elemen atau, jika Anda perlu mendukung browser lama,backface-visiblity: hidden
.Transformasi penghitung harus dihitung per frame. Di sinilah segalanya bisa menjadi sedikit lebih rumit, karena dengan asumsi bahwa animasi berada di CSS dan menggunakan fungsi easing, easing itu sendiri perlu dilawan saat menganimasikan transformasi penghitung. Namun, menghitung kurva terbalik untuk — misalnya —
cubic-bezier(0, 0, 0.3, 1)
tidaklah terlalu jelas.
Kemudian, Anda mungkin tergoda untuk mempertimbangkan animasi efek menggunakan JavaScript. Lagi pula, Anda kemudian dapat menggunakan persamaan easing untuk menghitung nilai skala dan skala penghitung per frame. Kelemahan dari setiap animasi berbasis JavaScript adalah yang terjadi saat thread utama (tempat JavaScript Anda berjalan) sibuk dengan beberapa tugas lain. Jawaban singkatnya adalah animasi Anda bisa tersendat atau berhenti sama sekali, yang tidak bagus untuk UX.
Langkah 2: Buat Animasi CSS dengan cepat
Solusinya, yang mungkin tampak aneh pada awalnya, adalah dengan membuat animasi keyframe dengan fungsi easing kita sendiri secara dinamis dan memasukkannya ke dalam halaman untuk digunakan oleh menu. (Terima kasih banyak kepada insinyur Chrome Robert Flack karena telah menunjukkan ini!) Manfaat utama dari hal ini adalah animasi dengan keyframe yang mengubah transformasi dapat dijalankan di compositor, yang berarti bahwa animasi tidak terpengaruh oleh tugas di thread utama.
Untuk membuat animasi keyframe, kita akan melangkah dari 0 hingga 100 dan menghitung nilai skala yang diperlukan untuk elemen dan kontennya. Hal ini kemudian dapat diringkas menjadi string, yang dapat dimasukkan ke dalam halaman sebagai elemen gaya. Memasukkan gaya akan menyebabkan Recalculated Styles diteruskan di halaman, yang merupakan pekerjaan tambahan yang harus dilakukan browser, tetapi hanya akan melakukannya sekali saat komponen booting.
function createKeyframeAnimation () {
// Figure out the size of the element when collapsed.
let {x, y} = calculateCollapsedScale();
let animation = '';
let inverseAnimation = '';
for (let step = 0; step <= 100; step++) {
// Remap the step value to an eased one.
let easedStep = ease(step / 100);
// Calculate the scale of the element.
const xScale = x + (1 - x) * easedStep;
const yScale = y + (1 - y) * easedStep;
animation += `${step}% {
transform: scale(${xScale}, ${yScale});
}`;
// And now the inverse for the contents.
const invXScale = 1 / xScale;
const invYScale = 1 / yScale;
inverseAnimation += `${step}% {
transform: scale(${invXScale}, ${invYScale});
}`;
}
return `
@keyframes menuAnimation {
${animation}
}
@keyframes menuContentsAnimation {
${inverseAnimation}
}`;
}
Pengguna yang penasaran mungkin akan bertanya-tanya tentang fungsi ease()
di dalam for-loop. Anda dapat menggunakan contoh seperti ini untuk memetakan nilai dari 0 ke 1 ke ekuivalen yang dikurangkan.
function ease (v, pow=4) {
return 1 - Math.pow(1 - v, pow);
}
Anda juga dapat menggunakan Google Penelusuran untuk memetakan tampilannya. Berguna! Jika Anda membutuhkan persamaan easing lainnya, lihat Tween.js dari Soledad Penadés, yang berisi banyak persamaan.
Langkah 3: Aktifkan Animasi CSS
Setelah animasi ini dibuat dan diterapkan ke halaman di JavaScript, langkah terakhir adalah mengalihkan class yang mengaktifkan animasi.
.menu--expanded {
animation-name: menuAnimation;
animation-duration: 0.2s;
animation-timing-function: linear;
}
.menu__contents--expanded {
animation-name: menuContentsAnimation;
animation-duration: 0.2s;
animation-timing-function: linear;
}
Hal ini menyebabkan animasi yang dibuat di langkah sebelumnya berjalan. Karena animasi
yang direkam sudah dipermudah, fungsi pengaturan waktu harus disetel ke linear
. Jika tidak, Anda akan
memudahkan di antara setiap keyframe yang akan terlihat sangat aneh.
Jika harus menciutkan elemen kembali, ada dua opsi: perbarui animasi CSS agar berjalan secara terbalik, bukan maju. Opsi ini akan berfungsi dengan baik, tetapi "nuansa" animasi akan terbalik, jadi jika Anda menggunakan kurva easy-out, bagian sebaliknya akan terasa melambat in, yang akan membuatnya terasa lambat. Solusi yang lebih tepat adalah membuat pasangan kedua animasi untuk menciutkan elemen. Ini dapat dibuat dengan cara yang persis sama seperti animasi keyframe perluasan, tetapi dengan nilai awal dan akhir yang ditukar.
const xScale = 1 + (x - 1) * easedStep;
const yScale = 1 + (y - 1) * easedStep;
Versi yang lebih canggih: menampilkan lingkaran
Anda juga dapat menggunakan teknik ini untuk membuat animasi perluasan dan penciutan melingkar.
Prinsipnya sebagian besar sama dengan versi sebelumnya, tempat Anda menskalakan elemen, dan
menskalakan turunan langsungnya. Dalam hal ini, elemen yang ditingkatkan skalanya memiliki
border-radius
50%, membuatnya berbentuk lingkaran, dan digabungkan oleh elemen lain yang
memiliki overflow: hidden
, yang berarti bahwa Anda tidak melihat lingkaran meluas di luar batas elemen.
Sebagai peringatan untuk varian khusus ini: Chrome memiliki teks buram di layar DPI rendah selama animasi karena error pembulatan akibat skala dan skala penghitung teks. Jika Anda ingin mengetahui detailnya, ada bug yang dilaporkan yang dapat Anda bintangi dan ikuti.
Kode untuk efek perluasan melingkar dapat ditemukan di repo GitHub.
Kesimpulan
Demikianlah, cara membuat animasi klip berperforma tinggi menggunakan transformasi skala. Di dunia yang
sempurna, akan sangat baik untuk melihat animasi klip yang dipercepat (ada
bug Chromium untuk itu yang dibuat oleh
Jake Archibald), tetapi sampai kita sampai di sana, Anda harus berhati-hati saat menganimasikan clip
atau clip-path
,
dan tentu saja menghindari animasi width
atau height
.
Sebaiknya gunakan
Animasi Web
untuk efek seperti ini, karena efek tersebut memiliki JavaScript
API, tetapi dapat berjalan di thread compositor jika Anda hanya menganimasikan transform
dan opacity
.
Sayangnya, dukungan untuk Animasi Web bukanlah hal yang baik,
meskipun Anda dapat menggunakan {i>progressive enhancement<i} untuk menggunakannya jika tersedia.
if ('animate' in HTMLElement.prototype) {
// Animate with Web Animations.
} else {
// Fall back to generated CSS Animations or JS.
}
Hingga perubahan itu terjadi, meskipun Anda dapat menggunakan library berbasis JavaScript untuk melakukan animasi, Anda mungkin akan mendapatkan performa yang lebih andal dengan membuat animasi CSS dan menggunakannya. Demikian pula, jika aplikasi Anda sudah mengandalkan JavaScript untuk animasinya, sebaiknya Anda setidaknya konsisten dengan codebase yang ada.
Jika Anda ingin mempelajari kode untuk efek ini, lihat repo GitHub Contoh Elemen UI dan, seperti biasa, beri tahu kami cara Anda menyampaikannya melalui komentar di bawah.