TL;DR
ใช้การเปลี่ยนรูปแบบขนาดเมื่อสร้างภาพเคลื่อนไหวของคลิป คุณสามารถป้องกันไม่ให้เด็กๆ ยืดออกและบิดเบี้ยวระหว่างภาพเคลื่อนไหวได้โดยการปรับขนาดสวนทาง
ก่อนหน้านี้เราได้โพสต์ข้อมูลอัปเดตเกี่ยวกับวิธีสร้างเอฟเฟกต์พารัลแลกซ์และแถบเลื่อนแบบเลื่อนได้ไม่จำกัดที่มีประสิทธิภาพ ในโพสต์นี้ เราจะพูดถึงสิ่งที่ต้องดำเนินการหากต้องการภาพเคลื่อนไหวคลิปที่มีประสิทธิภาพ หากต้องการดูเดโม ให้ไปที่ที่เก็บ GitHub ของตัวอย่างองค์ประกอบ UI
ตัวอย่างเช่น เมนูแบบขยาย
ตัวเลือกบางอย่างในการสร้างนี้จะมีประสิทธิภาพมากกว่าตัวเลือกอื่นๆ
ไม่เหมาะสม: การทำภาพเคลื่อนไหวความกว้างและความสูงในองค์ประกอบคอนเทนเนอร์
คุณอาจจินตนาการได้ว่าการใช้ CSS เล็กน้อยเพื่อทำให้ความกว้างและความสูงขององค์ประกอบคอนเทนเนอร์เคลื่อนไหว
.menu {
overflow: hidden;
width: 350px;
height: 600px;
transition: width 600ms ease-out, height 600ms ease-out;
}
.menu--collapsed {
width: 200px;
height: 60px;
}
ปัญหาที่เกิดขึ้นทันทีจากการใช้แนวทางนี้คือต้องมีการทำให้ width
และ height
เคลื่อนไหว
พร็อพเพอร์ตี้เหล่านี้ต้องคำนวณเลย์เอาต์และวาดผลลัพธ์ในทุกเฟรมของภาพเคลื่อนไหว ซึ่งอาจใช้ทรัพยากรมากและมักจะทำให้คุณพลาด 60 fps หากเพิ่งทราบเรื่องนี้ โปรดอ่านคู่มือประสิทธิภาพการแสดงผล ซึ่งจะให้ข้อมูลเพิ่มเติมเกี่ยวกับวิธีการทำงานของกระบวนการแสดงผล
ใช้แอตทริบิวต์ CSS clip หรือ clip-path
วิธีอื่นในการสร้างภาพเคลื่อนไหวของ width
และ height
อาจเป็นการใช้พร็อพเพอร์ตี้ clip
(เลิกใช้งานแล้ว) เพื่อแสดงภาพเคลื่อนไหวของเอฟเฟกต์การขยายและการยุบ หรือจะใช้ clip-path
instead แทนก็ได้หากต้องการ อย่างไรก็ตาม การใช้ clip-path
ได้รับการรองรับน้อยกว่าclip
แต่ clip
เลิกใช้งานแล้ว ทางขวา แต่ไม่ต้องกังวลไป นี่ไม่ใช่วิธีแก้ปัญหาที่คุณต้องการ
.menu {
position: absolute;
clip: rect(0px 112px 175px 0px);
transition: clip 600ms ease-out;
}
.menu--collapsed {
clip: rect(0px 70px 34px 0px);
}
แม้ว่าจะดีกว่าการทำให้ width
และ height
ขององค์ประกอบเมนูเคลื่อนไหวได้ แต่ข้อเสียของแนวทางนี้คือยังคงทริกเกอร์การวาดภาพ นอกจากนี้ พร็อพเพอร์ตี้ clip
(หากเลือกใช้) ยังกำหนดให้องค์ประกอบที่ดำเนินการอยู่อยู่ในตำแหน่งแบบสัมบูรณ์หรือแบบคงที่ ซึ่งอาจต้องมีการดัดแปลงเพิ่มเติมเล็กน้อย
ดี: การปรับขนาด
เนื่องจากเอฟเฟกต์นี้เกี่ยวข้องกับสิ่งที่มีขนาดใหญ่ขึ้นและเล็กลง คุณจึงใช้การเปลี่ยนรูปแบบขนาดได้ นี่เป็นข่าวดีเนื่องจากการเปลี่ยนแปลงการเปลี่ยนรูปแบบนั้นไม่จำเป็นต้องมีเลย์เอาต์หรือการวาด และเบราว์เซอร์สามารถส่งต่อไปยัง GPU ได้ ซึ่งหมายความว่าระบบจะเร่งผลลัพธ์และมีโอกาสที่จะได้ 60 fps มากขึ้น
ข้อเสียของแนวทางนี้เช่นเดียวกับหลายๆ อย่างเกี่ยวกับประสิทธิภาพการแสดงผลคือต้องใช้เวลาในการตั้งค่าเล็กน้อย แต่คุ้มค่ามาก
ขั้นตอนที่ 1: คํานวณสถานะเริ่มต้นและสถานะสิ้นสุด
เมื่อใช้แนวทางที่ใช้ภาพเคลื่อนไหวแบบปรับขนาด ขั้นตอนแรกคือการอ่านองค์ประกอบที่บอกขนาดที่เมนูต้องมีทั้งเมื่อยุบอยู่และเมื่อขยายออก ในบางสถานการณ์ คุณอาจไม่สามารถรับข้อมูลทั้ง 2 รายการนี้พร้อมกันได้ และอาจต้องสลับคลาสบางอย่างเพื่ออ่านสถานะต่างๆ ของคอมโพเนนต์
อย่างไรก็ตาม หากจำเป็นต้องทำเช่นนั้น โปรดระมัดระวัง: getBoundingClientRect()
(หรือ offsetWidth
และ offsetHeight
) จะบังคับให้เบราว์เซอร์เรียกใช้สไตล์และเลย์เอาต์หากมีการเปลี่ยนแปลงสไตล์ตั้งแต่เรียกใช้ครั้งล่าสุด
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
};
}
ในกรณีของรายการอย่างเมนู เราอาจอนุมานได้อย่างสมเหตุสมผลว่ารายการดังกล่าวจะเริ่มต้นด้วยสเกลตามธรรมชาติ (1, 1) ขนาดตามธรรมชาตินี้แสดงสถานะแบบขยาย ซึ่งหมายความว่าคุณจะต้องสร้างภาพเคลื่อนไหวจากเวอร์ชันที่ปรับขนาดให้เล็กลง (ซึ่งคำนวณไว้ด้านบน) กลับไปยังขนาดตามธรรมชาติ
แต่เดี๋ยวก่อน การดำเนินการนี้จะปรับขนาดเนื้อหาของเมนูด้วยใช่ไหม ใช่ ดังที่คุณเห็นด้านล่าง
คุณจะทำอย่างไรได้บ้าง คุณสามารถใช้การเปลี่ยนรูปแบบแบบย้อนกลับกับเนื้อหาได้ เช่น หากปรับขนาดคอนเทนเนอร์ให้เล็กลงเป็น 1/5 ของขนาดปกติ คุณสามารถปรับขนาดเนื้อหาขึ้น 5 เท่าเพื่อป้องกันไม่ให้เนื้อหาถูกบีบอัด โปรดทราบว่า
การเปลี่ยนรูปแบบย้อนกลับเป็นการดำเนินการปรับขนาดด้วย ซึ่งดีเนื่องจากสามารถเร่งความเร็วได้เช่นเดียวกับภาพเคลื่อนไหวในคอนเทนเนอร์ คุณอาจต้องตรวจสอบว่าองค์ประกอบที่มีภาพเคลื่อนไหวมีเลเยอร์คอมโพสิตของตัวเอง (เพื่อให้ GPU ช่วยทำงาน) ซึ่งคุณทำได้โดยการเพิ่ม
will-change: transform
ลงในองค์ประกอบ หรือbackface-visiblity: hidden
หากต้องการรองรับเบราว์เซอร์รุ่นเก่าต้องคำนวณการเปลี่ยนรูปแบบย้อนกลับต่อเฟรม ขั้นตอนนี้อาจซับซ้อนขึ้นเล็กน้อย เนื่องจากสมมติว่าภาพเคลื่อนไหวอยู่ใน CSS และใช้ฟังก์ชันการเปลี่ยนค่าอย่างช้าๆ คุณจะต้องหักลบการเปลี่ยนค่าอย่างช้าๆ นั้นเมื่อสร้างภาพเคลื่อนไหวการเปลี่ยนรูปแบบย้อนกลับ อย่างไรก็ตาม การคำนวณเส้นโค้งผกผันสำหรับ
cubic-bezier(0, 0, 0.3, 1)
นั้นไม่ชัดเจนนัก
คุณจึงอาจอยากลองสร้างเอฟเฟกต์ภาพเคลื่อนไหวโดยใช้ JavaScript ท้ายที่สุดแล้ว คุณก็ใช้สมการการโจมตีแบบนุ่มนวลเพื่อคํานวณค่าการปรับขนาดและการปรับขนาดย้อนกลับต่อเฟรมได้ ข้อเสียของภาพเคลื่อนไหวที่ใช้ JavaScript คือสิ่งที่จะเกิดขึ้นเมื่อเธรดหลัก (ที่ JavaScript ทำงานอยู่) ไม่ว่างเนื่องจากมีงานอื่น คําตอบสั้นๆ คือภาพเคลื่อนไหวอาจกระตุกหรือหยุดทำงานไปเลย ซึ่งส่งผลเสียต่อ UX
ขั้นตอนที่ 2: สร้างภาพเคลื่อนไหว CSS ขณะทำงาน
วิธีการแก้ปัญหาที่อาจดูแปลกๆ ในตอนแรกคือการสร้างภาพเคลื่อนไหวแบบคีย์เฟรมด้วยฟังก์ชันการทำให้นุ่มนวลของเราเองแบบไดนามิก แล้วแทรกลงในหน้าเว็บเพื่อให้เมนูใช้ (ขอขอบคุณ Robert Flack วิศวกรของ Chrome ที่ช่วยชี้เรื่องนี้ให้เราทราบ) ประโยชน์หลักของการดำเนินการนี้คือภาพเคลื่อนไหวที่คีย์เฟรมซึ่งเปลี่ยนรูปแบบจะทำงานในคอมโพสิตได้ ซึ่งหมายความว่าจะไม่ได้รับผลกระทบจากงานในเธรดหลัก
ในการสร้างภาพเคลื่อนไหวของคีย์เฟรม เราจะเริ่มจาก 0 ถึง 100 และคำนวณค่าสเกลที่จำเป็นสำหรับองค์ประกอบและเนื้อหาขององค์ประกอบ จากนั้นจะสรุปเป็นสตริงได้ ซึ่งสามารถแทรกลงในหน้าเว็บเป็นองค์ประกอบสไตล์ การส่งผ่านสไตล์จะทําให้หน้าเว็บเรียกใช้การประมวลผลสไตล์ใหม่ ซึ่งจะเป็นงานเพิ่มเติมที่เบราว์เซอร์ต้องทํา แต่เบราว์เซอร์จะทําเพียงครั้งเดียวเมื่อคอมโพเนนต์เริ่มทํางาน
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}
}`;
}
ผู้ที่มีความอยากรู้ไม่หยุดอาจสงสัยเกี่ยวกับฟังก์ชัน ease()
ภายในวง for คุณสามารถใช้รูปแบบคำสั่งนี้เพื่อแมปค่าจาก 0 ถึง 1 กับค่าที่เทียบเท่าแบบเบาลง
function ease (v, pow=4) {
return 1 - Math.pow(1 - v, pow);
}
คุณใช้การค้นหาของ Google เพื่อดูภาพคร่าวๆ ของสถานที่ได้ด้วย มีประโยชน์ หากต้องการใช้สมการการโจมตีแบบอื่นๆ โปรดดู Tween.js โดย Soledad Penadés ซึ่งมีสมการโจมตีมากมาย
ขั้นตอนที่ 3: เปิดใช้ภาพเคลื่อนไหว CSS
เมื่อสร้างภาพเคลื่อนไหวเหล่านี้และนำไปใช้กับหน้าเว็บใน JavaScript แล้ว ขั้นตอนสุดท้ายคือการสลับคลาสที่เปิดใช้ภาพเคลื่อนไหว
.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;
}
ซึ่งจะทำให้ภาพเคลื่อนไหวที่สร้างในขั้นตอนก่อนหน้าทำงาน เนื่องจากภาพเคลื่อนไหวที่ฝังไว้มีการปรับให้ช้าลงแล้ว คุณจึงต้องตั้งค่าฟังก์ชันเวลาเป็น linear
ไม่เช่นนั้นภาพเคลื่อนไหวระหว่างแต่ละเฟรมหลักจะดูแปลกมาก
สำหรับการยุบองค์ประกอบกลับลง คุณจะเลือกได้ 2 วิธี ได้แก่ อัปเดตภาพเคลื่อนไหว CSS ให้ทำงานย้อนกลับแทนการทำงานไปข้างหน้า วิธีนี้ใช้ได้ แต่ "ความรู้สึก" ของภาพเคลื่อนไหวจะกลับกัน ดังนั้นหากคุณใช้เส้นโค้งการผ่อนคลาย การเคลื่อนไหวย้อนกลับจะดูช้าลง ซึ่งจะทำให้ดูช้า วิธีแก้ปัญหาที่เหมาะสมกว่าคือสร้างภาพเคลื่อนไหวคู่ที่ 2 สำหรับการยุบองค์ประกอบ ซึ่งสร้างได้โดยใช้วิธีเดียวกับภาพคีย์เฟรมแบบขยาย แต่สลับค่าเริ่มต้นและค่าสิ้นสุด
const xScale = 1 + (x - 1) * easedStep;
const yScale = 1 + (y - 1) * easedStep;
เวอร์ชันขั้นสูง: การเปิดเผยแบบวงกลม
นอกจากนี้ คุณยังใช้เทคนิคนี้เพื่อสร้างภาพเคลื่อนไหวแบบขยายและยุบเป็นวงกลมได้ด้วย
โดยหลักการส่วนใหญ่จะเหมือนกับเวอร์ชันก่อนหน้า ซึ่งคุณปรับขนาดองค์ประกอบและปรับขนาดองค์ประกอบย่อยที่อยู่ตรงใต้ขององค์ประกอบนั้นให้เล็กลง ในกรณีนี้ องค์ประกอบที่ปรับขนาดขึ้นมี border-radius
เท่ากับ 50% ซึ่งทำให้เป็นวงกลม และถูกตัดโดยองค์ประกอบอื่นที่มี overflow: hidden
ซึ่งหมายความว่าคุณจะไม่เห็นว่าวงกลมขยายออกนอกขอบเขตขององค์ประกอบ
คำเตือนเกี่ยวกับตัวแปรนี้: Chrome มีข้อความที่เบลอบนหน้าจอที่มี DPI ต่ำระหว่างภาพเคลื่อนไหวเนื่องจากข้อผิดพลาดในการปัดเศษเนื่องจากการปรับขนาดและการปรับขนาดย้อนกลับของข้อความ หากสนใจรายละเอียดของปัญหานี้ มีรายงานข้อบกพร่องที่คุณสามารถทำเครื่องหมายเป็นสำคัญและติดตามได้
โค้ดสำหรับเอฟเฟกต์การขยายแบบวงกลมอยู่ในที่เก็บ GitHub
สรุป
นี่เป็นวิธีสร้างภาพเคลื่อนไหวของคลิปที่มีประสิทธิภาพโดยใช้การเปลี่ยนรูปแบบขนาด ในโลกที่สมบูรณ์แบบ เราคงอยากเห็นภาพเคลื่อนไหวของคลิปเร่งขึ้น (มีข้อบกพร่องของ Chromium สำหรับการดำเนินการดังกล่าวซึ่ง Jake Archibald เป็นผู้เขียน) แต่ในระหว่างนี้ คุณควรระมัดระวังเมื่อสร้างภาพเคลื่อนไหวของ clip
หรือ clip-path
และหลีกเลี่ยงการสร้างภาพเคลื่อนไหวของ width
หรือ height
นอกจากนี้ คุณยังใช้ภาพเคลื่อนไหวบนเว็บสำหรับเอฟเฟกต์เช่นนี้ได้ด้วย เนื่องจากมี API ของ JavaScript แต่สามารถทำงานบนเธรดคอมโพสิเตอร์ได้หากคุณทำให้ transform
และ opacity
เคลื่อนไหวเท่านั้น
ขออภัย การรองรับภาพเคลื่อนไหวบนเว็บยังไม่ดีนัก แต่คุณใช้การปรับปรุงแบบเป็นขั้นเป็นตอนเพื่อใช้ภาพเคลื่อนไหวได้หากมี
if ('animate' in HTMLElement.prototype) {
// Animate with Web Animations.
} else {
// Fall back to generated CSS Animations or JS.
}
ในระหว่างนี้ แม้ว่าคุณจะสามารถใช้ไลบรารีที่อิงตาม JavaScript เพื่อสร้างภาพเคลื่อนไหวได้ แต่คุณอาจพบว่าประสิทธิภาพจะน่าเชื่อถือมากขึ้นหากสร้างภาพเคลื่อนไหว CSS ไว้ล่วงหน้าแล้วนำมาใช้แทน ในทํานองเดียวกัน หากแอปใช้ JavaScript สําหรับภาพเคลื่อนไหวอยู่แล้ว คุณอาจได้รับประโยชน์มากกว่าหากใช้โค้ดฐานที่มีอยู่
หากต้องการดูโค้ดสำหรับเอฟเฟกต์นี้ โปรดดูที่เก็บ GitHub ของตัวอย่างองค์ประกอบ UI และอย่าลืมแจ้งให้เราทราบเกี่ยวกับปัญหาที่คุณพบในความคิดเห็นด้านล่าง