Xây dựng thành phần Thẻ

Thông tin tổng quan cơ bản về cách tạo thành phần thẻ tương tự như các thành phần thẻ trong ứng dụng iOS và Android.

Trong bài đăng này, tôi muốn chia sẻ suy nghĩ về việc xây dựng một thành phần Thẻ cho web có khả năng thích ứng, hỗ trợ nhiều phương thức nhập trên thiết bị và hoạt động trên các trình duyệt. Hãy dùng thử bản minh hoạ.

Bản minh hoạ

Nếu bạn thích xem video, hãy xem phiên bản video của bài đăng này trên YouTube:

Tổng quan

Thẻ là một thành phần phổ biến của các hệ thống thiết kế nhưng có thể có nhiều hình dạng và kiểu dáng. Trước tiên, có các thẻ trên máy tính được tạo trên phần tử <frame>, và giờ đây, chúng ta có các thành phần di động mượt mà tạo ảnh động cho nội dung dựa trên các thuộc tính vật lý. Tất cả đều cố gắng làm cùng một việc: tiết kiệm không gian.

Ngày nay, yếu tố thiết yếu của trải nghiệm người dùng về thẻ là khu vực điều hướng bằng nút bật/tắt chế độ hiển thị nội dung trong khung hiển thị. Nhiều khu vực nội dung khác nhau chia sẻ cùng một không gian, nhưng được trình bày có điều kiện dựa trên nút đã chọn trong bảng điều hướng.

ảnh ghép khá lộn xộn do sự đa dạng về kiểu mà web đã áp dụng cho khái niệm thành phần
Ảnh ghép các kiểu thiết kế web thành phần thẻ trong hơn 10 năm qua

Chiến thuật web

Nhìn chung, tôi thấy thành phần này khá dễ tạo, nhờ một số tính năng quan trọng của nền tảng web:

  • scroll-snap-points để vuốt và tương tác với bàn phím một cách thanh lịch với các vị trí dừng cuộn thích hợp
  • Liên kết sâu thông qua hàm băm URL để hỗ trợ tính năng chia sẻ và neo cuộn trong trang do trình duyệt xử lý
  • Hỗ trợ trình đọc màn hình bằng mã đánh dấu phần tử <a>id="#hash"
  • prefers-reduced-motion để bật hiệu ứng chuyển đổi khuếch tán và tính năng cuộn trang tức thì
  • Tính năng web @scroll-timeline trong bản nháp để tự động gạch chân và thay đổi màu của thẻ đã chọn

HTML

Về cơ bản, trải nghiệm người dùng ở đây là: nhấp vào một đường liên kết, để URL đại diện cho trạng thái trang lồng nhau, sau đó xem khu vực nội dung cập nhật khi trình duyệt cuộn đến phần tử phù hợp.

Có một số thành phần nội dung cấu trúc trong đó: đường liên kết và :target. Chúng ta cần một danh sách đường liên kết, trong đó <nav> là phù hợp và một danh sách phần tử <article>, trong đó <section> là phù hợp. Mỗi hàm băm đường liên kết sẽ khớp với một phần, cho phép trình duyệt cuộn các mục thông qua tính năng neo.

Nhấp vào nút đường liên kết, trượt nội dung được lấy làm tâm điểm vào

Ví dụ: khi nhấp vào một đường liên kết, bài viết :target sẽ tự động được lấy tiêu điểm trong Chrome 89 mà không cần JS. Sau đó, người dùng có thể cuộn nội dung bài viết bằng thiết bị đầu vào như bình thường. Đây là nội dung bổ sung, như được chỉ định trong thẻ đánh dấu.

Tôi đã sử dụng mã đánh dấu sau để sắp xếp các thẻ:

<snap-tabs>
  <header>
    <nav>
      <a></a>
      <a></a>
      <a></a>
      <a></a>
    </nav>
  </header>
  <section>
    <article></article>
    <article></article>
    <article></article>
    <article></article>
  </section>
</snap-tabs>

Tôi có thể thiết lập mối liên kết giữa các phần tử <a><article> bằng các thuộc tính hrefid như sau:

