Xây dựng ảnh động mở rộng & thu gọn hiệu suất

Paul Lewis
Stephen McGruer
Stephen McGruer

TL;DR

Sử dụng biến đổi tỷ lệ khi tạo ảnh động cho đoạn video. Bạn có thể ngăn phần tử con bị kéo giãn và lệch trong ảnh động bằng cách điều chỉnh tỷ lệ phần tử con.

Trước đây, chúng tôi đã đăng thông tin cập nhật về cách tạo hiệu ứng thị saitrình cuộn vô hạn. Trong bài đăng này, chúng tôi sẽ xem xét những vấn đề có liên quan nếu bạn muốn tạo ảnh động dạng đoạn video hiệu quả. Nếu bạn muốn xem bản minh hoạ, hãy xem kho lưu trữ GitHub cho Phần tử giao diện người dùng mẫu.

Ví dụ: trong một trình đơn mở rộng:

Một số cách để tạo quảng cáo này có hiệu suất cao hơn các cách khác.

Không tốt: Tạo ảnh động cho chiều rộng và chiều cao trên phần tử vùng chứa

Bạn có thể hình dung việc sử dụng một chút CSS để tạo ảnh động cho chiều rộng và chiều cao trên phần tử vùng chứa.

.menu {
  overflow: hidden;
  width: 350px;
  height: 600px;
  transition: width 600ms ease-out, height 600ms ease-out;
}

.menu--collapsed {
  width: 200px;
  height: 60px;
}

Vấn đề tức thì với phương pháp này là yêu cầu tạo ảnh động cho widthheight. Các thuộc tính này yêu cầu tính toán bố cục và vẽ kết quả trên mọi khung ảnh động. Điều này có thể rất tốn kém và thường sẽ khiến bạn bỏ lỡ tốc độ 60 khung hình/giây. Nếu bạn tin vậy, hãy đọc hướng dẫn Hiệu suất hiển thị của chúng tôi, trong đó bạn có thể tìm hiểu thêm thông tin về cách hoạt động của quá trình kết xuất.

Không phù hợp: Sử dụng thuộc tính clip hoặc clip-path của CSS

Một giải pháp thay thế để tạo ảnh động cho widthheight, có thể là sử dụng thuộc tính clip (hiện không dùng nữa) để tạo hiệu ứng tạo ảnh động cho hiệu ứng mở rộng và thu gọn. Hoặc nếu muốn, bạn có thể sử dụng clip-path. Tuy nhiên, việc sử dụng clip-path được hỗ trợ kém hơn so với clip. Tuy nhiên, clip không được dùng nữa. Phải. Nhưng đừng lo lắng, đây không phải là giải pháp bạn muốn!

.menu {
  position: absolute;
  clip: rect(0px 112px 175px 0px);
  transition: clip 600ms ease-out;
}

.menu--collapsed {
  clip: rect(0px 70px 34px 0px);
}

Mặc dù tốt hơn là tạo ảnh động cho widthheight của phần tử trình đơn, nhưng nhược điểm của phương pháp này là vẫn kích hoạt tính năng vẽ. Ngoài ra, thuộc tính clip, nếu bạn đi theo tuyến đó, bắt buộc phần tử mà nó đang vận hành phải ở vị trí tuyệt đối hoặc cố định. Điều này có thể đòi hỏi thêm một chút xáo trộn.

Tốt: ảnh động dạng tỷ lệ

Vì hiệu ứng này liên quan đến việc thứ gì đó trở nên lớn hơn và nhỏ hơn, nên bạn có thể sử dụng phép biến đổi tỷ lệ. Đây là tin vui vì việc thay đổi phép biến đổi là việc không cần phải bố cục hoặc vẽ, và trình duyệt có thể chuyển giao cho GPU, nghĩa là hiệu ứng được tăng tốc và có nhiều khả năng đạt đến tốc độ 60 khung hình/giây.

Nhược điểm của phương pháp này, giống như hầu hết các phương pháp về hiệu suất kết xuất, là yêu cầu thiết lập một chút. Tuy nhiên, điều này hoàn toàn xứng đáng!

Bước 1: Tính toán trạng thái bắt đầu và trạng thái kết thúc

Với phương pháp sử dụng ảnh động theo tỷ lệ, bước đầu tiên là đọc các phần tử cho bạn biết kích thước của trình đơn cần phải ở cả khi thu gọn và mở rộng. Trong một số tình huống, bạn không thể lấy cả hai thông tin này cùng một lúc và bạn cần phải chuyển đổi một số lớp để có thể đọc các trạng thái khác nhau của thành phần. Tuy nhiên, nếu bạn cần làm điều đó, hãy thận trọng: getBoundingClientRect() (hoặc offsetWidthoffsetHeight) buộc trình duyệt chạy kiểu và chuyển bố cục nếu kiểu thay đổi kể từ lần chạy gần đây nhất.

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
    };
}

