Bắt đầu sử dụng ảnh động dựa trên cuộn trong CSS

1. Trước khi bắt đầu

Ảnh động dựa trên thao tác cuộn cho phép bạn kiểm soát việc phát ảnh động dựa trên vị trí cuộn của một vùng chứa có thể cuộn. Điều này có nghĩa là khi bạn cuộn lên hoặc xuống, ảnh động sẽ tua đi hoặc tua lại. Ngoài ra, với ảnh động dựa trên thao tác cuộn, bạn cũng có thể kiểm soát ảnh động dựa trên vị trí của một phần tử trong vùng chứa cuộn. Nhờ đó, bạn có thể tạo ra những hiệu ứng thú vị, chẳng hạn như hình nền thị sai, thanh tiến trình cuộn và hình ảnh tự xuất hiện khi hiển thị.

Chrome 115 có tính năng mới là hỗ trợ một nhóm các lớp JavaScript và thuộc tính CSS giúp bạn dễ dàng tạo các ảnh động dựa trên thao tác cuộn theo cách khai báo. Các API mới này hoạt động cùng với API Web Animations và CSS Animations hiện có.

Lớp học lập trình này hướng dẫn bạn cách tạo ảnh động dựa trên thao tác cuộn bằng CSS. Khi hoàn thành lớp học lập trình này, bạn sẽ làm quen với nhiều thuộc tính CSS mới do tính năng thú vị này giới thiệu, chẳng hạn như scroll-timeline, view-timeline, animation-timelineanimation-range.

Kiến thức bạn sẽ học được

  • Cách tạo hiệu ứng nền thị sai bằng Dòng thời gian cuộn trong CSS.
  • Cách tạo thanh tiến trình bằng Dòng thời gian cuộn trong CSS.
  • Cách tạo hiệu ứng hiển thị hình ảnh bằng View Timeline trong CSS.
  • Cách nhắm đến các loại phạm vi khác nhau của Dòng thời gian xem trong CSS.

Bạn cần có

Một trong những tổ hợp thiết bị sau:

  • Chrome phiên bản gần đây (115 trở lên) trên ChromeOS, macOS hoặc Windows có cờ "Tính năng nền tảng web thử nghiệm" được đặt thành bật.
  • Có kiến thức cơ bản về HTML
  • Có kiến thức cơ bản về CSS, đặc biệt là ảnh động trong CSS

2. Bắt đầu thiết lập

Mọi thứ bạn cần cho dự án này đều có trong một kho lưu trữ GitHub. Để bắt đầu, hãy sao chép mã và mở mã đó trong môi trường phát triển mà bạn yêu thích.

  1. Mở một thẻ trình duyệt mới rồi chuyển đến https://github.com/googlechromelabs/io23-scroll-driven-animations-codelab.
  2. Sao chép kho lưu trữ.
  3. Mở mã trong IDE mà bạn muốn.
  4. Chạy npm install để cài đặt các phần phụ thuộc.
  5. Chạy npm start và truy cập vào http://localhost:3000/.
  6. Ngoài ra, nếu bạn chưa cài đặt npm, hãy mở tệp src/index.html trong Chrome.

3. Tìm hiểu về dòng thời gian của ảnh động

Theo mặc định, ảnh động được đính kèm vào một phần tử sẽ chạy trên Dòng thời gian của tài liệu. Điều đó có nghĩa là khi trang tải, ảnh động sẽ tiến về phía trước khi thời gian trôi qua. Đây là dòng thời gian mặc định của ảnh động và cho đến nay, đây là dòng thời gian duy nhất của ảnh động mà bạn có quyền truy cập.

Với ảnh động dựa trên thao tác cuộn, bạn sẽ có quyền truy cập vào 2 loại dòng thời gian mới:

  • Thanh tiến trình cuộn
  • Xem Dòng thời gian tiến trình

Trong CSS, bạn có thể đính kèm các dòng thời gian này vào một ảnh động bằng cách sử dụng thuộc tính animation-timeline. Hãy xem ý nghĩa của những mốc thời gian mới này và điểm khác biệt giữa chúng.

Thanh tiến trình cuộn

Dòng thời gian tiến trình cuộn là một dòng thời gian của ảnh động được liên kết với tiến trình ở vị trí cuộn của một vùng chứa có thể cuộn (còn gọi là cổng cuộn hoặc trình cuộn) dọc theo một trục cụ thể. Thao tác này chuyển đổi một vị trí trong phạm vi cuộn thành tỷ lệ phần trăm tiến trình dọc theo dòng thời gian.

