Quảng cáo thị sai hiệu suất

Robert Flack
Robert Flack

Dù bạn có thích hay không, hiệu ứng thị sai vẫn sẽ tồn tại. Khi được sử dụng một cách hợp lý, hiệu ứng này có thể tăng độ sâu và sự tinh tế cho ứng dụng web. Tuy nhiên, vấn đề là việc triển khai hiệu ứng thị sai theo cách hiệu quả có thể là một thách thức. Trong bài viết này, chúng ta sẽ thảo luận về một giải pháp vừa hiệu quả, vừa quan trọng là hoạt động trên nhiều trình duyệt.

Hình minh hoạ hiệu ứng thị sai.

TL;DR

  • Đừng sử dụng sự kiện cuộn hoặc background-position để tạo ảnh động có hiệu ứng thị giác.
  • Sử dụng phép biến đổi 3D CSS để tạo hiệu ứng thị sai chính xác hơn.
  • Đối với Mobile Safari, hãy sử dụng position: sticky để đảm bảo hiệu ứng thị sai được truyền tải.

Nếu bạn muốn có giải pháp thả vào, hãy truy cập vào kho lưu trữ GitHub của Mẫu thành phần giao diện người dùng và lấy JS trình trợ giúp hiệu ứng thị giác Parallax! Bạn có thể xem bản minh hoạ trực tiếp về thanh cuộn có hiệu ứng thị giác 3D trong kho lưu trữ GitHub.

Vấn đề về hiệu ứng thị sai

Trước tiên, hãy cùng xem xét hai cách phổ biến để tạo hiệu ứng thị sai, và đặc biệt là lý do tại sao chúng không phù hợp với mục đích của chúng ta.

Không tốt: sử dụng sự kiện cuộn

Yêu cầu chính của hiệu ứng thị giác này là phải được kết hợp với thao tác cuộn; đối với mọi thay đổi trong vị trí cuộn của trang, vị trí của phần tử hiệu ứng thị giác này phải cập nhật. Mặc dù nghe có vẻ đơn giản, nhưng một cơ chế quan trọng của các trình duyệt hiện đại là khả năng hoạt động không đồng bộ. Trong trường hợp cụ thể của chúng ta, điều này áp dụng cho các sự kiện cuộn. Trong hầu hết các trình duyệt, các sự kiện cuộn được phân phối dưới dạng "tối đa" và không được đảm bảo sẽ được phân phối trên mọi khung hình của ảnh động cuộn!

Thông tin quan trọng này cho chúng ta biết lý do cần tránh giải pháp dựa trên JavaScript di chuyển các phần tử dựa trên sự kiện cuộn: JavaScript không đảm bảo rằng hiệu ứng thị giác sẽ phù hợp với vị trí cuộn của trang. Trong các phiên bản cũ của Safari dành cho thiết bị di động, các sự kiện cuộn thực sự được phân phối ở cuối thao tác cuộn, khiến bạn không thể tạo hiệu ứng cuộn dựa trên JavaScript. Các phiên bản mới hơn phân phối các sự kiện cuộn trong ảnh động, nhưng tương tự như Chrome, trên cơ sở "tối đa có thể". Nếu luồng chính đang bận với bất kỳ công việc nào khác, các sự kiện cuộn sẽ không được phân phối ngay lập tức, nghĩa là hiệu ứng thị sai sẽ bị mất.

Không tốt: cập nhật background-position

Một tình huống khác mà chúng ta muốn tránh là vẽ trên mọi khung hình. Nhiều giải pháp cố gắng thay đổi background-position để cung cấp hiệu ứng thị giác song song, khiến trình duyệt vẽ lại các phần bị ảnh hưởng của trang khi cuộn. Điều này có thể tốn kém đủ để ảnh động bị giật đáng kể.

Nếu muốn thực hiện lời hứa về chuyển động thị sai, chúng ta cần một thuộc tính có thể áp dụng dưới dạng thuộc tính tăng tốc (hiện có nghĩa là bám sát các phép biến đổi và độ mờ) và không phụ thuộc vào các sự kiện cuộn.

CSS ở chế độ 3D

