Thêm hiệu ứng xuất hiện và biến mất cho các phần tử khi chúng xuất hiện hoặc biến mất khỏi khung nhìn cuộn

Hiệu ứng xuất hiện và biến mất là ảnh động được kích hoạt khi một phần tử xuất hiện hoặc biến mất khỏi khung nhìn. Bạn có thể dùng tính năng này để tạo trải nghiệm hấp dẫn và linh hoạt cho người dùng. Ví dụ: bạn có thể sử dụng hiệu ứng xuất hiện để làm mờ một phần tử khi phần tử đó xuất hiện trong khung nhìn hoặc hiệu ứng thoát để thu nhỏ phần tử đó khi phần tử đó ra khỏi khung nhìn.

Cách triển khai

Để thêm hiệu ứng xuất hiện và biến mất vào một phần tử, bạn cần kết hợp một vài thuộc tính CSS. Sau đây là hướng dẫn từng bước:

  1. Tạo @keyframes riêng biệt cho ảnh động vào cảnh và thoát cảnh. Ảnh động vào cảnh sẽ được áp dụng khi phần tử vào khung nhìn và ảnh động thoát cảnh sẽ được áp dụng khi phần tử rời khỏi khung nhìn.

    @keyframes slide-in {
      from { transform: translateX(-100%); }
    }
    @keyframes slide-out {
      to { transform: translateX(100%); }
    }
    
  2. Đính kèm khung hình chính vào và ra vào phần tử. Bạn có thể thực hiện việc này bằng cách xác định nhiều ảnh động trong thuộc tính animation.

    • Cung cấp cho ảnh động vào cảnh một animation-fill-mode của backwards để áp dụng trạng thái ban đầu trước khi ảnh động bắt đầu.
    • Đặt animation-fill-mode của ảnh động thoát thành forwards để ảnh động duy trì trạng thái cuối cùng sau khi hoàn tất.
    .animated-element {
      animation:
        slide-in 1s linear backwards,
        slide-out 1s linear forwards;
    }
    
  3. Tạo một View Timeline và liên kết với ảnh động. Dòng thời gian của khung hiển thị là một loại dòng thời gian được liên kết với chế độ hiển thị của một phần tử trong khung nhìn. Bạn có thể tạo một bộ nội suy bằng hàm view(), sau đó áp dụng bộ nội suy đó cho ảnh động bằng thuộc tính animation-timeline.

    .animated-element {
      animation-timeline: view();
    }
    

    Theo mặc định, view() theo dõi phần tử trên trục block. Nếu cần theo dõi trên trục inline, bạn có thể sử dụng view(inline).

  4. Giới hạn ảnh động trong phạm vi entry và exit. Thuộc tính animation-range cho phép bạn chỉ định phần nào của dòng thời gian mà ảnh động sẽ chạy.

    • Phạm vi entry bao gồm thời gian từ khi phần tử lần đầu tiên xuất hiện trong khung nhìn cho đến khi phần tử đó hiển thị đầy đủ.
    • Phạm vi exit bao gồm thời gian từ khi phần tử bắt đầu rời khỏi khung nhìn cho đến khi phần tử đó bị ẩn hoàn toàn.
    .animated-element {
      animation-range: entry, exit;
    }
    

Mã mẫu

Mã này tạo ảnh động cho các phần tử con trực tiếp của trình cuộn khi cuộn bằng cách sử dụng view-timeline ẩn danh:

@media (prefers-reduced-motion: no-preference) {
  @supports ((animation-timeline: view()) and (animation-range: entry)) {
    @keyframes grow {
      from {
        scale: 0.5;
      }
    }
    @keyframes shrink {
      to {
        scale: 0.5;
      }
    }

    .scroller > * {
      animation:
        grow auto linear backwards,
        shrink auto linear forwards;
      animation-timeline: view(inline);
      animation-range: entry, exit;
    }
  }
}

Khi các phần tử đi vào khung nhìn cuộn, ảnh động grow sẽ được phát và khi các phần tử rời khỏi khung nhìn cuộn, ảnh động shrink sẽ được phát.

Đoạn mã sau đây có cùng kết quả hình ảnh, nhưng tạo ảnh động cho các phần tử con trực tiếp của trình cuộn khi cuộn bằng view-timeline được đặt tên:

@media (prefers-reduced-motion: no-preference) {
  @supports ((animation-timeline: view()) and (animation-range: entry)) {
    @keyframes grow {
      from {
        scale: 0.5;
      }
    }
    @keyframes shrink {
      to {
        scale: 0.5;
      }
    }

    .scroller > * {
      view-timeline: --tl inline;
      animation:
        grow auto linear backwards,
        shrink auto linear forwards;
      animation-timeline: --tl;
      animation-range: entry, exit;
    }
  }
}

Các phương pháp hay nhất