<snap-tabs>
  <header>
    <nav>
      <a href="#responsive"></a>
      <a href="#accessible"></a>
      <a href="#overscroll"></a>
      <a href="#more"></a>
    </nav>
  </header>
  <section>
    <article id="responsive"></article>
    <article id="accessible"></article>
    <article id="overscroll"></article>
    <article id="more"></article>
  </section>
</snap-tabs>

Tiếp theo, tôi đã điền vào các bài viết bằng nhiều đoạn văn bản Lorem ipsum và các đường liên kết có độ dài và bộ hình ảnh tiêu đề khác nhau. Khi có nội dung để làm việc, chúng ta có thể bắt đầu thiết kế bố cục.

Bố cục cuộn

Có 3 loại vùng cuộn khác nhau trong thành phần này:

  • Thanh điều hướng (hồng) có thể cuộn ngang
  • Khu vực nội dung (màu xanh dương) có thể cuộn theo chiều ngang
  • Mỗi mục bài viết (màu xanh lục) có thể cuộn theo chiều dọc.
3 hộp đầy màu sắc với các mũi tên chỉ hướng có màu sắc phù hợp, vạch ra các khu vực cuộn và cho biết hướng cuộn.

Có 2 loại phần tử liên quan đến thao tác cuộn:

  1. Cửa sổ
    Hộp có kích thước được xác định có kiểu thuộc tính overflow.
  2. Vùng hiển thị quá khổ
    Trong bố cục này, đó là các vùng chứa danh sách: đường liên kết điều hướng, bài viết theo phần và nội dung bài viết.

Bố cục <snap-tabs>

Bố cục cấp cao nhất mà tôi chọn là flex (Flexbox). Tôi đặt hướng thành column để tiêu đề và phần được sắp xếp theo chiều dọc. Đây là cửa sổ cuộn đầu tiên và nó ẩn mọi thứ có phần tràn bị ẩn. Tiêu đề và phần sẽ sớm sử dụng tính năng cuộn xuống dưới cùng, dưới dạng các vùng riêng lẻ.

HTML
<snap-tabs>
  <header></header>
  <section></section>
</snap-tabs>
CSS
  snap-tabs {
  display: flex;
  flex-direction: column;

  /* establish primary containing box */
  overflow: hidden;
  position: relative;

  & > section {
    /* be pushy about consuming all space */
    block-size: 100%;
  }

  & > header {
    /* defend against 
needing 100% */ flex-shrink: 0; /* fixes cross browser quarks */ min-block-size: fit-content; } }

Quay lại sơ đồ 3 cuộn đầy màu sắc:

  • <header> hiện đã sẵn sàng trở thành vùng chứa cuộn (hồng).
  • <section> được chuẩn bị để trở thành vùng chứa cuộn (màu xanh dương).

Các khung mà tôi đã làm nổi bật bên dưới bằng VisBug giúp chúng ta thấy cửa sổ mà vùng chứa cuộn đã tạo.

các phần tử tiêu đề và phần có lớp phủ màu hồng đậm, nêu rõ không gian mà các phần tử này chiếm trong thành phần

Bố cục <header> của thẻ

Bố cục tiếp theo gần giống như vậy: Tôi sử dụng flex để tạo thứ tự dọc.

HTML
<snap-tabs>
  <header>
    <nav></nav>
    <span class="snap-indicator"></span>
  </header>
  <section></section>
</snap-tabs>
CSS
header {
  display: flex;
  flex-direction: column;
}

.snap-indicator sẽ di chuyển theo chiều ngang với nhóm đường liên kết và bố cục tiêu đề này sẽ giúp thiết lập giai đoạn đó. Không có phần tử có vị trí tuyệt đối nào ở đây!

các phần tử nav và span.indicator có lớp phủ màu hồng đậm, nêu rõ không gian mà các phần tử này chiếm trong thành phần

Tiếp theo là các kiểu cuộn. Hóa ra chúng ta có thể chia sẻ các kiểu cuộn giữa 2 khu vực cuộn theo chiều ngang (tiêu đề và mục), vì vậy, tôi đã tạo một lớp tiện ích là .scroll-snap-x.