Trong trường hợp một trình đơn như trình đơn, chúng ta có thể đưa ra giả định hợp lý rằng sự kiện đó sẽ bắt đầu ở quy mô tự nhiên (1, 1). Tỷ lệ tự nhiên này biểu thị trạng thái mở rộng, nghĩa là bạn sẽ cần tạo ảnh động từ phiên bản thu nhỏ (đã được tính toán ở trên) sao lưu sang tỷ lệ tự nhiên đó.

Nhưng chờ đã! Chắc chắn điều này sẽ điều chỉnh nội dung của trình đơn theo tỷ lệ đúng không? Như bạn có thể thấy ở bên dưới, đúng vậy.

Vậy bạn có thể làm gì với việc này? Bạn có thể áp dụng phép biến đổi counter- cho nội dung, chẳng hạn như nếu vùng chứa được thu nhỏ xuống còn 1/5 kích thước thông thường, bạn có thể điều chỉnh tỷ lệ nội dung counter- 5 lần để nội dung không bị nén. Có hai điều cần lưu ý về điều đó:

  1. Phép biến đổi ngược cũng là một phép toán quy mô. Điều này là tốt vì nó cũng có thể được tăng tốc, giống như ảnh động trên vùng chứa. Bạn cần đảm bảo rằng các phần tử đang được tạo ảnh động có lớp trình kết hợp riêng (cho phép GPU trợ giúp). Để làm được điều đó, bạn có thể thêm will-change: transform vào phần tử hoặc backface-visiblity: hidden nếu cần hỗ trợ các trình duyệt cũ.

  2. Phép biến đổi ngược phải được tính toán cho từng khung hình. Đây là lúc mọi thứ có thể trở nên khó khăn hơn một chút, vì giả sử ảnh động nằm trong CSS và sử dụng hàm gia tốc, thì chính ảnh động cần được đối xử khi tạo ảnh động cho phép biến đổi ngược. Tuy nhiên, việc tính toán đường cong nghịch đảo cho (giả sử) cubic-bezier(0, 0, 0.3, 1) không phải lúc nào cũng rõ ràng.

Do đó, bạn có thể muốn tạo ảnh động cho hiệu ứng bằng JavaScript. Sau cùng, bạn có thể sử dụng phương trình gia tốc để tính tỷ lệ và giá trị bộ đếm trên mỗi khung hình. Nhược điểm của mọi ảnh động dựa trên JavaScript là xảy ra khi luồng chính (nơi JavaScript chạy) đang bận rộn với một số tác vụ khác. Câu trả lời ngắn gọn là ảnh động có thể bị giật hoặc dừng lại hoàn toàn, điều này không tốt cho trải nghiệm người dùng.

Bước 2: Tạo nhanh ảnh động CSS

Giải pháp, lúc đầu có vẻ kỳ lạ, là tạo một ảnh động có khung hình chính bằng hàm gia tốc riêng của chúng tôi một cách linh động và chèn ảnh đó vào trang để trình đơn sử dụng. (Cảm ơn kỹ sư Chrome Robert Flack đã chỉ ra điều này!) Lợi ích chính của việc này là một ảnh động có khung hình chính có thể biến đổi biến đổi có thể chạy trên trình tổng hợp, nghĩa là nó không bị ảnh hưởng bởi các tác vụ trên luồng chính.

