การสร้างภาพเคลื่อนไหวแบบขยายและยุบ

พอล ลูอิส
สตีเฟน แมคกรูเออร์
Stephen McGruer

สรุปคร่าวๆ

ใช้การแปลงอัตราส่วนเมื่อทำให้คลิปเคลื่อนไหว คุณสามารถป้องกันไม่ให้เด็กยืดและบิดในขณะที่เคลื่อนไหวได้โดยการปรับขนาดตัวนับ

ก่อนหน้านี้เราโพสต์อัปเดตเกี่ยวกับวิธีสร้างเอฟเฟกต์พารัลแลกซ์และการเลื่อนได้ไม่รู้จบที่มีประสิทธิภาพ ในโพสต์นี้ เราจะมาดูสิ่งที่เกี่ยวข้องหากคุณต้องการภาพเคลื่อนไหวของคลิปที่มีประสิทธิภาพ หากคุณต้องการดูการสาธิต โปรดดูตัวอย่างที่เก็บ 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 หรือคลิปเส้นทาง

อีกทางเลือกหนึ่งที่ใช้แทนการสร้าง 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 ขององค์ประกอบเมนูเคลื่อนไหวได้ดีกว่า แต่ข้อเสียของวิธีนี้คือยังคงทริกเกอร์ Paint นอกจากนี้ หากคุณใช้พร็อพเพอร์ตี้ clip ตามเส้นทางดังกล่าว องค์ประกอบที่กําลังทํางานอยู่จะต้องอยู่ในตําแหน่งที่แน่นอนหรือคงที่ ซึ่งอาจต้องใช้การลองผิดลองถูกเล็กน้อย

ดี: แสดงสเกลภาพเคลื่อนไหว

เนื่องจากเอฟเฟกต์นี้จะมีบางอย่างที่ใหญ่ขึ้นและเล็กลง คุณจึงใช้การเปลี่ยนรูปแบบได้ นี่เป็นข่าวดี เพราะการเปลี่ยนแปลงของการเปลี่ยนรูปแบบเป็นสิ่งที่ไม่จำเป็นต้องใช้การจัดวางหรือลงสี และเบราว์เซอร์สามารถส่งผ่าน GPU ได้ ซึ่งหมายความว่าเอฟเฟ็กต์จะถูกเร่งขึ้นและมีแนวโน้มที่จะถึง 60 เฟรมต่อวินาทีเป็นอย่างมาก

ข้อเสียของวิธีนี้ก็เหมือนกับประสิทธิภาพการแสดงผลส่วนใหญ่ คือต้องมีการตั้งค่าเล็กน้อย แต่รับรองว่าคุ้มค่าแน่นอน

ขั้นตอนที่ 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- มี 2 สิ่งที่ควรสังเกตเกี่ยวกับเรื่องดังกล่าว ได้แก่

  1. การเปลี่ยนรูปแบบตัวนับยังเป็นการดําเนินการเกี่ยวกับขนาดด้วย นี่เป็นดีเนื่องจากสามารถเร่งความเร็วได้ เช่นเดียวกับภาพเคลื่อนไหวในคอนเทนเนอร์ คุณอาจต้องตรวจสอบว่าองค์ประกอบที่เป็นภาพเคลื่อนไหวมีเลเยอร์องค์ประกอบเอง (ช่วยให้ GPU สามารถช่วยได้) และให้คุณสามารถเพิ่ม will-change: transform ลงในองค์ประกอบหรือ backface-visiblity: hidden หากคุณจำเป็นต้องรองรับเบราว์เซอร์รุ่นเก่า

  2. ต้องคำนวณการเปลี่ยนรูปแบบตัวนับต่อเฟรม จากตรงนี้อาจมีเรื่องยุ่งยากเล็กน้อย เนื่องจากภาพเคลื่อนไหวใน CSS และใช้ฟังก์ชันการค่อยๆ เปลี่ยน จะต้องตอบโต้การค่อยๆ เปลี่ยนเมื่อทำให้ภาพเคลื่อนไหวของการเปลี่ยนรูปแบบเคลื่อนไหว แต่การคำนวณเส้นโค้งผกผัน เช่น cubic-bezier(0, 0, 0.3, 1) ไม่ใช่สิ่งที่ชัดเจนขนาดนั้น

ดังนั้น คุณอาจจะอยากลองทำให้เอฟเฟ็กต์เคลื่อนไหวโดยใช้ JavaScript จากนั้นก็ใช้สมการการค่อยๆ เปลี่ยนเพื่อคำนวณค่าสเกลและค่าตัวนับต่อเฟรม ข้อเสียของภาพเคลื่อนไหวที่ใช้ JavaScript คือสิ่งที่เกิดขึ้นเมื่อเทรดหลัก (ที่ JavaScript ทำงาน) ยุ่งอยู่กับงานอื่น คำตอบสั้นๆ คือภาพเคลื่อนไหวอาจกระตุกหรือหยุดไปเลย ซึ่งไม่ดีต่อ UX

ขั้นตอนที่ 2: สร้างภาพเคลื่อนไหว CSS อย่างรวดเร็ว

วิธีแก้ไขซึ่งอาจดูแปลกๆ ในตอนแรกคือการสร้างภาพเคลื่อนไหวที่มีคีย์เฟรมด้วยฟังก์ชันการค่อยๆ เปลี่ยนของเราเองแบบไดนามิก และแทรกภาพนั้นลงในหน้าเพื่อใช้โดยเมนู (ต้องขอขอบคุณวิศวกร Chrome Robert Flack ที่ชี้ให้เห็นข้อมูลนี้) ประโยชน์หลักของวิธีนี้คือภาพเคลื่อนไหวที่มีคีย์เฟรมที่เปลี่ยนแปลงการเปลี่ยนรูปแบบสามารถเรียกใช้บนตัวจัดวางองค์ประกอบได้ ซึ่งหมายความว่าวิดีโอนั้นจะไม่ได้รับผลจากงานในเทรดหลัก

ในการสร้างภาพเคลื่อนไหวของคีย์เฟรม เราจะกำหนดระดับจาก 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-loop คุณใช้ลักษณะนี้เพื่อแมปค่าจาก 0 ถึง 1 ไปยังค่าเทียบเท่าแบบค่อยๆ เปลี่ยนได้

function ease (v, pow=4) {
  return 1 - Math.pow(1 - v, pow);
}

นอกจากนี้ คุณยังใช้ Google Search เพื่อพล็อตหน้าตา ได้ด้วย สะดวก หากคุณต้องการสมการการค่อยๆ เปลี่ยนแบบอื่นๆ ลองดู 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 ในการสร้างภาพเคลื่อนไหว แต่คุณอาจพบว่ามีประสิทธิภาพน่าเชื่อถือมากขึ้นด้วยการ Bake ภาพเคลื่อนไหว CSS แล้วใช้รูปแบบนั้นแทน ในทำนองเดียวกัน หากแอปใช้ JavaScript สำหรับภาพเคลื่อนไหวของแอปอยู่แล้ว แอปของคุณอาจให้บริการได้ดียิ่งขึ้นเนื่องจากมีความสอดคล้องกับฐานของโค้ดที่คุณมีอยู่เป็นอย่างน้อย

หากต้องการดูรายละเอียดโค้ดสำหรับเอฟเฟกต์นี้ โปรดดูตัวอย่างที่เก็บ GitHub สำหรับองค์ประกอบ UI และเช่นเคย โปรดแจ้งให้เราทราบถึงวิธีการใช้งานของคุณในส่วนความคิดเห็นด้านล่าง