.scroll-snap-x {
  /* browser decide if x is ok to scroll and show bars on, y hidden */
  overflow: auto hidden;
  /* prevent scroll chaining on x scroll */
  overscroll-behavior-x: contain;
  /* scrolling should snap children on x */
  scroll-snap-type: x mandatory;

  @media (hover: none) {
    scrollbar-width: none;

    &::-webkit-scrollbar {
      width: 0;
      height: 0;
    }
  }
}

Mỗi thành phần hiển thị cần có vùng tràn trên trục x, vùng chứa cuộn để chặn tình trạng cuộn quá mức, thanh cuộn ẩn cho thiết bị cảm ứng và cuối cùng là tính năng cuộn nhanh để khoá vùng hiển thị nội dung. Bạn có thể truy cập vào thứ tự thẻ bàn phím và mọi hoạt động tương tác đều hướng tâm điểm một cách tự nhiên. Các vùng chứa ảnh chụp nhanh cuộn cũng có được một kiểu tương tác kiểu băng chuyền đẹp mắt từ bàn phím.

Bố cục <nav> của tiêu đề thẻ

Các đường liên kết điều hướng cần được bố trí theo một dòng, không có dấu ngắt dòng, căn giữa theo chiều dọc và mỗi mục đường liên kết phải chụp nhanh vào vùng chứa cuộn-chụp nhanh. Swift hoạt động cho CSS 2021!

HTML
<nav>
  <a></a>
  <a></a>
  <a></a>
  <a></a>
</nav>
CSS
  nav {
  display: flex;

  & a {
    scroll-snap-align: start;

    display: inline-flex;
    align-items: center;
    white-space: nowrap;
  }
}

Mỗi đường liên kết tự định kiểu và kích thước, vì vậy, bố cục điều hướng chỉ cần chỉ định hướng và luồng. Chiều rộng riêng biệt trên các mục điều hướng giúp quá trình chuyển đổi giữa các thẻ trở nên thú vị khi chỉ báo điều chỉnh chiều rộng của mục đó cho phù hợp với mục tiêu mới. Tuỳ thuộc vào số lượng phần tử có trong đây, trình duyệt sẽ hiển thị thanh cuộn hay không.

các phần tử a của thanh điều hướng có lớp phủ màu hồng đậm, nêu rõ không gian mà các phần tử này chiếm trong thành phần cũng như vị trí tràn

Bố cục <section> của thẻ

Phần này là một mục flex và cần phải là phần tiêu thụ không gian chính. Trình quản lý cũng cần tạo các cột để đặt bài viết vào. Một lần nữa, hãy làm việc nhanh chóng cho CSS 2021! block-size: 100% kéo giãn phần tử này để lấp đầy phần tử mẹ nhiều nhất có thể, sau đó đối với bố cục của riêng phần tử này, phần tử này sẽ tạo một loạt cột có chiều rộng 100% của phần tử mẹ. Tỷ lệ phần trăm hoạt động hiệu quả ở đây vì chúng ta đã viết các quy tắc ràng buộc mạnh mẽ cho thành phần mẹ.

HTML
<section>
  <article></article>
  <article></article>
  <article></article>
  <article></article>
</section>
CSS
  section {
  block-size: 100%;

  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 100%;
}

Điều này giống như chúng ta đang nói "mở rộng theo chiều dọc nhiều nhất có thể, theo cách mạnh mẽ" (hãy nhớ tiêu đề chúng ta đặt thành flex-shrink: 0: đó là biện pháp bảo vệ chống lại thao tác đẩy mở rộng này), giúp đặt chiều cao hàng cho một nhóm cột có chiều cao đầy đủ. Kiểu auto-flow yêu cầu lưới luôn sắp xếp các thành phần con theo một dòng ngang, không gói, chính xác là những gì chúng ta muốn; để tràn ra cửa sổ mẹ.

các phần tử bài viết có lớp phủ màu hồng đậm, nêu rõ không gian mà các phần tử đó chiếm trong thành phần và vị trí tràn

Đôi khi, tôi thấy khó hiểu những điều này! Phần tử phần này vừa với một hộp, nhưng cũng tạo ra một tập hợp các hộp. Tôi hy vọng hình ảnh và nội dung giải thích sẽ giúp ích cho bạn.