Vị trí bắt đầu cuộn biểu thị tiến trình 0% và vị trí kết thúc cuộn biểu thị tiến trình 100%. Trong hình ảnh trực quan sau đây, hãy lưu ý rằng tiến trình tăng từ 0% lên 100% khi bạn di chuyển xuống dưới trong thanh cuộn.

Xem Dòng thời gian tiến trình

Loại tiến trình này được liên kết với tiến trình tương đối của một phần tử cụ thể trong vùng chứa có thể cuộn. Giống như một Dòng thời gian tiến trình cuộn, độ lệch cuộn của trình cuộn cũng được theo dõi. Không giống như Dòng thời gian tiến trình cuộn, vị trí tương đối của một đối tượng trong thành phần có thể cuộn đó sẽ xác định tiến trình. Điều này có thể so sánh với IntersectionObserver, giúp theo dõi mức độ hiển thị của một phần tử trong trình cuộn. Nếu phần tử không xuất hiện trong trình cuộn, thì phần tử đó sẽ không giao nhau. Nếu phần tử đó xuất hiện bên trong trình cuộn (ngay cả đối với phần nhỏ nhất), thì phần tử đó đang giao nhau.

Dòng thời gian tiến trình xem bắt đầu từ thời điểm một đối tượng bắt đầu giao nhau với thành phần cuộn và kết thúc khi đối tượng đó ngừng giao nhau với thành phần cuộn. Trong hình ảnh minh hoạ sau đây, lưu ý rằng tiến trình bắt đầu tăng từ 0% khi đối tượng đi vào vùng chứa có thể cuộn và đạt đến 100% vào thời điểm đối tượng rời khỏi vùng chứa có thể cuộn.

Theo mặc định, ảnh động được liên kết với Dòng thời gian tiến trình của khung hiển thị sẽ gắn vào toàn bộ phạm vi của dòng thời gian đó. Thời gian này bắt đầu từ thời điểm đối tượng xuất hiện trong khung hiển thị và kết thúc khi đối tượng rời khỏi khung hiển thị.

Bạn cũng có thể liên kết chú thích này với một phần cụ thể của Dòng thời gian xem tiến trình bằng cách chỉ định phạm vi mà chú thích sẽ gắn vào. Ví dụ: chỉ khi đối tượng đang vào vùng cuộn. Trong hình ảnh trực quan sau đây, tiến trình bắt đầu tăng từ 0% khi đối tượng đi vào vùng chứa có thể cuộn nhưng đã đạt đến 100% kể từ thời điểm đối tượng hoàn toàn giao nhau.

Các phạm vi Dòng thời gian có thể nhắm đến là cover, contain, entry, exit, entry-crossingexit-crossing. Các dải này sẽ được giải thích ở phần sau của lớp học lập trình này. Tuy nhiên, nếu bạn không thể chờ đợi, hãy sử dụng công cụ tại https://goo.gle/view-timeline-range-tool để xem mỗi dải đại diện cho điều gì.

4. Tạo hiệu ứng nền thị sai

Hiệu ứng đầu tiên cần thêm vào trang là hiệu ứng nền thị sai trên hình nền chính. Khi bạn di chuyển xuống trang, hình nền sẽ di chuyển, mặc dù với tốc độ khác. Để làm được điều này, bạn phải dựa vào Dòng thời gian tiến trình cuộn.

Để triển khai việc này, bạn cần thực hiện 2 bước:

  1. Tạo một ảnh động di chuyển vị trí của hình nền.
  2. Liên kết ảnh động với tiến trình cuộn của tài liệu.

Tạo ảnh động

  1. Để tạo ảnh động, hãy sử dụng một bộ khoá khung hình thông thường. Trong đó, hãy di chuyển vị trí nền theo chiều dọc từ 0% đến 100%:

src/styles.css

@keyframes move-background {
  from {
    background-position: 50% 0%;
  }
  to {
    background-position: 50% 100%;
  }
}
  1. Bây giờ, hãy đính kèm các khung hình chính này vào phần tử nội dung:

src/styles.css

body {
  animation: 1s linear move-background;
}

Với mã này, ảnh động move-background sẽ được thêm vào phần tử nội dung. Thuộc tính animation-duration của thành phần này được đặt thành một giây và sử dụng hiệu ứng chuyển động linear.

Cách dễ nhất để tạo dòng thời gian Scroll Progress (Tiến trình cuộn) là sử dụng hàm scroll(). Thao tác này sẽ tạo một Dòng thời gian tiến trình cuộn ẩn danh mà bạn có thể đặt làm giá trị cho thuộc tính animation-timeline.

