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

Robert Flack
Robert Flack

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, phương pháp này có thể tăng thêm chiều sâu và sự tinh tế cho ứng dụng web. Tuy nhiên, vấn đề là việc triển khai thị sai theo cách hiệu quả có thể gặp khó khăn. 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à hoạt động được trên nhiều trình duyệt.

Hình minh hoạ quảng cáo thị sai.

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 Safari trên thiết bị di động, hãy sử dụng position: sticky để đảm bảo hiệu ứng thị sai được phổ biến.

Nếu bạn muốn sử dụng 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à tải 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 bài toán

Để 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 phù hợp: sử dụng sự kiện cuộn

Yêu cầu chính của thị sai là cần phải ghép nối nó với nhau; với mọi thay đổi riêng lẻ trong vị trí cuộn của trang, vị trí của phần tử thị sai 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ể này, thao tác này áp dụng để cuộn các sự kiệ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 "nỗ lực tối đa" và không được đảm bảo phân phối trên mọi khung của ảnh động cuộn!

Thông tin quan trọng này cho chúng tôi biết lý do tại sao chúng ta cần tránh sử dụng giải pháp dựa trên JavaScript mà 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ẽ giữ nguyên vị trí cuộn của trang. Trong các phiên bản cũ của Mobile Safari, sự kiện cuộn thực sự được phân phối ở 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 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, dựa trên "nỗ lực tối đa". Nếu luồng chính đang bận thực hiện bất kỳ tác vụ 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.

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 để 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 triển vọng của chuyển động thị sai, chúng ta muốn một điều gì đó có thể được áp dụng dưới dạng thuộc tính được tăng tốc (hiện tại có nghĩa là gắn liề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 trong mô hình 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 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).
  • Cũng với phần tử đó, hãy áp dụng giá trị perspectiveperspective-origin được đặt 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 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ì rất có thể chúng ta muốn phần tử thị sai tạo thành thị sai nhưng xuất hiện ở kích thước mà chúng ta tạo ra, nên phần tử này 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-2px. Tức là phần tử sẽ cần được điều chỉnh theo tỷ lệ 3x, mà bạn có thể thấy là giá trị đượ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. Tức là tỷ lệ màn hình là (góc nhìn - 0) / phối cảnh, sẽ thu được giá trị bằng 1, có nghĩa là tỷ lệ không được tăng lên hoặc giảm 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 khiến điều này mang lại hiệu quả, vì chúng ta sẽ sớm sử dụng kiến thức đó. Thao tác cuộn là một cách biến đổi hiệu quả, đó là lý do tại sao tính năng này có thể được tăng tốc; hoạt động này chủ yếu liên quan đến việc dịch chuyển các lớp 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 số 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, một lượt cuộn 300px chỉ có thể di chuyển phần tử con thêm 150px, tuỳ thuộc vào các giá trị perspectivetranslateZ bạn chọn. Nếu một phần tử có giá trị translateZ bằng 0, thì phần tử đó sẽ được cuộn ở tỷ lệ 1:1 (như trước đây), nhưng phần tử con được đẩy trong Z ra khỏi gốc phối cảnh sẽ được cuộn ở một tốc độ khác! Kết quả ròng: chuyển động thị sai. Điều rất quan trọng là việc này được tự động xử lý như một phần của bộ máy cuộn nội bộ của trình duyệt, có nghĩa là bạn không cần phải nghe các sự kiện scroll hoặc thay đổi background-position.

Một con ruồi trong thuốc mỡ: Mobile Safari

Chúng tôi cần lưu ý mọi hiệu ứng và một điều quan trọng đối với hiệu ứng chuyể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. Trong hầu hết các trường hợp, giải pháp khá đơn giản: bạn thêm transform-style: preserve-3d vào phần tử, để phần tử này truyền mọi hiệu ứng 3D (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 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 đã hoạt động về mặt kỹ thuật, nhưng đổi lại là có thể hất phần tử cuộn. Giải pháp là thêm -webkit-overflow-scrolling: touch, nhưng thao tác này cũng sẽ làm phẳng perspective và chúng ta sẽ không nhận được bất kỳ 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 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 thật tốt khi tìm ra cách giải quyết.

position: sticky giải cứu!

Trên thực tế, có một số trợ giúp dưới dạng position: sticky, tồn tại để cho phép các phần tử "gắn" lên đầu khung nhìn hoặc một phần tử mẹ nhất định trong quá trình 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 lại chứa một viên ngọc nhỏ hữu ích bên trong:

Lúc đầu, điều này có vẻ không có nghĩa là rất nhiều, nhưng điểm chính trong câu đó là khi nói đến cách tính chính xác độ hấp dẫn của một phần tử: "độ 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 (để phần tử đó xuất hiện được đính kèm vào một phần tử khác hoặc khung nhìn) đượ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à, 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 phối cảnh (hoặc bất kỳ biến đổi nào khác) để thao tác với 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ử thị sai tham chiếu đến đối tượng 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. Giá trị này thay đổi độ lệch cuộn đã 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 Mobile Safari, đây là tin tức tuyệt vời!

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

Tuy nhiên, một sự khác biệt ở đây: position: sticky làm thay đổi cơ chế thị sai. Vị trí cố định sẽ cố 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à thị sai có cố định sẽ trở thành giá trị 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ử ít hơn mà phần 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ả có vẻ hơi trừu tượng, hãy xem bản minh hoạ này của Robert Flack. Video 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) 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 việc cuộn thị sai.

Các loại lỗi và cách giải quyết

Tuy nhiên, tương tự như mọi thứ khác, vẫn còn những vấn đề chưa được xử lý triệt để:

  • Sự 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 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 những trường hợp như vậy, bạn chỉ 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, chỉ dành cho Mobile Safari.
  • Hiệu ứng này không "chỉ có tác dụng" trên Edge. Edge cố gắng xử lý thao tác cuộn ở cấp hệ điều hành. Đây thường là một dấu hiệu tốt, nhưng trong trường hợp này thì ứng dụng không phát hiện được các thay đổi về phối cảnh trong quá trình 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ần tử này có tính đến các thay đổi về phối cảnh.
  • "Nội dung trên trang vô cùng lớn!" Nhiều trình duyệt xem xét 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, Chrome và Safari không tính đến góc nhìn. Ví dụ: nếu có tỷ lệ 3x áp dụng cho một phần tử, thì bạn cũng có thể thấy thanh cuộn và các dạng tương tự, ngay cả khi phần tử có tỷ lệ gấp 1 lần 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ử ở góc dưới cùng bên phải (bằng transform-origin: bottom right). Thao tác này sẽ hoạt động vì sẽ khiến các phần tử quá khổ phát triển thành "vùng âm" (thường là trên cùng bên trái) của vùng có thể cuộn; các vùng có thể cuộn sẽ 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 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 khó khăn về toán học và một lượng nhỏ mã nguyên mẫu để có được hiệu quả mong muốn, nên chúng tôi đã tổng hợp một thư viện và mẫu trợ giúp nhỏ 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é.