Bố cục <article> của thẻ

Người dùng phải có thể cuộn nội dung bài viết và thanh cuộn chỉ xuất hiện nếu có tràn. Các phần tử bài viết này ở vị trí gọn gàng. Các thành phần này đồng thời là thành phần mẹ cuộn và thành phần con cuộn. Trình duyệt thực sự đang xử lý một số thao tác tương tác khó khăn bằng cách chạm, chuột và bàn phím cho chúng ta tại đây.

HTML
<article>
  <h2></h2>
  <p></p>
  <p></p>
  <h2></h2>
  <p></p>
  <p></p>
  ...
</article>
CSS
article {
  scroll-snap-align: start;

  overflow-y: auto;
  overscroll-behavior-y: contain;
}

Tôi chọn để các bài viết chụp nhanh trong thanh cuộn mẹ. Tôi rất thích cách các mục đường liên kết điều hướng và phần tử bài viết chụp nhanh vào phần bắt đầu cùng dòng của vùng chứa cuộn tương ứng. Nó trông giống như một mối quan hệ hài hòa.

phần tử article và các phần tử con của phần tử này có lớp phủ màu hồng đậm, nêu rõ không gian mà các phần tử này chiếm trong thành phần và hướng tràn

Bài viết là một thành phần con của lưới và kích thước của bài viết được xác định trước là vùng khung nhìn mà chúng ta muốn cung cấp trải nghiệm người dùng cuộn. Điều này có nghĩa là tôi không cần bất kỳ kiểu chiều cao hoặc chiều rộng nào ở đây, tôi chỉ cần xác định cách tràn. Tôi đặt overflow-y thành auto, sau đó cũng chặn các hoạt động tương tác cuộn bằng thuộc tính overscroll-behavior tiện dụng.

Tóm tắt 3 khu vực cuộn

Dưới đây là phần cài đặt hệ thống mà tôi đã chọn "luôn hiển thị thanh cuộn". Tôi nghĩ rằng việc bật chế độ cài đặt này là rất quan trọng để bố cục hoạt động, vì tôi cần xem xét bố cục và cách điều phối cuộn.

3 thanh cuộn được đặt thành hiển thị, hiện đang chiếm không gian bố cục và thành phần của chúng ta vẫn trông rất đẹp

Tôi nghĩ rằng việc xem rãnh thanh cuộn trong thành phần này giúp cho thấy rõ vị trí của các khu vực cuộn, hướng mà các khu vực này hỗ trợ và cách các khu vực này tương tác với nhau. Hãy xem xét cách mỗi khung cửa sổ cuộn này cũng là khung mẹ flex hoặc lưới cho một bố cục.

Công cụ cho nhà phát triển có thể giúp chúng ta hình dung điều này:

các khu vực cuộn có lớp phủ công cụ lưới và flexbox, nêu rõ không gian mà các khu vực này chiếm trong thành phần và hướng tràn
Chromium Devtools, hiển thị bố cục phần tử điều hướng flexbox chứa đầy phần tử neo, bố cục phần lưới chứa đầy phần tử bài viết và phần tử bài viết chứa đầy các đoạn văn bản và một phần tử tiêu đề.

Bố cục cuộn đã hoàn chỉnh: chụp nhanh, có thể liên kết sâu và có thể truy cập bằng bàn phím. Nền tảng vững chắc để cải thiện trải nghiệm người dùng, phong cách và sự hài lòng.

Điểm nổi bật của tính năng

Các thành phần con được chụp nhanh khi cuộn duy trì vị trí khoá của chúng trong quá trình đổi kích thước. Điều này có nghĩa là JavaScript sẽ không cần hiển thị bất kỳ nội dung nào khi xoay thiết bị hoặc đổi kích thước trình duyệt. Hãy thử trong Chế độ thiết bị của Chromium DevTools bằng cách chọn chế độ bất kỳ khác với Đáp ứng, sau đó đổi kích thước khung thiết bị. Hãy lưu ý rằng phần tử này vẫn hiển thị và được khoá với nội dung của phần tử đó. Tính năng này đã có từ khi Chromium cập nhật cách triển khai để phù hợp với thông số kỹ thuật. Dưới đây là bài đăng trên blog về tính năng này.