Cả Scott KellumKeith Clark đều đã làm được những việc quan trọng trong lĩnh vực sử dụng CSS 3D để đạt được chuyển động thị sai, và kỹ thuật mà họ sử dụng rất hiệu quả như sau:

  • Thiết lập một phần tử chứa để cuộn bằng overflow-y: scroll (và có thể là overflow-x: hidden).
  • Đối với cùng một phần tử đó, hãy áp dụng giá trị perspective và đặt perspective-origin thành top left hoặc 0 0.
  • Đối với các phần tử con của phần tử đó, hãy áp dụng phép dịch trong Z và điều chỉnh tỷ lệ các phần tử đó trở lại để tạo chuyển động song song mà không ảnh hưởng đến kích thước của các phần tử đó trên màn hình.

CSS cho phương pháp này có dạng như sau:

.container {
  width: 100%;
  height: 100%;
  overflow-x: hidden;
  overflow-y: scroll;
  perspective: 1px;
  perspective-origin: 0 0;
}

.parallax-child {
  transform-origin: 0 0;
  transform: translateZ(-2px) scale(3);
}

Giả sử bạn có một đoạn mã HTML như sau:

<div class="container">
    <div class="parallax-child"></div>
</div>

Điều chỉnh tỷ lệ cho phối cảnh

Việc đẩy phần tử con trở lại sẽ khiến phần tử đó nhỏ hơn theo tỷ lệ với giá trị phối cảnh. Bạn có thể tính toán mức độ cần mở rộng bằng phương trình này: (perspective – distance) / perspective. Vì chúng ta rất có thể muốn thành phần có hiệu ứng thị sai có hiệu ứng thị sai nhưng xuất hiện ở kích thước mà chúng ta đã tạo, nên thành phần này cần được điều chỉnh theo tỷ lệ theo cách này thay vì giữ nguyên.

Trong trường hợp của mã trên, phối cảnh là 1px và khoảng cách Z của parallax-child-2px. Điều này có nghĩa là phần tử sẽ cần được tăng tỷ lệ theo 3 lần, bạn có thể thấy giá trị này được cắm vào mã: scale(3).

Đối với mọi nội dung không áp dụng giá trị translateZ, bạn có thể thay thế bằng giá trị 0. Điều này có nghĩa là tỷ lệ là (perspective - 0) / perspective, có giá trị là 1, nghĩa là tỷ lệ này không được tăng hoặc giảm. Thật sự rất tiện lợi.

Cách hoạt động của phương pháp này

Điều quan trọng là bạn phải hiểu rõ lý do tại sao điều này hoạt động, vì chúng ta sẽ sớm sử dụng kiến thức đó. Cuộn là một phép biến đổi hiệu quả, đó là lý do tại sao bạn có thể tăng tốc cuộn; thao tác này chủ yếu liên quan đến việc di chuyển các lớp xung quanh bằng GPU. Trong một thao tác cuộn thông thường (không có khái niệm về phối cảnh), thao tác cuộn sẽ diễn ra theo tỷ lệ 1:1 khi so sánh phần tử cuộn và các phần tử con của phần tử đó. Nếu bạn cuộn một phần tử xuống 300px, thì các phần tử con của phần tử đó sẽ được chuyển đổi lên cùng một lượng: 300px.

Tuy nhiên, việc áp dụng giá trị phối cảnh cho phần tử cuộn sẽ làm rối loạn quá trình này; nó thay đổi các ma trận hỗ trợ phép biến đổi cuộn. Giờ đây, thao tác cuộn 300px có thể chỉ di chuyển các thành phần con 150px, tuỳ thuộc vào giá trị perspectivetranslateZ mà bạn đã chọn. Nếu một phần tử có giá trị translateZ là 0, thì phần tử đó sẽ cuộn theo tỷ lệ 1:1 (như trước đây), nhưng phần tử con được đẩy theo trục Z ra khỏi gốc phối cảnh sẽ cuộn theo tốc độ khác! Kết quả ròng: chuyển động thị sai. Và điều quan trọng là thao tác này được xử lý tự động trong cơ chế cuộn nội bộ của trình duyệt, nghĩa là bạn không cần theo dõi các sự kiện scroll hoặc thay đổi background-position.

Một điểm trừ: Safari cho thiết bị di động