Để tạo ảnh động cho khung hình chính, chúng ta bước từ 0 đến 100 và tính toán giá trị tỷ lệ cần thiết cho phần tử và nội dung của phần tử đó. Sau đó, các đối tượng này có thể được chuyển nhỏ thành một chuỗi và có thể được đưa vào trang dưới dạng một phần tử kiểu. Việc chèn các kiểu sẽ khiến tính năng Tính toán lại kiểu trên trang. Đây là công việc bổ sung mà trình duyệt phải thực hiện, nhưng sẽ chỉ thực hiện một lần khi thành phần này khởi động.

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}
    }`;
}

Vô cùng hiếu kỳ có thể sẽ thắc mắc về hàm ease() bên trong vòng lặp. Bạn có thể sử dụng nội dung như thế này để liên kết các giá trị từ 0 đến 1 đến một giá trị tương đương có mức độ dễ dàng tương đương.

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

Bạn cũng có thể sử dụng Google Tìm kiếm để lập biểu đồ. Tiện lợi! Nếu bạn cần sử dụng các phương trình gia tốc khác, hãy xem Tween.js của Soledad Penadés, trong đó có một tập hợp các phương trình gia tốc.

Bước 3: Bật ảnh động CSS

Với những ảnh động này được tạo và đưa vào trang trong JavaScript, bước cuối cùng là bật/tắt các lớp đang bật ảnh động.

.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;
}

Điều này sẽ chạy các ảnh động đã tạo ở bước trước. Vì các ảnh động bắt sáng đã được giảm bớt, nên bạn cần đặt hàm thời gian thành linear, nếu không, bạn sẽ dễ dàng chuyển đổi giữa từng khung hình chính và trông rất kỳ lạ!

Khi thu gọn phần tử trở lại, có hai lựa chọn: cập nhật ảnh động CSS để chạy ngược lại thay vì tiến. Việc này sẽ hiệu quả, nhưng "cảm giác" của ảnh động sẽ bị đảo ngược, vì vậy, nếu bạn sử dụng đường cong giảm tốc độ, chiều ngược lại sẽ được nới lỏng trong, tạo cảm giác chậm chạp. Một giải pháp thích hợp hơn là tạo một cặp ảnh động thứ hai để thu gọn phần tử. Bạn có thể tạo các ảnh động này theo cách chính xác như ảnh động khung hình chính mở rộng, nhưng với các giá trị bắt đầu và kết thúc được hoán đổi.

const xScale = 1 + (x - 1) * easedStep;
const yScale = 1 + (y - 1) * easedStep;

Phiên bản nâng cao hơn: hiển thị hình tròn

Bạn cũng có thể sử dụng kỹ thuật này để tạo ảnh động hình tròn mở rộng và thu gọn.

Nguyên tắc phần lớn giống như phiên bản trước, trong đó bạn điều chỉnh tỷ lệ một phần tử và ngược lại tỷ lệ phần tử con trực tiếp của phần tử đó. Trong trường hợp này, phần tử đang mở rộng có border-radius là 50%, làm cho phần tử này dạng tròn và được gói bởi một phần tử khácoverflow: hidden, có nghĩa là bạn không thấy vòng tròn mở rộng ra ngoài ranh giới phần tử.

Lưu ý về biến thể cụ thể này: Chrome có văn bản mờ trên màn hình DPI thấp trong quá trình tạo ảnh động do lỗi làm tròn do tỷ lệ và tỷ lệ bộ đếm của văn bản. Nếu bạn quan tâm đến thông tin chi tiết về yêu cầu đó, thì có một loại lỗi mà bạn có thể gắn dấu sao và theo dõi.

Bạn có thể tìm thấy mã cho hiệu ứng mở rộng vòng tròn trong kho lưu trữ GitHub.

Kết luận

Vậy là xong, bạn đã có một cách để tạo ảnh động cắt đoạn hiệu quả bằng cách sử dụng phép biến đổi tỷ lệ. Trong một thế giới hoàn hảo, sẽ thật tuyệt khi thấy các ảnh động của đoạn video được tăng tốc (có lỗi Chromium cho việc đó do Jake Archibald tạo ra), nhưng cho đến khi chúng ta đến đó, bạn nên thận trọng khi tạo ảnh động clip hoặc clip-path và chắc chắn tránh tạo ảnh động width hoặc height.

Bạn cũng có thể sử dụng Ảnh động trên web cho các hiệu ứng như thế này, vì chúng có API JavaScript nhưng có thể chạy trên chuỗi trình tổng hợp nếu bạn chỉ tạo ảnh động cho transformopacity. Tiếc là Ảnh động trên web chưa được hỗ trợ hiệu quả, nhưng bạn có thể dùng tính năng nâng cao tăng dần để dùng tính năng này nếu có.

if ('animate' in HTMLElement.prototype) {
    // Animate with Web Animations.
} else {
    // Fall back to generated CSS Animations or JS.
}

Cho đến khi điều đó thay đổi, mặc dù bạn có thể sử dụng các thư viện dựa trên JavaScript để tạo ảnh động, nhưng bạn có thể thấy rằng mình có hiệu suất đáng tin cậy hơn bằng cách xử lý ảnh động CSS và sử dụng ảnh động đó. Tương tự, nếu ứng dụng của bạn đã dựa vào JavaScript cho ảnh động, thì bạn có thể cải thiện hiệu quả hơn nhờ tối thiểu là nhất quán với cơ sở mã hiện có của mình.

Nếu bạn muốn xem qua mã cho hiệu ứng này, hãy xem kho lưu trữ GitHub về giao diện người dùng mẫu và như thường lệ, hãy cho chúng tôi biết cách bạn truy cập trong các nhận xét bên dưới.