Hoạt ảnh

Mục tiêu của công việc tạo ảnh động ở đây là liên kết rõ ràng các lượt tương tác với phản hồi giao diện người dùng. Điều này giúp hướng dẫn hoặc hỗ trợ người dùng khám phá liền mạch tất cả nội dung (hy vọng là như vậy). Tôi sẽ thêm chuyển động có mục đích và có điều kiện. Giờ đây, người dùng có thể chỉ định lựa chọn ưu tiên về chuyển động trong hệ điều hành của họ và tôi rất thích phản hồi các lựa chọn ưu tiên của họ trong giao diện của mình.

Tôi sẽ liên kết đường gạch dưới thẻ với vị trí cuộn bài viết. Tính năng chụp nhanh không chỉ giúp căn chỉnh đẹp mắt mà còn giúp cố định điểm bắt đầu và kết thúc của ảnh động. Điều này giúp <nav> (đóng vai trò như một bản đồ thu nhỏ) kết nối với nội dung. Chúng ta sẽ kiểm tra lựa chọn ưu tiên về chuyển động của người dùng từ cả CSS và JS. Có một số nơi tuyệt vời để bạn cân nhắc!

Hành vi cuộn

Có cơ hội để cải thiện hành vi chuyển động của cả :targetelement.scrollIntoView(). Theo mặc định, giá trị này là tức thì. Trình duyệt chỉ đặt vị trí cuộn. Nếu chúng ta muốn chuyển sang vị trí cuộn đó thay vì nhấp nháy ở đó thì sao?

@media (prefers-reduced-motion: no-preference) {
  .scroll-snap-x {
    scroll-behavior: smooth;
  }
}

Vì chúng ta đang giới thiệu chuyển động ở đây và chuyển động mà người dùng không kiểm soát (chẳng hạn như cuộn), nên chúng ta chỉ áp dụng kiểu này nếu người dùng không có lựa chọn ưu tiên trong hệ điều hành về việc giảm chuyển động. Bằng cách này, chúng ta chỉ giới thiệu tính năng cuộn cho những người đồng ý với tính năng này.

Chỉ báo thẻ

Mục đích của ảnh động này là giúp liên kết chỉ báo với trạng thái của nội dung. Tôi quyết định tô màu các kiểu border-bottom chuyển màu cho những người dùng thích giảm chuyển động và ảnh động cuộn liên kết trượt + làm mờ màu cho những người dùng không ngại chuyển động.

Trong Chromium Devtools, tôi có thể bật/tắt tuỳ chọn ưu tiên và minh hoạ 2 kiểu chuyển đổi khác nhau. Tôi rất vui khi xây dựng ứng dụng này.

@media (prefers-reduced-motion: reduce) {
  snap-tabs > header a {
    border-block-end: var(--indicator-size) solid hsl(var(--accent) / 0%);
    transition: color .7s ease, border-color .5s ease;

    &:is(:target,:active,[active]) {
      color: var(--text-active-color);
      border-block-end-color: hsl(var(--accent));
    }
  }

  snap-tabs .snap-indicator {
    visibility: hidden;
  }
}

Tôi ẩn .snap-indicator khi người dùng muốn giảm chuyển động vì tôi không cần đến nó nữa. Sau đó, tôi thay thế bằng các kiểu border-block-endtransition. Ngoài ra, hãy lưu ý trong hoạt động tương tác với thẻ rằng mục điều hướng đang hoạt động không chỉ có phần gạch dưới thương hiệu nổi bật mà màu văn bản của mục đó cũng đậm hơn. Phần tử đang hoạt động có độ tương phản màu văn bản cao hơn và điểm nhấn sáng dưới ánh sáng.

Chỉ cần thêm một vài dòng CSS là người dùng sẽ cảm thấy được quan tâm (theo nghĩa là chúng ta đang cẩn thận tôn trọng lựa chọn ưu tiên về chuyển động của họ). Tôi thích cái tên đó.

@scroll-timeline

