Dù thích hay không thích thì thị sai vẫn sẽ tồn tại. Nếu được sử dụng thận trọng, tăng thêm chiều sâu và sự tinh tế cho ứng dụng web. Tuy nhiên, vấn đề đặt ra là việc triển khai thị sai một cách hiệu quả có thể là thách thức. Trong bài viết này, chúng tôi sẽ thảo luận về một giải pháp vừa hiệu quả, vừa quan trọng là hiệu quả trên nhiều trình duyệt.
![Hình minh hoạ quảng cáo thị sai.](https://developer.chrome.com/static/blog/performant-parallaxing/image/parallax-illustration-330a26c14e2fc.jpg?authuser=19&hl=vi)
Tóm tắt
- Không dùng sự kiện cuộn hoặc
background-position
để tạo ảnh động thị sai. - Sử dụng các biến đổi CSS 3D để 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 rằng hiệu ứng thị sai được nhân bản.
Nếu bạn muốn có giải pháp thả xuống, hãy chuyển đến kho lưu trữ GitHub mẫu phần tử giao diện người dùng và lấy Trình trợ giúp thị sai JS! 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.
Thị sai vấn đề
Để bắt đầu, hãy tìm hiểu hai cách phổ biến để đạt được thị sai hiệu quả và cụ thể là tại sao chúng không phù hợp với mục đích của chúng tôi.
Không phù hợp: sử dụng sự kiện cuộn
Yêu cầu chính của thị sai là nó phải được ghép nối; với mọi thay đổi về vị trí cuộn của trang, phần tử thị sai vị trí sẽ được cập nhật. Mặc dù điều đó nghe có vẻ đơn giản nhưng một cơ chế quan trọng để trình duyệt hiện đại là khả năng hoạt động không đồng bộ. Điều này áp dụng, trong trường hợp cụ thể, để cuộn sự kiện. Trong hầu hết các trình duyệt, sự kiện cuộn được phân phối là "nỗ lực tối đa" và không đảm bảo được phân phối trên mọi khung hình của hoạt ảnh cuộn!
Thông tin quan trọng này cho chúng tôi biết tại sao chúng tôi 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 thị sai sẽ bắt kịp với vị trí cuộn của trang. Trong các phiên bản cũ hơn của Mobile Safari, các sự kiện cuộn thực sự được phân phối ở cuối cuộn, điều này khiến không thể tạo ra Hiệu ứng cuộn dựa trên JavaScript. Các phiên bản gần đây hơn có phân phối sự kiện cuộn trong suốt ảnh động, nhưng tương tự như với Chrome, một "nỗ lực tối đa" cơ sở. Nếu luồng chính đang bận với mọi tác vụ khác, nên các sự kiện cuộn sẽ không được gửi tức là hiệu ứng thị sai sẽ bị mất.
Không phù hợp: đang 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
để tạo ra giao diện thị sai.
khiến trình duyệt vẽ lại những phần bị ảnh hưởng của trang khi cuộn và điều đó
có thể tốn kém để làm ảnh động đáng kể.
Nếu muốn thực hiện triển vọng của chuyển động thị sai, chúng tôi muốn điều gì đó có thể được áp dụng như một thuộc tính tăng tốc (điều này có nghĩa là bạn nên duy trì biến đổi và độ mờ) và không dựa vào sự kiện cuộn.
CSS trong mô hình 3D
Cả Scott Kellum và Keith Clark đều có đã thực hiện 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 họ sử dụng là rất hiệu quả:
- 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
vàperspective-origin
cho cùng phần tử đó được đặt 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 bằng Z và thu nhỏ chúng lại để 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ẽ làm cho phần tử đó nhỏ hơn tỷ lệ với giá trị phối cảnh. Bạn có thể tính toán số tiền cần thiết để mở rộng quy mô phương trình này: (góc độ xem - khoảng cách) / phối cảnh. Vì rất có thể chúng tôi muốn phần tử thị sai thành thị sai nhưng xuất hiện ở kích thước mà chúng ta đã tạo ra, nó cần được mở rộng 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 thêm 3x, mà bạn có thể thấy là giá trị đượ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ể
thế vào một giá trị bằng 0. Tức là tỷ lệ (góc nhìn - 0) /
phối cảnh, thu được ở giá trị bằng 1, có nghĩa là quảng cáo được điều chỉnh theo tỷ lệ
cả lên hoặc xuống. Khá tiện lợi, thực sự hữu ích.
Cách hoạt động của phương pháp này
Điều quan trọng là phải biết rõ lý do tại sao làm như vậy hiệu quả, vì chúng ta sẽ sử dụng
sớm nhất có thể. Cuộn là một cách biến đổi hiệu quả, đó là lý do tại sao nó có thể
accelerated; chủ yếu liên quan đến việc dịch chuyển các lớp bằng GPU. Trong một
cuộn thông thường, tức là không có bất kỳ khái niệm nào về phối cảnh, cuộn
xảy ra theo cách 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ì phần tử con của phần tử đó sẽ được chuyển đổi lên trên
với cùng số tiền là: 300px
.
Tuy nhiên, việc áp dụng giá trị phối cảnh cho phần tử cuộn sẽ gây lộn xộn
với 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, cuộn 300px chỉ có thể di chuyển phần tử 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, nó sẽ được cuộn ở tỷ lệ 1:1 (như trước đây), nhưng là một phần tử con
bị đẩy vào Z từ gốc phối cảnh sẽ được cuộn ở một vị trí khác
! Kết quả ròng: chuyển động thị sai. Và một điều rất quan trọng là chúng tôi xử lý việc này
là một phần của bộ máy cuộn nội bộ của trình duyệt, nghĩa là luôn có
không cần nghe sự kiện scroll
hoặc thay đổi background-position
.
Một con ruồi trong thuốc mỡ: Mobile Safari
Tất cả các hiệu ứng đều có các cảnh báo và một cảnh báo quan trọng về chuyể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ệ thống phân cấp giữa phần tử có phối cảnh và phần tử con thị sai, phối cảnh 3D bị "làm phẳng", nghĩa là hiệu ứng 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ã mới và sẽ được thiết lập hiệu quả
làm phẳng giá trị perspective
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 sẽ khá đơn giản: bạn thêm transform-style: preserve-3d
vào phần tử, khiến nó truyền bất kỳ hiệu ứng 3D nào (như phối cảnh của chúng ta
giá trị) đã được áp dụng cho phía trên 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 sẽ hoạt động về mặt kỹ thuật, nhưng ở
chi phí để có thể hất phần tử cuộn. Giải pháp là thêm
-webkit-overflow-scrolling: touch
, nhưng cũng sẽ làm phẳng perspective
và chúng tôi sẽ không nhận được bất kỳ thị sai nào.
Nếu xét theo góc nhìn ngày càng nâng cao thì đây có thể không phải là quá vấn đề. Nếu chúng ta không thể thị sai trong mọi tình huống, ứng dụng của chúng ta sẽ vẫn hoạt động nhưng nó thì tốt nhất là tìm ra giải pháp.
position: sticky
giải cứu!
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" lên đầ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ố này, khá lớn, nhưng vẫn chứa
viên ngọc nhỏ hữu ích bên trong:
Điều này có vẻ không có ý nghĩa lớn khi mới nhìn thoáng qua, nhưng là điểm chính trong câu đó là khi đề cập chính xác đến mức độ hấp dẫn của một phần tử được tính: "độ lệch được tính toán có tham chiếu đến đối tượng cấp trên gần nhất bằng hộp cuộn". Nói cách khác, khoảng cách để di chuyển phần tử cố định (để thành phần hiển thị này xuất hiện được đính kèm vào một phần tử khác hoặc khung nhìn) được đượ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 độ lệch được tính ở 300px, bạn sẽ có cơ hội mới để sử dụng các quan điểm (hoặc bất kỳ biến đổi nào khác) để thao tác với giá trị bù trừ 300px đó trước khi giá trị đó được áp dụng cho bất kỳ quảng cáo cố định nào phần tử.
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ử thị sai tham chiếu đến giá trị
đối tượng cấp trên có 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 độ lệch cuộn đã 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);
}
Thao tác này sẽ khôi phục hiệu ứng thị sai cho Mobile Safari. Đây là tin tuyệt vời tròn!
Lưu ý về vị trí cố định
Tuy nhiên, có một sự khác biệt ở đây: position: sticky
làm thay đổi
cơ học 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 dính thì không. Điều này có nghĩa là
thị sai có cố định sẽ trở thành nghịch đảo của giá trị không có:
- Với
position: sticky
, phần tử càng gần z=0 thì phần tử càng thấp hơn di chuyển. - Không có
position: sticky
, phần tử càng gần z=0 thì càng nhiều nó di chuyển.
Nếu tất cả có vẻ hơi trừu tượng, hãy xem bản minh hoạ này của Robert Flack. thể hiện cách các phần tử hoạt động khác nhau khi có và không có cố định vị trí. Để thấy sự khác biệt, bạn cần có Chrome Canary (phiên bản 56) vào thời điểm viết) hoặc Safari.
![Ảnh chụp màn hình phối cảnh thị sai](https://developer.chrome.com/static/blog/performant-parallaxing/image/parallax-perspective-scre-e5a0b46e4d2ad.jpg?authuser=19&hl=vi)
Bản minh hoạ của Robert Flack minh hoạ cách
position: sticky
ảnh hưởng đến việc cuộn thị sai.
Các loại lỗi và cách giải quyết
Mặc dù vậy, như bất kỳ thứ gì vẫn còn những cục u và bướu cần được giải quyết đã làm mượt:
- Sự hỗ trợ cố định không nhất quán. Dịch vụ hỗ trợ vẫn đang được triển khai trong
Chrome, Edge không hỗ trợ hoàn toàn, còn Firefox gặp lỗi vẽ lỗi khi tính năng cố định kết hợp với biến đổi phối cảnh. Trong phạm vi như vậy
trong trường hợp, bạn nên thêm một mã nhỏ để chỉ thêm
position: sticky
( phiên bản có tiền tố-webkit-
) khi cần. Phiên bản này dành cho Mobile Safari . - Hiệu ứng không "chỉ có tác dụng" trong Edge. Edge cố gắng xử lý thao tác cuộn tại cấp hệ điều hành, nhìn chung là tốt, nhưng trong trường hợp này, nó sẽ ngăn chặn 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ì 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 công cụ này tính đến các thay đổi về góc nhìn.
- "Nội dung trên trang vô cùng lớn!" Nhiều trình duyệt chiếm tỷ lệ này
khi quyết định kích thước của nội dung trên trang, nhưng rất tiếc là Chrome và Safari
không xét đến quan điểm. Loại đối thủ sau lượt đánh bóng
giả sử nếu áp dụng thang điểm 3x cho một nguyên tố, bạn có thể
bạn cũng sẽ thấy thanh cuộn và những thứ tương tự, ngay cả khi phần tử ở mức 1x sau
Đã áp dụng
perspective
. 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ử ở góc dưới cùng bên phải (bằngtransform-origin: bottom right
), cách này phù hợp vì sẽ khiến các phần tử quá khổ phát triển lên "khu vực phủ định" (thường là trên cùng bên trái) của khu vực có thể cuộn; 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 phủ định.
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 thẻ theo cách hiệu quả, cuộn cùng nhau và trên nhiều trình duyệt. Vì phương pháp này yêu cầu một chút khó khăn về toán học và một lượng nhỏ mã nguyên mẫu để có được hiệu ứng mong muốn, chúng tôi đã gói gọn một thư viện trình trợ giúp nhỏ và mẫu mà bạn có thể tìm thấy trong kho lưu trữ GitHub về mẫu phần tử giao diện người dùng.
Hãy chơi và cho chúng tôi biết kết quả của bạn nhé.