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

Paul Lewis
Robert Flack
Robert Flack

Dù thích hay không thích, công cụ thị giác vẫn luôn tồn tại. Khi được sử dụng một cách thận trọng, phương pháp này có thể làm tăng 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ể rất 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, hoạt động trên nhiều trình duyệt.

Hình minh hoạ thị sai.

Tóm tắt

  • Đừng sử dụng các sự kiện cuộn hoặc background-position để tạo ảnh động thị sai.
  • Sử dụng biến đổi 3D CSS để 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 rằng hiệu ứng thị sai được truyền.

Nếu bạn muốn có giải pháp mới, hãy truy cập kho lưu trữ GitHub về giao diện người dùng (UI Element Samples) 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ề thanh cuộn thị sai trong kho lưu trữ GitHub.

Người thị trường cho vấn đề

Để bắt đầu, hãy xem xét hai cách phổ biến để đạt được hiệu ứng thị sai và cụ thể là tại sao các cách này 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à phải kết hợp 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ù điều đó 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ộ. Điều này áp dụng cho các sự kiện cuộn trong trường hợp cụ thể của chúng tôi. 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 đả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 chúng ta cần tránh sử dụng giải pháp dựa trên JavaScript nhằ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 hiệu ứng thị sai sẽ phù hợ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 thao tác cuộn, điều này khiế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ở "nỗ lực tối đa". 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: đang cập nhật background-position

Một tình huống khác 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 giao diện thị sai, khiến trình duyệt phải 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 đáng kể để làm giật đáng kể ảnh động.

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

CSS trong mô hình 3D

Cả Scott KellumKeith Clark đã đạt được những thành quả đáng kể trong việ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 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 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 các phần tử đó để cung cấp chuyển động thị sai mà không ảnh hưởng đến kích thước trên màn hình.

CSS cho phương pháp này sẽ 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ử có một đoạn 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ẽ làm cho phần tử đó tỷ lệ nhỏ hơn với giá trị quan điểm. Bạn có thể tính toán mức độ cần mở rộng bằng phương trình sau: (phối cảnh – khoảng cách) / phối cảnh. Vì chúng ta rất có thể muốn phần tử thị sai xuất hiện ở kích thước mà chúng ta thiết kế, nên bạn cần phải tăng tỷ lệ theo cách này, thay vì để nguyên.

Trong trường hợp của 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ệ 3x. Bạn có thể thấy giá trị được thê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à thang điểm là (phối cảnh - 0) / góc nhìn, có giá trị là 1, có nghĩa là thang đo này không được điều chỉnh theo tỷ lệ tăng hoặc giảm. Thực sự khá tiện dụng.

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

Bạn cần phải hiểu rõ lý do tại sao điều này 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 sự biến đổi hiệu quả, vì vậy, bạn có thể tăng tốc độ cuộn này chủ yếu bằng cách dịch chuyển các lớp xung quanh cùng GPU. Trong thao tác cuộn thông thường, tức là thao tác cuộn 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 theo số lượng tương đươ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; nó sẽ thay đổi ma trận làm cơ sở cho sự biến đổi cuộn. Giờ đây, một thao tác cuộn 300px chỉ có thể di chuyển phần tử 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). Tuy nhiên, một phần tử con được đẩy vào Z ra khỏi nguồn gốc của phối cảnh sẽ được cuộn theo tỷ lệ khác! Kết quả ròng: chuyển động thị sai. Và một điều rất quan trọng là việc này được xử lý tự động như một phần của bộ máy cuộn nội bộ của trình duyệt, 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 chú ruồi trong thuốc mỡ: Mobile Safari

Mọi hiệu ứng đều có một số cảnh báo và một điều quan trọng đối với việc chuyển đổi là việc bảo tồn các 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 thị sai của phần tử đó, thì góc nhìn 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à 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ử, để phần tử này truyền mọi hiệu ứng 3D (như giá trị phối cảnh ) đã được áp dụng tiếp tục lê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 phải trả giá cho việc 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ỳ thị sai nào.

Từ góc nhìn của tính năng nâng cao tăng dần, đây có thể không phải là vấn đề quá lớn. Nếu chúng ta không thể phản ứng ngược trong mọi tình huống, ứng dụng của chúng ta sẽ vẫn hoạt động, nhưng sẽ rất tốt nếu bạn tìm ra cách giải quyết.

position: sticky giải cứu đội hình!

Trên thực tế, có một số trợ giúp ở dạng position: sticky, cho phép các phần tử "gắn" vào đầ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 trong số đó, khá cao, nhưng nó chứa một viên đá quý nhỏ hữu ích trong:

Điều này có vẻ không có ý nghĩa gì khi thoạt nhìn, nhưng điểm quan trọng trong câu này là khi đề cập đến cách tính chính xác độ gắn bó của một phần tử: "độ lệch được tính bằng cách tham chiếu đến đối tượng 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ử này xuất hiện đính kèm với 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à, giống như ví dụ cuộn trước đó, nếu độ lệch được tính ở 300px, thì sẽ có một cơ hội mới để sử dụng các góc nhìn (hoặc bất kỳ phép biến đổi nào khác) để điều chỉnh giá trị chênh 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 ứ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 sẽ thay đổi độ lệch cuộn đã tính và tạo ra 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);
}

Việc này sẽ khôi phục hiệu ứng thị sai cho Mobile Safari, đây là tin tuyệt vời!

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

Tuy nhiên một điểm khác biệt ở đây: position: sticky làm 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à thị sai có cố định sẽ trở thành nghịch đảo của biến không có:

  • Với position: sticky, phần tử càng ở gần z=0 thì phần tử di chuyển càng ít.
  • Nếu không có position: sticky, phần tử càng gần z=0 thì phần tử đó sẽ 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. Bản minh hoạ này sẽ minh hoạ 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 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 hoạt động cuộn thị sai.

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

Tuy nhiên, như mọi trường hợp khác vẫn tồn tại những cục u và bướu cần được làm dịu:

  • Chế độ 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 thiếu dịch vụ hỗ trợ hoàn toàn và Firefox có lỗi hiển thị khi cố định được kết hợp với các chuyể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 thiết, vốn chỉ dành cho Safari trên thiết bị di động.
  • Hiệu ứng không "chỉ có tác dụng" trong Edge. Edge cố gắng xử lý thao tác cuộn ở cấp hệ điều hành. Nhìn chung, đây là điều tốt, nhưng trong trường hợp này, Edge ngăn không cho phát hiện 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 có vẻ như chuyển Edge sang phương thức cuộn không phải hệ điều hành, đồng thời đảm bảo rằng yếu tố này có tính đến các thay đổi về góc nhìn.
  • "Nội dung của trang vừa dung lượng lớn!" Nhiều trình duyệt tính đến quy mô 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 góc nhìn. Giả sử có tỷ lệ 3x áp dụng cho một phần tử, thì bạn có thể thấy các thanh cuộn và các thanh tương tự, ngay cả khi phần tử đó 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ử từ góc dưới cùng bên phải (với transform-origin: bottom right). Cách này sẽ khiến các phần tử quá kích thước mở rộng 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 phủ định.

Kết luận

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

Hãy thử chơi và cho chúng tôi biết làm cách nào bạn có thể tiếp tục chơi.