Trong phần trên, tôi đã hướng dẫn bạn cách xử lý các kiểu chuyển đổi động giảm và trong phần này, tôi sẽ hướng dẫn bạn cách liên kết chỉ báo và vùng cuộn với nhau. Tiếp theo là một số nội dung thử nghiệm thú vị. Tôi hy vọng bạn cũng hào hứng như tôi.

const { matches:motionOK } = window.matchMedia(
  '(prefers-reduced-motion: no-preference)'
);

Trước tiên, tôi kiểm tra lựa chọn ưu tiên về chuyển động của người dùng từ JavaScript. Nếu kết quả của việc này là false, nghĩa là người dùng muốn giảm chuyển động, thì chúng ta sẽ không chạy bất kỳ hiệu ứng chuyển động liên kết cuộn nào.

if (motionOK) {
  // motion based animation code
}

Tại thời điểm viết bài này, trình duyệt hỗ trợ cho @scroll-timeline là không có. Đây là một thông số kỹ thuật nháp chỉ có các phương thức triển khai thử nghiệm. Tuy nhiên, trình duyệt này có một polyfill mà tôi sử dụng trong bản minh hoạ này.

ScrollTimeline

Mặc dù cả CSS và JavaScript đều có thể tạo tiến trình cuộn, nhưng tôi đã chọn sử dụng JavaScript để có thể sử dụng các phép đo phần tử trực tiếp trong ảnh động.

const sectionScrollTimeline = new ScrollTimeline({
  scrollSource: tabsection,  // snap-tabs > section
  orientation: 'inline',     // scroll in the direction letters flow
  fill: 'both',              // bi-directional linking
});

Tôi muốn 1 thứ tuân theo vị trí cuộn của một thứ khác và bằng cách tạo một ScrollTimeline, tôi xác định trình điều khiển của đường liên kết cuộn, scrollSource. Thông thường, ảnh động trên web chạy theo một dấu thời gian khung hình toàn cục, nhưng với sectionScrollTimeline tuỳ chỉnh trong bộ nhớ, tôi có thể thay đổi tất cả những điều đó.

tabindicator.animate({
    transform: ...,
    width: ...,
  }, {
    duration: 1000,
    fill: 'both',
    timeline: sectionScrollTimeline,
  }
);

Trước khi đi vào các khung hình chính của ảnh động, tôi nghĩ điều quan trọng là phải chỉ ra rằng phần tử theo dõi của thao tác cuộn, tabindicator, sẽ được tạo ảnh động dựa trên dòng thời gian tuỳ chỉnh, tức là phần cuộn của phần. Thao tác này sẽ hoàn tất mối liên kết, nhưng còn thiếu thành phần cuối cùng, các điểm có trạng thái để tạo ảnh động giữa các điểm, còn gọi là khung hình chính.

Khung hình chính động

Có một cách CSS khai báo thuần tuý thực sự mạnh mẽ để tạo ảnh động bằng @scroll-timeline, nhưng ảnh động tôi chọn làm lại quá linh động. Không có cách nào để chuyển đổi giữa chiều rộng auto và không có cách nào để tạo một số khung hình chính một cách linh động dựa trên chiều dài của phần tử con.

Tuy nhiên, JavaScript biết cách lấy thông tin đó, vì vậy, chúng ta sẽ tự lặp lại các phần tử con và lấy các giá trị đã tính toán trong thời gian chạy:

tabindicator.animate({
    transform: [...tabnavitems].map(({offsetLeft}) =>
      `translateX(${offsetLeft}px)`),
    width: [...tabnavitems].map(({offsetWidth}) =>
      `${offsetWidth}px`)
  }, {
    duration: 1000,
    fill: 'both',
    timeline: sectionScrollTimeline,
  }
);

Đối với mỗi tabnavitem, hãy huỷ cấu trúc vị trí offsetLeft và trả về một chuỗi sử dụng vị trí đó làm giá trị translateX. Thao tác này sẽ tạo 4 khung hình chính biến đổi cho ảnh động. Tương tự như vậy đối với chiều rộng, mỗi chiều rộng được hỏi chiều rộng động là bao nhiêu rồi được dùng làm giá trị khung hình chính.

Dưới đây là kết quả mẫu, dựa trên phông chữ và lựa chọn ưu tiên của trình duyệt:

Khung hình chính TranslateX:

[...tabnavitems].map(({offsetLeft}) =>
  `translateX(${offsetLeft}px)`)

// results in 4 array items, which represent 4 keyframe states
// ["translateX(0px)", "translateX(121px)", "translateX(238px)", "translateX(464px)"]

Khung hình chính theo chiều rộng:

[...tabnavitems].map(({offsetWidth}) =>
  `${offsetWidth}px`)

// results in 4 array items, which represent 4 keyframe states
// ["121px", "117px", "226px", "67px"]

Tóm tắt chiến lược, chỉ báo thẻ hiện sẽ tạo ảnh động trên 4 khung hình chính, tuỳ thuộc vào vị trí chụp nhanh cuộn của thanh cuộn phần. Các điểm chụp nhanh tạo ra sự phân định rõ ràng giữa các khung hình chính và thực sự làm tăng cảm giác đồng bộ của ảnh động.

thẻ đang hoạt động và thẻ không hoạt động được hiển thị cùng với lớp phủ VisBug cho thấy điểm tương phản đạt yêu cầu cho cả hai

Người dùng điều khiển ảnh động bằng hoạt động tương tác, xem chiều rộng và vị trí của chỉ báo thay đổi từ phần này sang phần tiếp theo, theo dõi hoàn hảo bằng thao tác cuộn.

Có thể bạn chưa nhận thấy, nhưng tôi rất tự hào về hiệu ứng chuyển đổi màu sắc khi mục điều hướng được làm nổi bật được chọn.

Màu xám nhạt hơn chưa được chọn sẽ xuất hiện lùi xa hơn khi mục được làm nổi bật có độ tương phản cao hơn. Thông thường, bạn sẽ chuyển đổi màu cho văn bản, chẳng hạn như khi di chuột và khi được chọn, nhưng bạn có thể chuyển đổi màu đó ở cấp độ cao hơn khi cuộn, đồng bộ hoá với chỉ báo gạch dưới.

Dưới đây là cách tôi thực hiện:

tabnavitems.forEach(navitem => {
  navitem.animate({
      color: [...tabnavitems].map(item =>
        item === navitem
          ? `var(--text-active-color)`
          : `var(--text-color)`)
    }, {
      duration: 1000,
      fill: 'both',
      timeline: sectionScrollTimeline,
    }
  );
});

Mỗi đường liên kết điều hướng thẻ cần có ảnh động màu mới này, theo dõi cùng một tiến trình cuộn với chỉ báo gạch dưới. Tôi sử dụng cùng một tiến trình như trước: vì vai trò của tiến trình này là phát ra một dấu kiểm khi cuộn, nên chúng ta có thể sử dụng dấu kiểm đó trong bất kỳ loại ảnh động nào mà chúng ta muốn. Như đã làm trước đó, tôi tạo 4 khung hình chính trong vòng lặp và trả về màu sắc.

[...tabnavitems].map(item =>
  item === navitem
    ? `var(--text-active-color)`
    : `var(--text-color)`)

// results in 4 array items, which represent 4 keyframe states
// [
  "var(--text-active-color)",
  "var(--text-color)",
  "var(--text-color)",
  "var(--text-color)",
]

Khung hình chính có màu var(--text-active-color) làm nổi bật đường liên kết, còn nếu không thì đó là màu văn bản tiêu chuẩn. Vòng lặp lồng nhau ở đó giúp việc này tương đối đơn giản, vì vòng lặp bên ngoài là mỗi mục điều hướng và vòng lặp bên trong là các khung hình chính riêng của mỗi mục điều hướng. Tôi kiểm tra xem phần tử vòng lặp bên ngoài có giống với phần tử vòng lặp bên trong hay không và sử dụng phần tử đó để biết thời điểm phần tử đó được chọn.

Tôi rất vui khi viết bài này. Rất nhiều.

Các tính năng nâng cao khác về JavaScript

Xin lưu ý rằng cốt lõi của nội dung tôi sắp trình bày hoạt động mà không cần JavaScript. Tuy nhiên, hãy xem cách chúng ta có thể cải thiện ứng dụng này khi có JS.

