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

Dù bạn thích hay không thích, hiệu ứng thị sai vẫn sẽ tiếp tục được sử dụng. Khi được sử dụng một cách hợp lý, hiệu ứng này có thể tăng thêm chiều sâu và sự tinh tế cho một ứ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 hoạt động trên nhiều trình duyệt.

Hình minh hoạ thị sai.

TL;DR

  • Đừng sử dụng sự kiện cuộn hoặc background-position để tạo ảnh động thị sai.
  • Sử dụng các 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 đ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 về Mẫu phần tử trên giao diện người dùng và lấy JS trợ giúp hiệu ứng thị sai! Bạn có thể xem bản minh hoạ trực tiếp về trình cuộn thị sai trong kho lưu trữ GitHub.

Problem parallaxers

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

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

Yêu cầu chính của hiệu ứng thị sai là hiệu ứng này phải được liên kết với thao tác cuộn; đối với mỗi thay đổi về vị trí cuộn của trang, vị trí của phần tử thị sai sẽ 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 tôi, đ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, sự kiện cuộn được phân phối dưới dạng "cố gắng hết sức" và không đảm bảo đượ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 một 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ị sai sẽ đồng bộ với vị trí cuộn của trang. Trong các phiên bản cũ của Mobile Safari, các sự kiện cuộn thực sự được gửi vào cuối thao tác cuộn, điều này 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 gần đây hơn gửi các sự kiện cuộn trong quá trình tạo ảnh động, nhưng tương tự như Chrome, trên cơ sở "cố gắng hết sức". Nếu luồng chính đang bận với bất kỳ công việc nào khác, thì 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.

Kém: cập nhật background-position

Một tình huống khác mà chúng ta nê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 để tạo hiệu ứng thị sai. Điều này khiến trình duyệt vẽ lại các phần bị ảnh hưởng của trang khi cuộn và có thể tốn kém đến mức làm ảnh động bị giật đáng kể.

Nếu muốn thực hiện hiệu ứng chuyển động thị sai, chúng ta cần một thứ có thể được áp dụng làm thuộc tính được tăng tốc (hiện tại có nghĩa là gắn với 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 đã thực hiện nhiều cô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 có 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).
  • Áp dụng giá trị perspective cho cùng một phần tử đó và đặt perspective-origin thành top left hoặc 0 0.
  • Áp dụng một phép tịnh tiến theo trục Z cho các thành phần con của phần tử đó và điều chỉnh tỷ lệ các thành phần con đó để tạo chuyển động thị sai mà không ảnh hưởng đến kích thước của chúng 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 ra sau sẽ khiến phần tử đó nhỏ hơn tương ứng với giá trị phối cảnh. Bạn có thể tính toán mức độ cần tăng kích thước bằng phương trình sau: (góc nhìn – khoảng cách) / góc nhìn. Vì rất có thể chúng ta muốn phần tử chuyển động thị sai chuyển động thị sai nhưng xuất hiện ở kích thước mà chúng ta đã tạo, nên phần tử đó cần được tăng tỷ lệ theo cách này, thay vì giữ nguyên.

Trong trường hợp mã ở trên, góc nhìn 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ệ lên 3 lần. Bạn có thể thấy đây là giá trị được cắm vào mã: scale(3).

Đối với mọi nội dung không có giá trị translateZ được áp dụng, 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, cho ra giá trị là 1, tức là không được tăng hoặc giảm tỷ lệ. 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 phương pháp này hiệu quả, vì chúng ta sẽ sử dụng kiến thức đó trong thời gian ngắn. Thao tác cuộn thực chất là một phép biến đổi, đó là lý do tại sao thao tác này có thể được tăng tốc; thao tác này chủ yếu liên quan đến việc dịch 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ề góc nhìn), thao tác cuộn diễn ra theo tỷ lệ 1:1 khi so sánh phần tử có thể 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 biến đổi lên trê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 quá trình này; giá trị này sẽ thay đổi các ma trận làm cơ sở cho 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 các 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ẽ được cuộn theo tỷ lệ 1:1 (như trước đây), nhưng một phần tử con được đẩy ra xa nguồn gốc phối cảnh theo hướng Z sẽ được cuộn theo một tốc độ khác! Kết quả cuối cùng: chuyển động thị sai. Và điều rất quan trọng là việc này được xử lý tự động như một phần của cơ chế cuộn bên trong trình duyệt, tức là bạn không cần theo dõi các sự kiện scroll hoặc thay đổi background-position.

Điểm yếu: Safari trên thiết bị di động

Mọi hiệu ứng đều có những điểm hạn chế và một điểm quan trọng đối với các phép biến đổi là về việc duy trì 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ó góc nhìn và các phần tử con tạo hiệu ứng thị sai, thì góc nhìn 3D sẽ bị "làm phẳng", tức là hiệu ứng sẽ biến 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. Trong hầu hết các trường hợp, giải pháp này khá đơn giản: bạn thêm transform-style: preserve-3d vào phần tử, khiến phần tử đó truyền bất kỳ hiệu ứng 3D nào (chẳng hạn như giá trị phối cảnh của chúng ta) đã được áp dụng thêm vào cây.

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

