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

Stephen McGruer
Stephen McGruer

TL;DR

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

Trước đây, chúng tôi đã đăng thông tin cập nhật về cách tạo hiệu ứng parallax (hiện tượng chênh lệch) và cuộn vô hạn hiệu quả. Trong bài đăng này, chúng ta sẽ xem xét những gì cần làm nếu bạn muốn tạo ảnh động trong đoạn video có hiệu suất cao. Nếu bạn muốn xem bản minh hoạ, hãy tham khảo kho lưu trữ GitHub về thành phần giao diện người dùng mẫu.

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

Một số lựa chọn tạo đối tượng này hiệu quả hơn các lựa chọn khác.

Không phù hợp: 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ể tưởng tượng 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 đề cấp thiết với phương pháp này là bạn cần 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 hình ảnh động, điều này có thể rất tốn kém và thường sẽ khiến bạn bỏ lỡ 60 khung hình/giây. Nếu bạn chưa biết về điều này, hãy đọc hướng dẫn về Hiệu suất kết xuất để biết thêm thông tin về cách hoạt động của quy trình kết xuất.

Không nên: Sử dụng thuộc tính clip hoặc clip-path của CSS

Thay vì tạo ảnh động cho widthheight, bạn có thể sử dụng thuộc tính clip (hiện không dùng nữa) để 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 tuyệt vọng, đây không phải là giải pháp mà bạn mong 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 so với việc 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 quá trình vẽ. Ngoài ra, thuộc tính clip, nếu đi theo tuyến đó, bạn phải yêu cầu phần tử mà thuộc tính đang hoạt động có vị trí tuyệt đối hoặc cố định. Điều này có thể khiến bạn phải thay đổi một chút.

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

Vì hiệu ứng này liên quan đến việc một đối tượng trở nên to 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à tính năng không yêu cầu bố cục hoặc vẽ mà trình duyệt có thể chuyển giao cho GPU, nghĩa là hiệu ứng đó được tăng tốc và khả năng đạt 60 khung hình/giây cao hơn đáng kể.

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

Bước 1: Tính trạng thái bắt đầu và 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 có cả khi thu gọn và khi mở rộng. Có thể trong một số trường hợp, bạn không thể nhận được cả hai thông tin này cùng một lúc và bạn cần phải bật/tắt một số lớp để có thể đọc nhiều trạng thái của thành phần. Tuy nhiên, nếu bạn cần làm như vậy, hãy thận trọng: getBoundingClientRect() (hoặc offsetWidthoffsetHeight) buộc trình duyệt chạy các kiểu và lượt truyền bố cục nếu các 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 của một trình đơn, chúng ta có thể giả định hợp lý rằng trình đơn đó sẽ bắt đầu ở tỷ lệ tự nhiên (1, 1). Tỷ lệ tự nhiên này thể hiện 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ỏ (đã tính toán ở trên) trở lại tỷ lệ tự nhiên đó.

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

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

  1. Biến đổi phản hồi cũng là một phép toán theo tỷ lệ. Điều này rất tốt vì bạn cũng có thể tăng tốc ảnh động này, giống như ảnh động trên vùng chứa. Bạn có thể cần đảm bảo rằng các phần tử đang tạo ảnh động có lớp trình kết hợp riêng (cho phép GPU hỗ trợ). Để làm việc này, 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 phản phải được tính cho mỗi 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 làm dịu, thì chính hàm làm dịu này cần được chống lại khi tạo ảnh động cho phép chuyển đổi ngược. Tuy nhiên, việc tính toán hàm số nghịch đảo cho — giả sử — cubic-bezier(0, 0, 0.3, 1) không phải là điều dễ dàng.

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

Bước 2: Tạo ảnh động CSS ngay lập tức

Giải pháp (có thể trông lạ mắt lúc đầu) là tạo ảnh động dạng khung hình chính bằng hàm làm dịu của riêng chúng ta và chèn ảnh động đó vào trang để trình đơn sử dụng. (Rất 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à ảnh động khung hình chính có thể biến đổi các phép biến đổi có thể chạy trên trình kết hợp, nghĩa là ảnh động không bị ảnh hưởng bởi các tác vụ trên luồng chính.

Để tạo ảnh động khung hình chính, chúng ta sẽ thực hiện từ 0 đến 100 và tính toán những giá trị tỷ lệ cần thiết cho phần tử và nội dung của phần tử đó. Sau đó, bạn có thể rút gọn các thuộc tính này thành một chuỗi, có thể được chèn 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 một lượt truyền Recalculate Styles (Tính toán lại kiểu) diễn ra trên trang. Đây là công việc bổ sung mà trình duyệt phải thực hiện, nhưng trình duyệt sẽ chỉ thực hiện một lần khi thành phần 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}
    }`;
}

Những người tò mò vô hạn có thể thắc mắc về hàm ease() bên trong vòng lặp for. Bạn có thể sử dụng một đoạn mã như sau để liên kết các giá trị từ 0 đến 1 với một giá trị tương đương được làm dịu.

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 để vẽ sơ đồ kết quả. Rất hữu ích! Nếu bạn cần các phương trình làm dịu khác, hãy xem Tween.js của Soledad Penadés. Thư viện này chứa rất nhiều phương trình làm dịu.

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

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

Khi thu gọn phần tử trở lại, bạn có hai lựa chọn: cập nhật ảnh động CSS để chạy ngược thay vì chạy xuôi. Cách này sẽ hoạt động tốt, 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 một đường cong giảm dần, thì hiệu ứng đảo ngược sẽ có cảm giác dần, khiến ảnh động có cảm giác chậm chạp. Một giải pháp phù hợp hơn là tạo cặp thứ hai ảnh động để thu gọn phần tử. Bạn có thể tạo các tệp này theo cách giống hệt như ảnh động mở rộng khung hình chính, nhưng với giá trị bắt đầu và giá trị 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 mở rộng và thu gọn hình tròn.

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

Cảnh báo về biến thể cụ thể này: Chrome có văn bản mờ trên màn hình có DPI thấp trong ảnh động do lỗi làm tròn do tỷ lệ và tỷ lệ ngược của văn bản. Nếu bạn quan tâm đến thông tin chi tiết về vấn đề đó, có một lỗi đã được báo cáo 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 hình tròn trong kho lưu trữ GitHub.

Kết luận

Như vậy, bạn đã biết cách tạo ảnh động cắt 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, thật tuyệt khi thấy ả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 đạt được điều đó, bạn nên thận trọng khi tạo ảnh động clip hoặc clip-path và tuyệt đối tránh tạo ảnh động width hoặc height.

Bạn cũng nên 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 luồng trình tổng hợp nếu bạn chỉ tạo ảnh động transformopacity. Rất tiếc, dịch vụ Ảnh động trên web không hỗ trợ tốt. Tuy nhiên, bạn có thể sử dụng tính năng nâng cao tăng dần (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ù 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ể nhận thấy hiệu suất đáng tin cậy hơn bằng cách tạo ả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 nên sử dụng ít nhất là nhất quán với cơ sở mã hiện có.

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