Đường liên kết sâu là một thuật ngữ dành cho thiết bị di động, nhưng tôi nghĩ ý định của đường liên kết sâu đã được đáp ứng ở đây bằng các thẻ, trong đó bạn có thể chia sẻ URL trực tiếp đến nội dung của thẻ. Trình duyệt sẽ di chuyển đến mã nhận dạng được so khớp trong hàm băm URL trên trang. Tôi nhận thấy trình xử lý onload này đã tạo ra hiệu ứng trên các nền tảng.

window.onload = () => {
  if (location.hash) {
    tabsection.scrollLeft = document
      .querySelector(location.hash)
      .offsetLeft;
  }
}

Đồng bộ hoá kết thúc cuộn

Người dùng không phải lúc nào cũng nhấp hoặc sử dụng bàn phím, đôi khi họ chỉ cuộn tự do vì họ có thể làm như vậy. Khi thanh cuộn phần ngừng cuộn, vị trí dừng cần khớp với thanh điều hướng trên cùng.

Dưới đây là cách tôi chờ kết thúc cuộn: js tabsection.addEventListener('scroll', () => { clearTimeout(tabsection.scrollEndTimer); tabsection.scrollEndTimer = setTimeout(determineActiveTabSection, 100); });

Bất cứ khi nào các phần đang được cuộn, hãy xoá thời gian chờ của phần nếu có và bắt đầu một phần mới. Khi các phần ngừng cuộn, đừng xoá thời gian chờ và kích hoạt 100 mili giây sau khi nghỉ. Khi sự kiện này kích hoạt, hãy gọi hàm tìm cách xác định vị trí người dùng đã dừng.

const determineActiveTabSection = () => {
  const i = tabsection.scrollLeft / tabsection.clientWidth;
  const matchingNavItem = tabnavitems[i];

  matchingNavItem && setActiveTab(matchingNavItem);
};

Giả sử cuộn được chụp nhanh, việc chia vị trí cuộn hiện tại cho chiều rộng của vùng cuộn sẽ dẫn đến một số nguyên chứ không phải số thập phân. Sau đó, tôi cố gắng lấy một navitem từ bộ nhớ đệm thông qua chỉ mục được tính toán này và nếu tìm thấy nội dung nào đó, tôi sẽ gửi nội dung trùng khớp để được đặt thành đang hoạt động.

const setActiveTab = tabbtn => {
  tabnav
    .querySelector(':scope a[active]')
    .removeAttribute('active');

  tabbtn.setAttribute('active', '');
  tabbtn.scrollIntoView();
};

Bạn có thể bắt đầu thiết lập thẻ đang hoạt động bằng cách xoá mọi thẻ đang hoạt động, sau đó gán thuộc tính trạng thái đang hoạt động cho mục điều hướng sắp tới. Lệnh gọi đến scrollIntoView() có một hoạt động tương tác thú vị với CSS mà bạn nên lưu ý.

.scroll-snap-x {
  overflow: auto hidden;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;

  @media (prefers-reduced-motion: no-preference) {
    scroll-behavior: smooth;
  }
}

Trong CSS tiện ích cuộn chụp nhanh theo chiều ngang, chúng tôi đã lồng một truy vấn nội dung đa phương tiện áp dụng tính năng cuộn smooth nếu người dùng chấp nhận chuyển động. JavaScript có thể tự do thực hiện các lệnh gọi để cuộn các phần tử vào chế độ xem và CSS có thể quản lý trải nghiệm người dùng theo cách khai báo. Đôi khi, chúng tạo ra một sự kết hợp thú vị.

Kết luận

Giờ thì bạn đã biết cách tôi làm, còn bạn thì sao?! Điều này tạo ra một số cấu trúc thành phần thú vị! Ai sẽ tạo phiên bản đầu tiên có các khe trong khung yêu thích của họ? 🙂

Hãy đa dạng hoá các phương pháp và tìm hiểu tất cả các cách xây dựng trên web. Hãy tạo một Glitch, twitt cho tôi phiên bản của bạn và tôi sẽ thêm phiên bản đó vào phần Bản phối lại của cộng đồng bên dưới.

Bản phối lại của cộng đồng