Hàm scroll() chấp nhận một đối số <scroller> và một đối số <axis>.

Các giá trị được chấp nhận cho đối số <scroller> là:

  • nearest. Sử dụng vùng chứa cuộn của phần tử tổ tiên gần nhất (mặc định).
  • root. Sử dụng khung hiển thị tài liệu làm vùng chứa cuộn.
  • self. Sử dụng chính phần tử này làm vùng chứa cuộn.

Các giá trị được chấp nhận cho đối số <axis> là:

  • block. Sử dụng thước đo tiến trình dọc theo trục khối của vùng chứa có thể cuộn (mặc định).
  • inline. Sử dụng chỉ số về tiến trình dọc theo trục nội tuyến của vùng chứa có thể cuộn.
  • y. Sử dụng thước đo tiến trình dọc theo trục y của vùng chứa cuộn.
  • x. Sử dụng chỉ số đo lường tiến trình dọc theo trục x của vùng chứa cuộn.

Để liên kết ảnh động với trình cuộn gốc trên trục khối, các giá trị cần truyền vào scroll()rootblock. Tổng giá trị là scroll(root block).

  • Đặt scroll(root block) làm giá trị cho thuộc tính animation-timeline trên phần nội dung.
  • Ngoài ra, vì animation-duration được biểu thị bằng giây không có ý nghĩa gì, hãy đặt thời lượng thành auto. Nếu bạn không chỉ định animation-duration, thì giá trị mặc định sẽ là auto.

src/styles.css

body {
  animation: linear move-background;
  animation-duration: auto;
  animation-timeline: scroll(root block);
}

Vì trình cuộn gốc cũng là trình cuộn mẹ gần nhất cho phần tử nội dung, nên bạn cũng có thể sử dụng giá trị nearest:

src/styles.css

body {
  animation: linear move-background;
  animation-duration: auto;
  animation-timeline: scroll(nearest block);
}

nearestblock là các giá trị mặc định, nên bạn thậm chí có thể chọn bỏ qua các giá trị này. Trong trường hợp đó, bạn có thể đơn giản hoá mã thành như sau:

src/styles.css

body {
  animation: linear move-background;
  animation-duration: auto;
  animation-timeline: scroll();
}

Xác minh các thay đổi

Nếu mọi thứ đều ổn, thì giờ bạn sẽ có:

Nếu không, hãy kiểm tra nhánh solution-step-1 của mã.

5. Tạo một thanh tiến trình cho thư viện hình ảnh

Trên trang này có một băng chuyền ngang cần thanh tiến trình để cho biết bạn đang xem ảnh nào.

Mã đánh dấu cho băng chuyền có dạng như sau:

src/index.html

<div class="gallery">
  <div class="gallery__scrollcontainer" style="--num-images: 3;">
    <div class="gallery__progress"></div>
    <div class="gallery__entry">
      ...
    </div>
    <div class="gallery__entry">
      ...
    </div>
    <div class="gallery__entry">
      ...
    </div>
  </div>
</div>

Các khung hình chính cho thanh tiến trình đã được đặt và có dạng như sau:

src/styles.css

@keyframes adjust-progress {
  from {
    transform: scaleX(calc(1 / var(--num-images)));
  }
  to {
    transform: scaleX(1);
  }
}

Ảnh động này cần được đính kèm vào .Phần tử gallery__progress có Dòng thời gian tiến trình cuộn. Như minh hoạ trong bước trước, bạn có thể thực hiện việc này bằng cách tạo một Scroll Progress Timeline ẩn danh bằng hàm scroll():

src/styles.css

.gallery__progress {
  animation: linear adjust-progress;
  animation-duration: auto;
  animation-timeline: scroll(nearest inline);
}

Mặc dù đoạn mã này có vẻ như sẽ hoạt động, nhưng thực tế thì không phải do cách hoạt động của các lệnh tra cứu vùng chứa cuộn tự động bằng nearest. Khi tra cứu trình cuộn gần nhất, phần tử sẽ chỉ xem xét những phần tử có thể ảnh hưởng đến vị trí của phần tử đó. Vì .gallery__progress được định vị tuyệt đối, nên phần tử mẹ đầu tiên sẽ xác định vị trí của phần tử này là phần tử .gallery vì phần tử này đã áp dụng position: relative. Điều này có nghĩa là phần tử .gallery__scrollcontainer (là trình cuộn cần được nhắm đến) hoàn toàn không được xem xét trong quá trình tra cứu tự động này.

Để khắc phục vấn đề này, hãy tạo một Scroll Progress Timeline (Dòng thời gian tiến trình cuộn) có tên trên phần tử .gallery__scrollcontainer và liên kết .gallery__progress với phần tử đó bằng tên đó.

Để tạo một Scroll Progress Timeline (Dòng thời gian tiến trình cuộn) có tên trên một phần tử, hãy đặt thuộc tính scroll-timeline-name CSS trên vùng chứa cuộn thành một giá trị mà bạn thích. Giá trị phải bắt đầu bằng --.

Vì thư viện cuộn theo chiều ngang, nên bạn cũng cần đặt thuộc tính scroll-timeline-axis. Các giá trị được phép giống với đối số <axis> của scroll().

Cuối cùng, để liên kết ảnh động với Dòng thời gian tiến trình cuộn, hãy đặt thuộc tính animation-timeline trên phần tử cần tạo ảnh động thành cùng giá trị với giá trị nhận dạng được dùng cho scroll-timeline-name.

  • Thay đổi tệp styles.css để tệp này chứa nội dung sau:

src/styles.css

.gallery__scrollcontainer {
  /* Create the gallery-is-scrolling timeline */
  scroll-timeline-name: --gallery-is-scrolling;
  scroll-timeline-axis: inline;
}

.gallery__progress {
  animation: linear adjust-progress;
  animation-duration: auto;
  /* Set gallery-is-scrolling as the timeline */
  animation-timeline: --gallery-is-scrolling;
}

Xác minh các thay đổi

Nếu mọi thứ đều ổn, thì giờ bạn sẽ có:

Nếu không, hãy kiểm tra nhánh solution-step-2 của mã.

6. Tạo ảnh động cho hình ảnh trong thư viện khi chúng xuất hiện và biến mất khỏi vùng cuộn

Thiết lập Dòng thời gian tiến trình xem ẩn danh

Một hiệu ứng đẹp mà bạn có thể thêm là làm mờ dần hình ảnh trong thư viện khi chúng xuất hiện. Để làm việc này, bạn có thể sử dụng Dòng thời gian xem tiến trình.

Để tạo Dòng thời gian tiến trình của Khung hiển thị, bạn có thể dùng hàm view(). Các đối số được chấp nhận là <axis><view-timeline-inset>.

  • <axis> giống như trong Dòng thời gian tiến trình cuộn và xác định trục cần theo dõi.
  • Với <view-timeline-inset>, bạn có thể chỉ định một độ lệch (dương hoặc âm) để điều chỉnh ranh giới khi một phần tử được coi là có trong khung hiển thị hay không.
  • Các khung hình chính đã có sẵn, vì vậy bạn chỉ cần đính kèm chúng. Để thực hiện việc này, hãy tạo một Dòng thời gian tiến trình xem trên mỗi phần tử .gallery__entry.

src/styles.css

@keyframes animate-in {
  from {
    opacity: 0;
    clip-path: inset(50% 0% 50% 0%);
  }
  to {
    opacity: 1;
    clip-path: inset(0% 0% 0% 0%);
  }
}

.gallery__entry {
  animation: linear animate-in;
  animation-duration: auto;
  animation-timeline: view(inline);
}

Giới hạn phạm vi của Dòng thời gian tiến trình xem

Nếu lưu CSS và tải trang, bạn sẽ thấy các phần tử mờ dần, nhưng có vẻ như có gì đó không ổn. Các phần tử bắt đầu ở độ mờ 0 khi hoàn toàn nằm ngoài khung hiển thị và chỉ kết thúc ở độ mờ 1 khi đã thoát hoàn toàn.

Đó là do phạm vi mặc định cho Dòng thời gian tiến trình xem là toàn bộ phạm vi. Đây được gọi là dải cover.

  1. Để chỉ nhắm đến phạm vi entry của đối tượng, hãy dùng thuộc tính animation-range CSS để giới hạn thời điểm chạy ảnh động.

src/styles.css

.gallery__entry {
  animation: linear fade-in;
  animation-duration: auto;
  animation-timeline: view(inline);
  animation-range: entry 0% entry 100%;
}

Giờ đây, ảnh động sẽ chạy từ entry 0% (đối tượng sắp vào trình cuộn) đến entry 100% (đối tượng đã vào trình cuộn hoàn toàn).

