Dù thích hay không thích thì 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.
Tóm tắt
- Đừ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ề công cụ cuộn thị sai trong kho lưu trữ GitHub.
Vấn đề về hiệu ứng thị sai
Để bắt đầu, hãy xem hai cách phổ biến để đạt được hiệu ứng thị sai và cụ thể là 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 có 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 chúng tôi muốn tránh là vẽ trên mọi khung hình. Nhiều giải pháp cố thay đổi background-position
để cung cấp giao diện thị sai, khiến trình duyệt vẽ lại các phần bị ảnh hưởng của trang khi cuộn và điều đó có thể gây tốn kém để làm ảnh động đá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à tuân theo 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 Kellum và Keith Clark đều đã thực hiện nhiều nỗ lực 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 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 chính phần tử đó, hãy áp dụng giá trị
perspective
và đặtperspective-origin
thànhtop left
hoặc0 0
. - Đối với các phần tử con của phần tử đó, hãy áp dụng bản dịch trong Z và điều chỉnh tỷ lệ sao lưu để cung cấp 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ử 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
Đẩy phần tử con trở lại 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 thiết để tăng kích thước bằng phương trình sau: (góc nhìn - khoảng cách) / phối cảnh. Vì chúng ta rất có thể muốn phần tử 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 phần tử 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
là -2px. Điều này có nghĩa là phần tử sẽ cần được tăng tỷ lệ theo 3x, bạn có thể thấy giá trị này được cắm vào mã: scale(3)
.
Đối với bất kỳ nội dung nào không áp dụng giá trị translateZ
, bạn có thể thay thế giá trị bằng 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, tức là thao tác không có bất kỳ khái niệm nào về phối cảnh, thao tác cuộn diễn ra theo tỷ lệ 1:1 khi so sánh phần tử cuộn và phần tử con.
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 bằng cùng một lượng: 300px
.
Tuy nhiên, việc áp dụng một giá trị phối cảnh cho phần tử cuộn sẽ gây xáo trộn quy trình này; nó thay đổi ma trận làm cơ sở cho 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ị perspective
và translateZ
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 phần tử con được đẩy theo trục Z ra khỏi gốc phối cảnh sẽ được cuộn theo tốc độ khác! Kết quả ròng: chuyển động thị sai. Và điều quan trọng là việ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 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ó phối cảnh và phần tử con thị sai, thì phối cảnh 3D sẽ được "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à nó sẽ làm phẳng giá trị perspective
một cách hiệu quả và 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ừ góc nhìn cải tiến ngày càng tăng, 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 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, có sự khác biệt ở đây: position: sticky
sẽ thay đổi cơ chế thị sai. Vị trí cố định cố gắng gắn phần tử vào vùng chứa 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ó 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ì phần tử đó càng nhiều di chuyển.
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.
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
Mặc dù vậy, như bất cứ thứ gì khác, vẫn còn những vấn đề chưa được xử lý kịp thời:
- 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ằngtransform-origin: bottom right
). Cách này hiệu quả vì 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
Quảng cáo thị sai là một hiệu ứng thú vị khi được sử dụng một cách thấu đáo. Như bạn có thể thấy, có thể triển khai tính năng này theo cách hiệu quả, cuộn cùng nhau 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.