Mỗi hiệu ứng đều có những lưu ý riêng và một lưu ý quan trọng đối với các phép biến đổi là việc bảo toàn hiệu ứng 3D cho các phần tử con. Nếu có các phần tử trong hệ phân cấp giữa phần tử có phối cảnh và các phần tử con có hiệu ứng thị sai, thì phối cảnh 3D sẽ bị "làm phẳng", nghĩa là hiệu ứng sẽ bị mất.

<div class="container">
    <div class="parallax-container">
    <div class="parallax-child"></div>
    </div>
</div>

Trong HTML ở trên, .parallax-container là mới và sẽ làm phẳng giá trị perspective một cách hiệu quả, đồng thời chúng ta sẽ mất hiệu ứng thị sai. Giải pháp trong hầu hết các trường hợp khá đơn giản: bạn thêm transform-style: preserve-3d vào phần tử, khiến phần tử đó truyền mọi hiệu ứng 3D (chẳng hạn như giá trị phối cảnh) đã được áp dụng ở phía trên cây.

.parallax-container {
  transform-style: preserve-3d;
}

Tuy nhiên, trong trường hợp của Safari dành cho thiết bị di động, mọi thứ sẽ phức tạp hơn một chút. Về mặt kỹ thuật, việc áp dụng overflow-y: scroll cho phần tử vùng chứa sẽ hoạt động, nhưng bạn sẽ không thể hất phần tử cuộn. Giải pháp là thêm -webkit-overflow-scrolling: touch, nhưng điều này cũng sẽ làm phẳng perspective và chúng ta sẽ không có hiệu ứng thị sai nào.

Từ quan điểm cải tiến dần, đây có thể không phải là vấn đề quá lớn. Nếu không thể tạo hiệu ứng thị sai trong mọi trường hợp, ứng dụng của chúng ta vẫn sẽ hoạt động, nhưng tốt hơn hết là bạn nên tìm ra giải pháp.

position: sticky sẽ giúp bạn giải quyết vấn đề này!

Trên thực tế, có một số trợ giúp ở dạng position: sticky, tồn tại để cho phép các phần tử "dính" vào đầu khung nhìn hoặc một phần tử mẹ nhất định trong khi cuộn. Thông số kỹ thuật, giống như hầu hết các thông số kỹ thuật khác, khá nặng nề, nhưng chứa một tính năng nhỏ hữu ích:

Thoạt nhìn, điều này có vẻ không có ý nghĩa gì lớn, nhưng điểm chính trong câu đó là khi đề cập đến cách tính chính xác độ bám của một phần tử: "giá trị chênh lệch được tính toán bằng cách tham chiếu đến phần tử cấp trên gần nhất có hộp cuộn". Nói cách khác, khoảng cách để di chuyển phần tử cố định (để phần tử đó xuất hiện gắn với một phần tử khác hoặc khung nhìn) được tính toán trước khi áp dụng bất kỳ phép biến đổi nào khác, chứ không phải sau. Điều này có nghĩa là rất giống với ví dụ cuộn trước đó, nếu độ dời được tính toán ở mức 300px, thì có một cơ hội mới để sử dụng phối cảnh (hoặc bất kỳ phép biến đổi nào khác) để thao tác với giá trị độ dời 300px đó trước khi áp dụng cho bất kỳ phần tử cố định nào.

Bằng cách áp dụng position: -webkit-sticky cho phần tử có hiệu ứng thị sai, chúng ta có thể "đảo ngược" hiệu ứng làm phẳng của -webkit-overflow-scrolling: touch một cách hiệu quả. Điều này đảm bảo rằng phần tử hiệu ứng thị giác tham chiếu đến phần tử cấp trên gần nhất bằng một hộp cuộn, trong trường hợp này là .container. Sau đó, tương tự như trước, .parallax-container áp dụng giá trị perspective, thay đổi độ dời cuộn được tính toán và tạo hiệu ứng thị sai.

<div class="container">
    <div class="parallax-container">
    <div class="parallax-child"></div>
    </div>
</div>
.container {
  overflow-y: scroll;
  -webkit-overflow-scrolling: touch;
}

.parallax-container {
  perspective: 1px;
}

.parallax-child {
  position: -webkit-sticky;
  top: 0px;
  transform: translate(-2px) scale(3);
}

Thao tác này sẽ khôi phục hiệu ứng thị sai cho Safari dành cho thiết bị di động. Đây là tin vui!

Lưu ý về vị trí cố định

Tuy nhiên, sự khác biệt ở đây: position: sticky sẽ thay đổi cơ chế thị sai. Vị trí cố định cố gắng cố định phần tử vào vùng chứa cuộn, còn phiên bản không cố định thì không. Điều này có nghĩa là hiệu ứng thị sai có hiệu ứng cố định sẽ là giá trị nghịch đảo của hiệu ứng thị sai không có hiệu ứng cố định:

  • Với position: sticky, phần tử càng gần z=0 thì càng ít di chuyển.
  • Không có position: sticky, phần tử càng gần z=0 thì càng di chuyển nhiều.

Nếu tất cả những điều đó có vẻ hơi trừu tượng, hãy xem bản minh hoạ này của Robert Flack. Bản minh hoạ này cho thấy cách các phần tử hoạt động khác nhau khi có và không có vị trí cố định. Để thấy sự khác biệt, bạn cần có Chrome Canary (phiên bản 56 tại thời điểm viết bài) hoặc Safari.

Ảnh chụp màn hình phối cảnh thị sai

Bản minh hoạ của Robert Flack cho thấy cách position: sticky ảnh hưởng đến hiệu ứng cuộn song song.

Một số lỗi và cách khắc phục

Tuy nhiên, như mọi thứ khác, vẫn còn một số điểm cần làm mượt:

  • Hỗ trợ cố định không nhất quán. Tính năng hỗ trợ vẫn đang được triển khai trong Chrome, Edge hoàn toàn không hỗ trợ và Firefox có lỗi vẽ khi tính năng cố định được kết hợp với phép biến đổi phối cảnh. Trong những trường hợp như vậy, bạn nên thêm một chút mã để chỉ thêm position: sticky (phiên bản có tiền tố -webkit-) khi cần, chỉ dành cho Safari dành cho thiết bị di động.
  • Hiệu ứng này không "chỉ hoạt động" trong Edge. Edge cố gắng xử lý thao tác cuộn ở cấp hệ điều hành, điều này thường là tốt, nhưng trong trường hợp này, thao tác này sẽ ngăn Edge phát hiện các thay đổi về phối cảnh trong khi cuộn. Để khắc phục vấn đề này, bạn có thể thêm một phần tử vị trí cố định, vì việc này có vẻ như chuyển Edge sang một phương thức cuộn không phải OS và đảm bảo rằng phương thức này tính đến các thay đổi về phối cảnh.
  • "Nội dung của trang này đã tăng lên rất nhiều!" Nhiều trình duyệt tính đến tỷ lệ khi quyết định kích thước nội dung của trang, nhưng đáng tiếc là Chrome và Safari không tính đến phối cảnh. Vì vậy, nếu có – giả sử – tỷ lệ 3x được áp dụng cho một phần tử, bạn có thể thấy thanh cuộn và các thành phần tương tự, ngay cả khi phần tử đó ở tỷ lệ 1x sau khi áp dụng perspective. Bạn có thể giải quyết vấn đề này bằng cách điều chỉnh tỷ lệ các phần tử từ góc dưới bên phải (bằng transform-origin: bottom right). Cách này hoạt động vì nó sẽ khiến các phần tử quá khổ phát triển thành "khu vực âm" (thường là trên cùng bên trái) của khu vực có thể cuộn; các khu vực có thể cuộn không bao giờ cho phép bạn xem hoặc cuộn đến nội dung trong khu vực âm.

Kết luận

Hiệu ứng thị sai là một hiệu ứng thú vị khi được sử dụng một cách hợp lý. Như bạn có thể thấy, bạn có thể triển khai tính năng này theo cách hiệu quả, kết hợp với thao tác cuộn và trên nhiều trình duyệt. Vì cần một chút toán học và một lượng nhỏ mã nguyên mẫu để đạt được hiệu ứng mong muốn, nên chúng tôi đã gói một thư viện và mẫu trình trợ giúp nhỏ. Bạn có thể tìm thấy thư viện và mẫu này trong kho lưu trữ GitHub của Mẫu thành phần giao diện người dùng.

Hãy dùng thử và cho chúng tôi biết cảm nhận của bạn.