Tuy nhiên, trong trường hợp của Mobile Safari, mọi thứ phức tạp hơn một chút. Việc áp dụng overflow-y: scroll cho phần tử vùng chứa về mặt kỹ thuật 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 giải pháp này cũng sẽ làm phẳng perspective và chúng ta sẽ không có hiệu ứng thị sai.

Theo quan điểm cải tiến tăng dần, có lẽ đây 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 sẽ tốt hơn nếu tìm ra một giải pháp thay thế.

position: sticky sẽ giúp bạn!

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

Thoạt nhìn, điều này có vẻ không có nhiều ý nghĩa, nhưng một điểm quan trọng trong câu đó là khi câu này đề cập đến cách tính toán chính xác độ dính của một phần tử: "độ lệch được tính toán dựa trên tổ tiê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 được đính kèm vào một phần tử khác hoặc khung hiển thị) được tính 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à, giống như ví dụ về thao tác cuộn trước đó, nếu độ lệch được tính ở mức 300px, thì bạn có cơ hội mới để sử dụng góc nhìn (hoặc bất kỳ phép biến đổi nào khác) nhằm điều chỉnh giá trị độ lệch 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ử thị sai, chúng ta có thể "đảo ngược" hiệu quả hiệu ứng làm phẳng của -webkit-overflow-scrolling: touch. Điều này đảm bảo rằng phần tử chuyển động thị sai tham chiếu đến phần tử tổ tiên gần nhất có hộp cuộn, trong trường hợp này là .container. Sau đó, tương tự như trước đây, .parallax-container sẽ áp dụng giá trị perspective, giá trị này sẽ thay đổi độ lệch cuộn được tính 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);
}

Điều này khôi phục hiệu ứng thị sai cho Safari trên thiết bị di động, đây là tin vui cho tất cả mọi người!

Lưu ý về tính năng cố định vị trí

Tuy nhiên, có một điểm khác biệt ở đây: position: sticky làm thay đổi cơ chế thị sai. Tính năng định vị cố định sẽ cố gắng gắn phần tử vào vùng chứa có thể cuộn, trong khi 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ó phần cuối cố định sẽ là hiệu ứng ngược lại với hiệu ứng không có phần cuối 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ì phần tử đó càng di chuyển nhiều.

Nếu bạn thấy tất cả những điều đó 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ó tính năng cố định vị trí. Để thấy sự khác biệt, bạn cần dùng Chrome Canary (hiện là phiên bản 56) 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 thị sai.

Một số lỗi và cách giải quyết

Tuy nhiên, giống như mọi thứ khác, vẫn còn những điểm cần được cải thiện:

  • Hỗ trợ tính năng dán không nhất quán. Tính năng này 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 sticky kết hợp với các 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 đoạn mã nhỏ để chỉ thêm position: sticky (phiên bản có tiền tố -webkit-) khi cần, tức là chỉ dành cho Mobile Safari.
  • 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à một điểm cộng, nhưng trong trường hợp này, thao tác đó ngăn Edge phát hiện các thay đổi về góc nhìn 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ì thao tác này dường như chuyển Edge sang phương thức cuộn không phải hệ điều hành và đảm bảo rằng phương thức này tính đến các thay đổi về góc nhìn.
  • "Nội dung của trang này vừa tăng lên đáng kể!" 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 rất tiếc là Chrome và Safari không tính đến góc nhìn. Vì vậy, nếu có một tỷ lệ 3x được áp dụng cho một phần tử, bạn có thể thấy các thanh cuộn và những thành phần tương tự, ngay cả khi phần tử ở tỷ lệ 1x sau khi perspective được áp dụng. 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 cùng bên phải (bằng transform-origin: bottom right). Cách này có hiệu quả vì nó sẽ khiến các phần tử có kích thước quá lớn phát triển thành "vùng âm" (thường là phía trên cùng bên trái) của vùng có thể cuộn; các vùng có thể cuộn không bao giờ cho phép bạn xem hoặc cuộn đến nội dung trong vùng â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 chu đáo. Như bạn có thể thấy, bạn có thể triển khai theo cách hiệu quả, liên kết với thao tác cuộn và hoạt động trên nhiều trình duyệt. Vì cần một chút kiến thức toán học và một lượng nhỏ mã lặp lại để đạt được hiệu ứng mong muốn, nên chúng tôi đã gói một thư viện trợ giúp nhỏ và mẫu mà bạn có thể tìm thấy trong kho lưu trữ GitHub Mẫu phần tử giao diện người dùng.

Hãy thử và cho chúng tôi biết kết quả nhé.