Khi sử dụng ảnh động dựa trên thao tác cuộn, bạn cần tuân thủ một số phương pháp hay nhất để đảm bảo trải nghiệm mượt mà và dễ tiếp cận:

  • NÊN thêm tính năng phát hiện: Không phải trình duyệt nào cũng hỗ trợ ảnh động dựa trên thao tác cuộn. Sử dụng @supports ((animation-timeline: view()) and (animation-range: entry)) để kiểm tra khả năng hỗ trợ và cung cấp phương án dự phòng cho các trình duyệt không hỗ trợ.
    • BẮT BUỘC phải đưa quy trình kiểm tra (animation-range: entry) vào đây để lọc ra những trình duyệt chỉ hỗ trợ một phần.
    • KHÔNG sử dụng gói scroll-timeline-polyfill cho chiến lược dự phòng vì gói này chưa hoàn thiện tính năng và có nhiều vấn đề đã biết.
    • Nếu ảnh động chỉ được coi là để trang trí, hãy chọn Progressive Enhancement (Cải tiến tăng dần) và KHÔNG cung cấp phương án dự phòng.
  • NÊN tôn trọng lựa chọn ưu tiên của người dùng: Một số người dùng muốn có ít chuyển động hơn trên web. Sử dụng truy vấn nội dung nghe nhìn prefers-reduced-motion để tắt hoặc giảm ảnh động cho những người dùng này.
  • NÊN cố gắng chỉ tạo ảnh động cho các thuộc tính CSS hiệu suất cao: Để có ảnh động mượt mà nhất, hãy chỉ tạo ảnh động cho các thuộc tính có thể được xử lý bằng luồng trình tổng hợp của trình duyệt, chẳng hạn như transform và opacity. Việc tạo ảnh động cho các thuộc tính khác như width hoặc height có thể dẫn đến các vấn đề về hiệu suất.
  • NÊN sử dụng đúng thứ tự khai báo: Khi sử dụng thuộc tính viết tắt animation, hãy khai báo animation-timeline sau thuộc tính đó để ngăn thuộc tính viết tắt đặt lại dòng thời gian.

Khi sử dụng hàm view() để tạo ảnh động dựa trên thao tác cuộn:

  • KHÔNG BẮT BUỘC nêu rõ trục cần theo dõi: Khi không nhắm đến trục block mặc định (chẳng hạn như trong trình cuộn ngang), hãy nêu rõ trục cần theo dõi bằng view(block) hoặc view(inline).
  • Khi ảnh động không được áp dụng cho chính đối tượng được theo dõi, hãy sử dụng dòng thời gian của khung hiển thị được đặt tên.

Khi sử dụng thuộc tính view-timeline để tạo ảnh động dựa trên thao tác cuộn:

  • NÊN sử dụng <dashed-ident> CSS cho tên.
  • KHÔNG BẮT BUỘC nêu rõ trục cần theo dõi: Khi không nhắm đến trục block mặc định (chẳng hạn như trong trình cuộn ngang), hãy nêu rõ trục cần theo dõi bằng view-timeline-axis.
  • NÊN đảm bảo phạm vi của hoạt động tra cứu hoạt động: Khi phần tử khai báo view-timeline không phải là phần tử cấp trên dạng cây phẳng của phần tử ảnh động, hãy nâng chế độ hiển thị tên của view-timeline bằng cách sử dụng timeline-scope trên một phần tử cấp trên được chia sẻ.

Ưu tiên view-timeline được đặt tên khi nhiều phần tử hoặc phần tử con của đối tượng được theo dõi cần tạo ảnh động.

Hỗ trợ trình duyệt và chiến lược dự phòng

Do đó, bạn thường cần có một chiến lược dự phòng.

Đối với những trình duyệt không hỗ trợ ảnh động dựa trên thao tác cuộn, bạn có thể sử dụng tính năng dự phòng để tạo lại hiệu ứng hình ảnh. Các phương án dự phòng thường được xây dựng bằng trình nghe cuộn (cho hiệu ứng ScrollTimeline) hoặc API IntersectionObserver (cho hiệu ứng ViewTimeline).

Trong các trình duyệt có hỗ trợ tích hợp cho ảnh động dựa trên thao tác cuộn, hãy LUÔN sử dụng phương thức triển khai CSS gốc vì các trình duyệt đó có hiệu suất cao hơn.

Xin lưu ý rằng không phải hiệu ứng nào cũng có thể được tạo lại bằng phương pháp dự phòng.

Cụ thể cho trường hợp sử dụng này, tập lệnh sau đây áp dụng phương án dự phòng cho các trình duyệt không hỗ trợ ảnh động dựa trên thao tác cuộn. Hàm này sử dụng IntersectionObserver để theo dõi chế độ hiển thị của phần tử .wrapper và cập nhật thuộc tính transform của các lớp dựa trên vị trí cuộn.

<script>
  if (!CSS.supports('(animation-timeline: view()) and (animation-range: entry)')) {
    const observer = new IntersectionObserver(
      (entries) => {
        for (const entry of entries) {
          // This matches the effect as defined in the CSS example above.
          // Customize this further if needed.
          entry.target.style.scale = 0.5 + entry.intersectionRatio * 0.5;
        }
      },
      {
        threshold: Array.from({ length: 101 }, (_, i) => i / 100),
      }
    );

    document.querySelectorAll('.scroller > *').forEach((el) => {
      observer.observe(el);
    });
  }
</script>