TL;DR
ใช้การเปลี่ยนรูปแบบขนาดเมื่อสร้างภาพเคลื่อนไหวของคลิป คุณสามารถป้องกันไม่ให้เด็กๆ ยืดออกและบิดเบี้ยวขณะแสดงภาพเคลื่อนไหวด้วยการปรับสเกล
ก่อนหน้านี้เราได้โพสต์ข้อมูลอัปเดตเกี่ยวกับวิธีสร้างเอฟเฟกต์พารัลแลกซ์และแถบเลื่อนแบบเลื่อนได้ไม่สิ้นสุดที่มีประสิทธิภาพ ในโพสต์นี้ เราจะพูดถึงสิ่งที่เกี่ยวข้องหากคุณต้องการภาพเคลื่อนไหวจากคลิปนักแสดง หากต้องการดูการสาธิต โปรดดูที่ที่เก็บตัวอย่างองค์ประกอบ UI ใน GitHub
ลองดูตัวอย่างเมนูแบบขยายต่อไปนี้
ตัวเลือกบางอย่างในการสร้างนี้จะมีประสิทธิภาพมากกว่าตัวเลือกอื่นๆ
ไม่เหมาะสม: การทำภาพเคลื่อนไหวความกว้างและความสูงในองค์ประกอบคอนเทนเนอร์
คุณอาจจินตนาการได้ว่าการใช้ 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
แทนก็ได้หากต้องการ อย่างไรก็ตาม การใช้ 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) ขนาดตามธรรมชาตินี้แสดงสถานะแบบขยาย ซึ่งหมายความว่าคุณจะต้องสร้างภาพเคลื่อนไหวจากเวอร์ชันที่ปรับขนาดให้เล็กลง (ซึ่งคำนวณไว้ด้านบน) กลับไปเป็นขนาดตามธรรมชาติ
แต่เดี๋ยวก่อน การดำเนินการนี้จะปรับขนาดเนื้อหาของเมนูด้วยใช่ไหม ใช่ ดังที่คุณเห็นด้านล่าง
คุณจะทำอย่างไรได้บ้าง คุณสามารถใช้การแปลงแบบ counter- กับเนื้อหาได้ ตัวอย่างเช่น หากคอนเทนเนอร์ปรับขนาดลงเป็น 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
อย่างแน่นอน
นอกจากนี้ การใช้ภาพเคลื่อนไหวบนเว็บสำหรับเอฟเฟกต์ดังกล่าวยังมีประโยชน์เนื่องจากมี JavaScript API แต่สามารถเรียกใช้บนเธรดคอมโพสิตได้หากคุณทำให้ 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 และอย่าลืมแจ้งให้เราทราบเกี่ยวกับปัญหาที่คุณพบในความคิดเห็นด้านล่าง