Sau đây là các phạm vi có thể xem theo dòng thời gian:

  • cover. Biểu thị toàn bộ phạm vi của dòng thời gian tiến trình xem.
  • entry. Biểu thị phạm vi trong đó hộp chính đang đi vào phạm vi hiển thị tiến trình xem.
  • exit. Biểu thị phạm vi trong đó hộp chính đang thoát khỏi phạm vi hiển thị tiến trình xem.
  • entry-crossing. Biểu thị phạm vi trong đó hộp chính vượt qua cạnh đường viền cuối.
  • exit-crossing. Biểu thị phạm vi trong đó hộp chính vượt qua cạnh đường viền bắt đầu.
  • contain. Biểu thị phạm vi mà hộp chính được chứa hoàn toàn hoặc bao phủ hoàn toàn phạm vi hiển thị tiến trình xem trong cổng cuộn. Điều này phụ thuộc vào việc chủ thể cao hơn hay thấp hơn thanh cuộn.

Sử dụng công cụ tại https://goo.gle/view-timeline-range-tool để xem mỗi phạm vi đại diện cho điều gì và tỷ lệ phần trăm ảnh hưởng đến vị trí bắt đầu và vị trí kết thúc như thế nào.

  1. Vì phạm vi bắt đầu và phạm vi kết thúc ở đây giống nhau và các độ lệch mặc định được sử dụng, hãy đơn giản hoá animation-range thành một tên phạm vi hoạt ảnh duy nhất:

src/styles.css

.gallery__entry {
  animation: linear animate-in;
  animation-duration: auto;
  animation-timeline: view(inline);
  animation-range: entry;
}
  • Để làm mờ hình ảnh khi chúng thoát khỏi trình cuộn, bạn có thể làm tương tự như ảnh động animate-in nhưng nhắm đến một dải ô khác.

src/styles.css

@keyframes animate-out {
  from {
    opacity: 1;
    clip-path: inset(0% 0% 0% 0%);
  }
  to {
    opacity: 0;
    clip-path: inset(50% 0% 50% 0%);
  }
}

.gallery__entry {
  animation: linear animate-in, linear animate-out;
  animation-duration: auto;
  animation-timeline: view(inline);
  animation-range: entry, exit;
}

Các khung hình chính animate-in sẽ được áp dụng cho dải entry và các khung hình chính animate-out sẽ được áp dụng cho dải exit.

Xác minh các thay đổi

Nếu mọi thứ đều ổn, thì giờ bạn sẽ có:

Nếu không, hãy kiểm tra nhánh solution-step-3 của mã.

7. Tạo ảnh động cho hình ảnh trong thư viện khi chúng xuất hiện và biến mất khỏi cổng cuộn bằng cách sử dụng một bộ khung hình chính

Trường hợp cho một nhóm khung hình chính

Thay vì đính kèm 2 ảnh động vào các dải ô khác nhau, bạn có thể tạo một bộ khoá khung hình đã chứa thông tin về dải ô.

Hình dạng của các khung hình chính sẽ có dạng như sau:

@keyframes keyframes-name {
  range-name range-offset {
    ...
  }
  range-name range-offset {
    ...
  }
}
  1. Kết hợp khung hình chính mờ dần và mờ dần như sau:

src/styles.css

@keyframes animate-in-and-out {
  entry 0% {
    opacity: 0;
    clip-path: inset(50% 0% 50% 0%);
  }
  entry 90% {
    opacity: 1;
    clip-path: inset(0% 0% 0% 0%);
  }

  exit 10% {
    opacity: 1;
    clip-path: inset(0% 0% 0% 0%);
  }
  exit 100% {
    opacity: 0;
    clip-path: inset(50% 0% 50% 0%);
  }
}
  1. Khi thông tin về phạm vi xuất hiện trong khung hình chính, bạn không cần chỉ định riêng animation-range nữa. Đính kèm khung hình chính dưới dạng thuộc tính animation.

src/styles.css

.gallery__entry {
  animation: linear animate-in-and-out both;
  animation-duration: auto;
  animation-timeline: view(inline);
}

Xác minh các thay đổi

Nếu mọi thứ diễn ra suôn sẻ, bạn sẽ nhận được kết quả tương tự như ở bước trước. Nếu không, hãy kiểm tra nhánh solution-step-4 của mã.

8. Xin chúc mừng!

Bạn đã hoàn thành lớp học lập trình này và giờ đây bạn đã biết cách tạo Dòng thời gian tiến trình cuộn và Dòng thời gian tiến trình xem trong CSS!

Tìm hiểu thêm

Tài nguyên: