Bốn tính năng CSS mới giúp hoạt ảnh nhập và thoát mượt mà

Chuyển động là một phần cốt lõi của mọi trải nghiệm kỹ thuật số, hướng dẫn người dùng từ lượt tương tác này đến lượt tương tác khác. Tuy nhiên, có một vài khoảng trống trong ảnh động mượt mà trên nền tảng web. Các tính năng này bao gồm khả năng dễ dàng tạo ảnh động khi mở và đóng, cũng như tạo ảnh động mượt mà đến và đi từ lớp trên cùng cho các phần tử có thể đóng như hộp thoại và cửa sổ bật lên.

Để lấp đầy những khoảng trống này, Chrome 116 và 117 có 4 tính năng mới dành cho nền tảng web, cho phép tạo ảnh động và hiệu ứng chuyển đổi mượt mà cho các thuộc tính riêng biệt.

4 tính năng mới này bao gồm:

  • Khả năng tạo ảnh động displaycontent-visibility trên dòng thời gian khung hình chính (Từ Chrome 116).
  • Thuộc tính transition-behavior với từ khoá allow-discrete để bật hiệu ứng chuyển đổi của các thuộc tính riêng biệt như display (Từ Chrome 117).
  • Quy tắc @starting-style để tạo hiệu ứng động cho mục nhập từ display: none và vào lớp trên cùng (Từ Chrome 117).
  • Thuộc tính overlay để kiểm soát hành vi của lớp trên cùng trong ảnh động (Từ Chrome 117).

Hiển thị ảnh động trong khung hình chính

Kể từ Chrome 116, bạn có thể sử dụng displaycontent-visibility trong các quy tắc khung hình chính. Sau đó, các hình ảnh này sẽ hoán đổi tại thời điểm khung hình chính xuất hiện. Bạn không cần thêm giá trị mới nào để hỗ trợ tính năng này:

.card {
  animation: fade-out 0.5s forwards;
}

@keyframes fade-out {
  100% {
    opacity: 0;
    display: none;
  }
}

Ví dụ trước tạo ảnh động độ mờ thành 0 trong khoảng thời gian 0,5s và sau đó đặt hiển thị thành không có. Ngoài ra, từ khoá forwards đảm bảo ảnh động vẫn ở trạng thái kết thúc để phần tử được áp dụng vẫn là display: noneopacity: 0.

Đây là một ví dụ đơn giản mô phỏng những gì bạn có thể làm với hiệu ứng chuyển đổi (xem bản minh hoạ trong phần hiệu ứng chuyển đổi). Tuy nhiên, hiệu ứng chuyển đổi không thể tạo ảnh động phức tạp hơn, chẳng hạn như ví dụ sau:

.card {
  animation: spin-and-delete 1s ease-in forwards;
}

@keyframes spin-and-delete {
  0% {
    transform: rotateY(0);
    filter: hue-rotate(0);
  }
  80% {
    transform: rotateY(360deg);
    filter: hue-rotate(180deg);
    opacity: 1;
  }
  100% {
    opacity: 0;
    display: none;
  }
}

Ảnh động spin-and-delete là ảnh động thoát. Trước tiên, thẻ sẽ quay trên trục y, chạy theo chế độ xoay màu, sau đó tại 80% theo dòng thời gian, chuyển đổi độ mờ từ 1 sang 0. Cuối cùng, thẻ này sẽ được đổi từ display: block thành display: none.

Đối với các ảnh động thoát này, thay vì áp dụng trực tiếp cho một phần tử, bạn có thể thiết lập điều kiện kích hoạt cho ảnh động. Chẳng hạn như bằng cách đính kèm trình nghe sự kiện vào nút kích hoạt một lớp để áp dụng ảnh động, như sau:

.spin-out {
   animation: spin-and-delete 1s ease-in forwards;
}
document.querySelector('.delete-btn').addEventListener('click', () => {
 document.querySelector('.card').classList.add('spin-out');
})

Ví dụ trên hiện có trạng thái kết thúc là display:none. Có nhiều trường hợp bạn sẽ muốn thực hiện thêm và xoá nút DOM với thời gian chờ để cho phép ảnh động hoàn tất trước.

Chuyển đổi ảnh động rời rạc

Không giống như khi tạo ảnh động cho các thuộc tính riêng biệt bằng khung hình chính, để chuyển đổi các thuộc tính riêng biệt, bạn cần sử dụng chế độ hành vi chuyển đổi allow-discrete.

Thuộc tính transition-behavior

Chế độ allow-discrete là chế độ cho phép chuyển đổi riêng biệt và là giá trị của thuộc tính transition-behavior. transition-behavior chấp nhận hai giá trị: normalallow-discrete.

.card {
  transition: opacity 0.25s, display 0.25s;
  transition-behavior: allow-discrete; /* Note: be sure to write this after the shorthand */
}

.card.fade-out {
  opacity: 0;
  display: none;
}
Lưu ý: Bản minh hoạ hiệu ứng chuyển đổi này sử dụng một kỹ thuật khác với bản minh hoạ ảnh động đầu tiên nhưng về mặt hình ảnh thì tương tự.

Mã viết tắt transition cũng đặt giá trị này, vì vậy, bạn có thể bỏ qua thuộc tính này và sử dụng từ khoá allow-discrete ở cuối mã viết tắt transition cho mỗi hiệu ứng chuyển đổi.

.card {
  transition: opacity 0.5s, display 0.5s allow-discrete;
}

.card.fade-out {
  opacity: 0;
  display: none;
}

Nếu đang tạo ảnh động cho nhiều thuộc tính riêng biệt, bạn cần thêm allow-discrete sau mỗi thuộc tính mà bạn muốn tạo ảnh động. Ví dụ:

.card {
  transition: opacity 0.5s, display 0.5s allow-discrete, overlay 0.5s allow-discrete;
}

.card.fade-out {
  opacity: 0;
  display: none;
}

Quy tắc @starting-style cho ảnh động xuất hiện

Cho đến nay, bài viết này đã đề cập đến ảnh động thoát, để tạo ảnh động cho mục nhập, bạn cần sử dụng quy tắc @starting-style.

Sử dụng @starting-style để áp dụng một kiểu mà trình duyệt có thể tra cứu trước khi phần tử mở trên trang. Đây là trạng thái "trước khi mở" (nơi bạn đang tạo ảnh động).

/*  0. IS-OPEN STATE   */
/*  The state at which the element is open + transition logic */
.item {
  height: 3rem;
  display: grid;
  overflow: hidden;
  transition: opacity 0.5s, transform 0.5s, height 0.5s, display 0.5s allow-discrete;
}

/*  1. BEFORE-OPEN STATE   */
/*  Starting point for the transition */
@starting-style {
  .item {
    opacity: 0;
    height: 0;
  }
}

/*  2. EXITING STATE   */
/*  While it is deleting, before DOM removal in JS, apply this
    transformation for height, opacity, and a transform which
    skews the element and moves it to the left before setting
    it to display: none */
.is-deleting {
  opacity: 0;
  height: 0;
  display: none;
  transform: skewX(50deg) translateX(-25vw);
}

Bây giờ, bạn có cả trạng thái nhập và thoát cho các mục trong danh sách VIỆC CẦN LÀM này:

Tạo ảnh động cho các phần tử đến và đi từ lớp trên cùng

Để tạo ảnh động cho các phần tử đến và đi từ lớp trên cùng, hãy chỉ định @starting-style ở trạng thái "mở" để cho trình duyệt biết nơi tạo ảnh động. Đối với hộp thoại, trạng thái mở được xác định bằng thuộc tính [open]. Đối với một cửa sổ bật lên, hãy sử dụng lớp giả :popover-open.

Một ví dụ đơn giản về hộp thoại có thể có dạng như sau:

/*   0. IS-OPEN STATE   */
dialog[open] {
  translate: 0 0;
}

/*   1. BEFORE-OPEN STATE   */
@starting-style {
  dialog[open] {
    translate: 0 100vh;
  }
}

/*   2. EXIT STATE   */
dialog {
  transition: translate 0.7s ease-out, overlay 0.7s ease-out allow-discrete, display 0.7s ease-out allow-discrete;
  translate: 0 100vh;
}

Trong ví dụ tiếp theo, hiệu ứng khi vào và khi thoát là khác nhau. Nhập bằng cách tạo ảnh động từ cuối khung nhìn lên, thoát khỏi hiệu ứng đến đầu khung nhìn. Mã này cũng được viết bằng CSS lồng nhau để đóng gói trực quan hơn.

Khi tạo ảnh động cho một cửa sổ bật lên, hãy sử dụng lớp giả lập :popover-open thay vì thuộc tính open đã sử dụng trước đó.

.settings-popover {
  &:popover-open {
    /*  0. IS-OPEN STATE  */
    /*  state when popover is open, BOTH:
        what we're transitioning *in* to 
        and transitioning *out* from */
    transform: translateY(0);
    opacity: 1;

    /*  1. BEFORE-OPEN STATE  */
    /*  Initial state for what we're animating *in* from, 
        in this case: goes from lower (y + 20px) to center  */
    @starting-style {
      transform: translateY(20px);
      opacity: 0;
    }
  }
  
  /*  2. EXIT STATE  */
  /*  Initial state for what we're animating *out* to , 
      in this case: goes from center to (y - 50px) higher */
  transform: translateY(-50px);
  opacity: 0;
  
  /*  Enumerate transitioning properties, 
      including display and allow-discrete mode */
  transition: transform 0.5s, opacity 0.5s, display 0.5s allow-discrete;
}

Tài sản overlay

Cuối cùng, để làm mờ popover hoặc dialog khỏi lớp trên cùng, hãy thêm thuộc tính overlay vào danh sách hiệu ứng chuyển đổi. popoverdialog thoát khỏi các đoạn và phép biến đổi của đối tượng cấp trên, đồng thời đặt nội dung vào lớp trên cùng. Nếu bạn không chuyển đổi overlay, phần tử của bạn sẽ ngay lập tức quay lại trạng thái bị cắt, biến đổi và che khuất, đồng thời bạn sẽ không thấy quá trình chuyển đổi diễn ra.

[open] {
  transition: opacity 1s, display 1s allow-discrete;
}

Thay vào đó, hãy đưa overlay vào hiệu ứng chuyển đổi hoặc ảnh động để tạo ảnh động cho overlay cùng với các tính năng còn lại và đảm bảo nó vẫn nằm ở lớp trên cùng khi tạo ảnh động. Việc này sẽ trông mượt mà hơn nhiều.

[open] {
  transition: opacity 1s, display 1s allow-discrete, overlay 1s allow-discrete;
}

Ngoài ra, khi bạn có nhiều phần tử mở trong lớp trên cùng, lớp phủ sẽ giúp bạn kiểm soát quá trình chuyển đổi mượt mà vào và ra khỏi lớp trên cùng. Bạn có thể thấy sự khác biệt trong ví dụ đơn giản này. Nếu bạn không áp dụng overlay cho cửa sổ bật lên thứ hai khi chuyển đổi, thì trước tiên, cửa sổ này sẽ chuyển ra khỏi lớp trên cùng, nhảy ra sau cửa sổ bật lên khác trước khi bắt đầu chuyển đổi. Hiệu ứng này không mượt mà lắm.

Lưu ý về hiệu ứng chuyển đổi khung hiển thị

Nếu bạn đang thực hiện các thay đổi đối với DOM, chẳng hạn như thêm và xoá các phần tử khỏi DOM, thì hiệu ứng chuyển đổi khung hiển thị là một giải pháp tuyệt vời khác để tạo ảnh động mượt mà. Sau đây là hai trong số các ví dụ trên được tạo bằng hiệu ứng chuyển đổi thành phần hiển thị.

Trong bản minh hoạ đầu tiên này, thay vì thiết lập @starting-style và các phép biến đổi CSS khác, hiệu ứng chuyển đổi khung hiển thị sẽ xử lý hiệu ứng chuyển đổi. Hiệu ứng chuyển đổi khung hiển thị được thiết lập như sau:

Trước tiên, trong CSS, hãy cung cấp cho mỗi thẻ một view-transition-name riêng biệt.

.card-1 {
  view-transition-name: card-1;
}

.card-2 {
  view-transition-name: card-2;
}

/* etc. */

Sau đó, trong JavaScript, hãy gói quá trình thay đổi DOM (trong trường hợp này là xoá thẻ) trong một quá trình chuyển đổi chế độ xem.

deleteBtn.addEventListener('click', () => {
  // Check for browser support
  if (document.startViewTransition) {
    document.startViewTransition(() => {
      // DOM mutation
      card.remove();
    });
  } 
  // Alternative if no browser support
  else {
    card.remove();
  }
})

Giờ đây, trình duyệt có thể xử lý việc làm mờ và biến đổi từng thẻ thành vị trí mới.

Một ví dụ khác về việc tính năng này có thể hữu ích là bản minh hoạ thêm/xoá các mục trong danh sách. Trong trường hợp này, bạn cần phải thêm một view-transition-name duy nhất cho mỗi thẻ được tạo.

Kết luận

Các tính năng mới này giúp chúng tôi tiến gần hơn đến việc tạo ảnh động mượt mà khi mở và đóng trên nền tảng web. Để tìm hiểu thêm, hãy xem các đường liên kết sau: