TL;DR
Gunakan transformasi skala saat menganimasikan klip. Anda dapat mencegah turunan diregangkan dan miring selama animasi dengan menskalakannya secara terbalik.
Sebelumnya, kami telah memposting informasi terbaru tentang cara membuat efek paralaks dan scroller tanpa batas yang berperforma tinggi. Dalam postingan ini, kita akan melihat hal-hal yang diperlukan jika Anda menginginkan animasi klip berperforma tinggi. Jika Anda ingin melihat demo, lihat repo GitHub Contoh Elemen UI.
Misalnya, menu yang diluaskan:
Beberapa opsi untuk mem-build ini memiliki performa yang lebih baik daripada opsi lainnya.
Buruk: Menganimasikan lebar dan tinggi pada elemen penampung
Anda bisa membayangkan menggunakan sedikit CSS untuk menganimasikan lebar dan tinggi pada 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 memerlukan animasi width
dan height
.
Properti ini memerlukan penghitungan tata letak, dan menggambar hasilnya di setiap frame animasi,
yang dapat sangat mahal, dan biasanya akan menyebabkan Anda kehilangan 60 fps. Jika Anda baru mengetahuinya,
baca panduan
Performa Rendering kami, tempat Anda bisa mendapatkan informasi selengkapnya tentang cara kerja proses rendering.
Buruk: Menggunakan properti clip atau clip-path CSS
Alternatif untuk menganimasikan width
dan height
mungkin adalah menggunakan properti clip
(yang kini tidak digunakan lagi) untuk menganimasikan efek luaskan dan ciutkan. Atau, jika mau, Anda dapat menggunakan clip-path
. Namun, penggunaan clip-path
kurang didukung
daripada clip
. Namun, clip
tidak digunakan lagi. Kanan. Namun, jangan putus asa, ini bukan 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, kelemahan pendekatan
ini adalah masih memicu paint. Selain itu, properti clip
, jika Anda menggunakan rute tersebut,
memerlukan bahwa elemen yang mengoperasikannya diposisikan secara mutlak atau tetap, yang dapat
memerlukan sedikit wrangling tambahan.
Baik: animasi skala
Karena efek ini melibatkan sesuatu yang menjadi lebih besar dan lebih kecil, Anda dapat menggunakan transformasi skala. Ini adalah kabar baik karena mengubah transformasi adalah sesuatu yang tidak memerlukan tata letak atau cat, dan yang dapat diserahkan browser ke GPU, yang berarti bahwa efeknya dipercepat dan kemungkinan besar mencapai 60 fps.
Kelemahan pendekatan ini, seperti sebagian besar hal dalam performa rendering, adalah memerlukan sedikit penyiapan. Namun, proses 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 diperlukan saat diciutkan, dan saat diluaskan. Mungkin untuk beberapa situasi, Anda tidak dapat mendapatkan kedua informasi ini sekaligus, dan Anda perlu — misalnya — mengalihkan beberapa class agar dapat membaca berbagai status komponen.
Namun, jika Anda perlu melakukannya, berhati-hatilah: getBoundingClientRect()
(atau offsetWidth
dan offsetHeight
) memaksa browser untuk menjalankan gaya dan meneruskan tata letak 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 wajar bahwa menu akan dimulai dalam skala alaminya (1, 1). Skala alami ini mewakili statusnya yang diperluas, yang berarti Anda harus menganimasikan dari versi yang diskalakan ke bawah (yang dihitung di atas) kembali ke skala alami tersebut.
Tapi tunggu! Tentunya ini akan menyesuaikan ukuran konten menu juga, bukan? Ya, seperti yang dapat Anda lihat di bawah.
Jadi, apa yang bisa Anda lakukan? Anda dapat menerapkan transformasi counter- ke konten, jadi misalnya jika penampung diskalakan ke 1/5 ukuran normalnya, Anda dapat menskalakan konten ke atas 5x untuk mencegah konten terhimpit. Ada dua hal yang perlu diperhatikan:
Transformasi penghitung juga merupakan operasi penskalaan. Hal ini baik karena juga dapat dipercepat, seperti animasi pada penampung. Anda mungkin perlu memastikan bahwa elemen yang dianimasikan mendapatkan lapisan kompositornya sendiri (memungkinkan GPU 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 hal-hal menjadi sedikit lebih rumit, karena dengan asumsi bahwa animasi berada dalam CSS dan menggunakan fungsi easing, easing itu sendiri perlu diimbangi saat menganimasikan transformasi penghitung. Namun, menghitung kebalikan kurva untuk — misalnya —
cubic-bezier(0, 0, 0.3, 1)
tidak terlalu jelas.
Oleh karena itu, mungkin tergoda untuk membuat animasi efek menggunakan JavaScript. Lagi pula, Anda nanti bisa menggunakan persamaan easing untuk menghitung skala dan nilai skala berlawanan per frame. Kelemahan animasi berbasis JavaScript adalah apa yang terjadi saat thread utama (tempat JavaScript Anda berjalan) sibuk dengan beberapa tugas lain. Jawaban singkatnya adalah animasi Anda dapat tersendat atau berhenti sama sekali, yang tidak bagus untuk UX.
Langkah 2: Build Animasi CSS dengan cepat
Solusinya, yang mungkin tampak aneh pada awalnya, adalah membuat animasi dengan frame utama menggunakan fungsi easing kita sendiri secara dinamis dan memasukkannya ke dalam halaman untuk digunakan oleh menu. (Terima kasih banyak kepada engineer Chrome Robert Flack yang telah menunjukkan hal ini!) Manfaat utamanya adalah animasi dengan frame utama yang memutar transformasi dapat dijalankan di kompositor, yang berarti tidak terpengaruh oleh tugas di thread utama.
Untuk membuat animasi keyframe, kita akan melakukan langkah dari 0 hingga 100 dan menghitung nilai skala yang akan diperlukan untuk elemen dan kontennya. Kemudian, ini dapat diringkas menjadi string, yang dapat dimasukkan ke halaman sebagai elemen gaya. Memasukkan gaya akan menyebabkan penerusan ReCalculate Styles di halaman, yang merupakan pekerjaan tambahan yang harus dilakukan browser, tetapi hanya akan melakukannya sekali saat komponen melakukan 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}
}`;
}
Penasaran mungkin akan bertanya-tanya tentang fungsi ease()
di dalam for-loop. Anda dapat menggunakan
hal seperti ini untuk memetakan nilai dari 0 hingga 1 ke nilai yang disederhanakan.
function ease (v, pow=4) {
return 1 - Math.pow(1 - v, pow);
}
Anda juga dapat menggunakan penelusuran Google untuk memetakan tampilannya. Praktis! Jika Anda memerlukan persamaan easing lainnya, lihat Tween.js oleh Soledad Penadés, yang berisi banyak persamaan easing.
Langkah 3: Aktifkan Animasi CSS
Dengan animasi ini dibuat dan di-bake ke halaman dalam JavaScript, langkah terakhir adalah mengubah 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;
}
Tindakan ini akan menyebabkan animasi yang dibuat di langkah sebelumnya berjalan. Karena animasi
yang di-bake sudah di-ease, fungsi pengaturan waktu harus ditetapkan ke linear
. Jika tidak, Anda akan
melakukan ease di antara setiap keyframe yang akan terlihat sangat aneh.
Untuk menciutkan elemen kembali, ada dua opsi: perbarui animasi CSS agar berjalan mundur, bukan maju. Hal ini akan berfungsi dengan baik, tetapi "rasa" animasi akan terbalik, jadi jika Anda menggunakan kurva ease-out, kebalikannya akan terasa lebih mudah dimasukkan, yang akan membuatnya terasa lambat. Solusi yang lebih sesuai adalah membuat pasangan kedua animasi untuk menyempitkan elemen. Animasi ini dapat dibuat dengan cara yang sama persis 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: tampilan melingkar
Anda juga dapat menggunakan teknik ini untuk membuat animasi perluas dan ciutkan melingkar.
Prinsipnya sebagian besar sama dengan versi sebelumnya, yaitu Anda menskalakan elemen, dan
menskalakan balik turunan langsungnya. Dalam hal ini, elemen yang diskalakan memiliki
border-radius
sebesar 50%, sehingga berbentuk lingkaran, dan digabungkan oleh elemen lain yang
memiliki overflow: hidden
, yang berarti Anda tidak melihat lingkaran meluas di luar batas elemen.
Peringatan tentang varian khusus ini: Chrome memiliki teks yang buram di layar DPI rendah selama animasi karena error pembulatan karena skala dan skala penghitung teks. Jika Anda tertarik dengan detailnya, ada laporan bug yang dapat Anda beri bintang dan ikuti.
Kode untuk efek perluasan melingkar dapat ditemukan di repo GitHub.
Kesimpulan
Jadi, itulah cara untuk melakukan animasi klip berperforma tinggi menggunakan transformasi skala. Dalam keadaan sempurna,
akan sangat bagus jika animasi klip dipercepat (ada
bug Chromium untuk itu yang dibuat oleh
Jake Archibald), tetapi sampai kita mencapainya, Anda harus berhati-hati saat menganimasikan clip
atau clip-path
,
dan tentu saja hindari menganimasikan width
atau height
.
Sebaiknya gunakan
Web Animations
untuk efek seperti ini, karena memiliki API
JavaScript, tetapi dapat berjalan di thread compositor jika Anda hanya menganimasikan transform
dan opacity
.
Sayangnya, dukungan untuk Animasi Web tidak terlalu baik,
meskipun Anda dapat menggunakan progressive enhancement untuk menggunakannya jika tersedia.
if ('animate' in HTMLElement.prototype) {
// Animate with Web Animations.
} else {
// Fall back to generated CSS Animations or JS.
}
Hingga hal itu berubah, meskipun Anda dapat menggunakan library berbasis JavaScript untuk melakukan animasi, Anda mungkin akan mendapati bahwa Anda mendapatkan performa yang lebih andal dengan membuat animasi CSS dan menggunakannya. Demikian pula, jika aplikasi Anda sudah mengandalkan JavaScript untuk animasinya, Anda mungkin akan lebih baik jika setidaknya konsisten dengan codebase yang ada.
Jika Anda ingin melihat kode untuk efek ini, lihat repositori GitHub Contoh Elemen UI dan, seperti biasa, beri tahu kami hasil Anda di komentar di bawah.