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

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

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

Bản minh hoạ

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

Tổng quan

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

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

ảnh ghép khá hỗn độn do rất đa dạng về kiểu dáng mà web đã áp dụng cho khái niệm thành phần
Ảnh ghép các phong cách thiết kế web cho 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ễ xây dựng, nhờ có một số tính năng quan trọng của nền tảng web:

  • scroll-snap-points để thực hiện các thao tác vuốt và tương tác bằng bàn phím mượt mà với các vị trí dừng cuộn phù hợp
  • Đường liên kết sâu thông qua hàm băm URL cho trình duyệt đã xử lý hỗ trợ chia sẻ và neo cuộn trong trang
  • 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 mờ dần và cuộn tức thì trong trang
  • Tính năng web @scroll-timeline dạng nháp để tự động gạch dưới và thay đổi màu 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 được lồng, sau đó xem thông tin cập nhật của vùng nội dung khi trình duyệt cuộn đến phần tử phù hợp.

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

Người dùng nhấp vào nút đường liên kết sau khi trượt nội dung trọng tâm

Ví dụ: việc nhấp vào một đường liên kết sẽ tự động tập trung vào bài viết :target trong Chrome 89, không cần có 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 của họ như mọi khi. Đây là nội dung bổ sung, như được nêu trong mã đánh dấu.

Tôi đã sử dụng mã đánh dấu sau đây để 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 kết nối 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 một lượng lớn lorem và các đường liên kết có bộ tiêu đề hình ảnh và độ dài kết hợp. Với nội dung cần xử lý, chúng ta có thể bắt đầu bố cục.

Bố cục cuộn

Có 3 loại khu vực cuộn trong thành phần này:

  • Thanh điều hướng (màu hồng) có thể cuộn theo chiều ngang
  • Vùng nội dung (màu xanh dương) có thể cuộn theo chiều ngang
  • Mỗi mục trong bài viết (màu xanh lục) đều có thể cuộn theo chiều dọc.
3 hộp nhiều màu sắc có các mũi tên định hướng cùng màu với nhau, vạch ra các khu vực cuộn và cho biết hướng di chuyển của chúng.

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

  1. Một cửa sổ
    Một hộp có kích thước được xác định và có kiểu thuộc tính overflow.
  2. Giao diện 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 trong mục 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à linh hoạt (Flexbox). Tôi đặt hướng thành column, vì vậy 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 của chúng ta và cửa sổ này ẩn mọi nội dung bằng cách ẩn mục bổ sung. Tiêu đề và phần sẽ sớm sử dụng hiệu ứng cuộn quá mức 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; } }

Trỏ trở lại biểu đồ cuộn 3 màu:

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

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 cửa sổ mà vùng chứa cuộn đã tạo.

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

Bố cục thẻ <header>

Bố cục tiếp theo gần giống như bố cục tiếp theo: 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 cùng với nhóm các đườ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 ở đâ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à chúng chiếm trong thành phần

Tiếp theo là kiểu cuộn. Hoá ra chúng ta có thể chia sẻ kiểu cuộn giữa 2 khu vực cuộn ngang (tiêu đề và phần), vì vậy, tôi đã tạo một lớp tiện ích, .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 tệp đều cần tràn trên trục x, vùng chứa cuộn để chặn hiện tượ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 chụp nhanh để khoá khu vực trình bày nội dung. Thứ tự thẻ trên bàn phím của chúng tôi có thể truy cập được và mọi tương tác đều hướng dẫn lấy nét tự nhiên. Các vùng chứa chụp nhanh cũng có thể nhận được tương tác kiểu băng chuyền đẹp mắt từ bàn phím.

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

Bạn cần bố trí các đường liên kết điều hướng trên một dòng, không có dấu ngắt dòng, được căn giữa theo chiều dọc và mỗi mục liên kết phải bám sát vùng chứa cuộn-snap. Swift 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 kiểu liên kết sẽ tự tạo 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 duy nhất trên các mục điều hướng giúp quá trình chuyển đổi giữa các thẻ thú vị vì chỉ báo điều chỉnh chiều rộng theo mục tiêu mới. Tuỳ thuộc vào số lượng phần tử trong đây, trình duyệt sẽ hiển thị thanh cuộn hoặc không.

các thành phần của thành phần điều hướng có lớp phủ màu hồng phấn, phác thảo không gian mà chúng chiếm trong thành phần cũng như vị trí mà chúng tràn vào

Bố cục thẻ <section>

Phần này là một mục linh hoạt và cần phải là đối tượng sử dụng không gian chủ yếu. Hàm này cũng cần tạo cột cho bài viết cần được đặt vào. Xin nhắc lại, hãy nhanh chóng làm việc cùng CSS 2021! block-size: 100% kéo dài 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 riêng, công cụ này sẽ tạo một loạt cột có chiều rộng bằng 100% của phần tử mẹ. Tỷ lệ phần trăm hoạt động tốt ở đây vì chúng tôi đã viết những ràng buộc mạnh mẽ trên đơn vị tiền tệ 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%;
}

Như thể chúng ta đang nói "mở rộng theo chiều dọc nhiều nhất có thể, theo cách tự đề cao" (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 tính năng đẩy mở rộng này), nhằm thiết lập chiều cao hàng cho một tập hợp các cột chiều cao đầy đủ. Kiểu auto-flow yêu cầu lưới luôn bố trí các phần tử con theo một dòng ngang, không xuống dòng, chính xác những gì chúng ta muốn; để tràn cửa sổ mẹ.

các thành phần bài viết có lớp phủ màu hồng phấn, phác thảo không gian mà chúng chiếm trong thành phần và nơi chúng tràn vào

Đôi khi tôi cảm thấy thật khó khăn! Phần tử mục này vừa khít 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à giải thích hữu ích với bạn.

Bố cục thẻ <article>

Người dùng cuộn được nội dung bài viết còn thanh cuộn chỉ xuất hiện nếu có mục tràn. Các thành phần bài viết này được đặt ở vị trí gọn gàng. Chúng đồng thời là thành phần mẹ cuộn và con cuộn. Trình duyệt thực sự đang xử lý một số thao tác tương tác phức tạp của thao tác chạm, chuột và bàn phím cho chúng ta ở đâ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 đặt các bài viết trong thanh cuộn chính. Tôi thực sự thích cách các mục trong đường liên kết điều hướng và các thành phần của bài viết hiển thị ngay tại điểm bắt đầu cùng dòng của vùng chứa cuộn tương ứng. Nó trông có vẻ như một mối quan hệ hài hoà.

phần tử bài viết và các phần tử con của phần tử con đều có lớp phủ màu hồng phấn, phác thảo không gian mà chúng chiếm trong thành phần và hướng mà các phần tử này tràn

Bài viết này là một lưới con và kích thước của bài viết được xác định trước là khu vực khung nhìn mà chúng ta muốn cung cấp trải nghiệm người dùng cuộn. Tức là tôi không cần 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 tự động, sau đó cũng chặn các tương tác cuộn bằng thuộc tính hành vi cuộn quá mức (overscroll-attribute) hữu ích.

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

Trong phần cài đặt hệ thống, tôi đã chọn "luôn hiển thị thanh cuộn" trong phần cài đặt hệ thống. Tôi nghĩ điều quan trọng gấp đôi là bố cục phải hoạt động với chế độ cài đặt này, vì tôi phải xem lại bố cục và việc điều phối thao tác cuộn.

3 thanh cuộn được thiết lập để 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 cho rằng việc thấy được rãnh trên thanh cuộn trong thành phần này sẽ giúp cho thấy rõ vị trí của các vùng cuộn, hướng mà các vùng đó hỗ trợ cũng như cách các vùng đó tương tác với nhau. Hãy cân nhắc xem mỗi khung cửa sổ cuộn này cũng là phần tử mẹ linh hoạt hoặc dạng lưới đối với một bố cục.

Công cụ cho nhà phát triển có thể giúp chúng tôi trực quan hoá việc này:

các khu vực cuộn có lớp phủ công cụ dạng lưới và linh hoạt, phác thảo không gian mà chúng chiếm trong thành phần và hướng mà chúng tràn vào
Chromium Devtools, hiển thị bố cục phần tử điều hướng flexbox với đầy đủ các phần tử liên kết, bố cục mục lưới gồm nhiều phần tử bài viết và các phần tử bài viết gồm toàn các đoạn văn và một phần tử tiêu đề.

Các bố cục cuộn đã hoàn tất: 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à cảm giác thích thú.

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

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

Hoạt ảnh

Mục tiêu của ảnh động hoạt động ở đây là liên kết rõ ràng các hoạt động tương tác với phản hồi trên 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à). Tôi sẽ thêm chuyển động có mục đích và có điều kiện. Người dùng hiện có thể chỉ định các lựa chọn ưu tiên về chuyển động trong hệ điều hành và tôi hoàn toàn 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 dấu gạch dưới thẻ với vị trí cuộn của bài viết. Việc chụp nhanh không chỉ là căn chỉnh đẹp, mà còn cố định phần đầu và phần cuối của ảnh động. Thao tác này sẽ giúp <nav> (hoạt động như một bản đồ thu nhỏ) kết nối với nội dung. Chúng tôi sẽ kiểm tra lựa chọn chuyển động ưu tiên của người dùng từ cả CSS và JS. Có một số nơi tuyệt vời để bạn chăm sóc bản thân!

Hành vi cuộn

Bạn có thể cải thiện hành vi chuyển động của cả :targetelement.scrollIntoView(). Theo mặc định, nội dung mô tả sẽ là tức thì. Trình duyệt chỉ đặt vị trí cuộn. Chà, 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ì đang giới thiệu chuyển động và chuyển động mà người dùng không kiểm soát (như cuộn), nên chúng tôi chỉ áp dụng kiểu này nếu người dùng không ưu tiên việc giảm chuyển động trong hệ điều hành. Bằng cách này, chúng tôi chỉ giới thiệu chuyển động cuộn với những người thấy phù hợp với nó.

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 kiểu border-bottom chuyển động cho những người dùng thích giảm chuyển động và sử dụng hiệu ứng chuyển động trượt liên kết + hiệu ứng chuyển động mờ dần màu đối với những người dùng vẫn ổn với chuyển động.

Trong Chromium Devtools, tôi có thể bật/tắt lựa chọn ưu tiên này và minh hoạ 2 kiểu chuyển đổi khác nhau. Tôi rất vui khi xây dựng trò chơi 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ữa. Sau đó, tôi thay thế bằng kiểu border-block-endtransition. Ngoài ra, hãy lưu ý trong các thao tác tương tác với thẻ, mục điều hướng đang hoạt động không chỉ có dấu gạch dưới thương hiệu được làm nổi bật, mà màu văn bản cũng tối 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 dưới ánh sáng sáng.

Chỉ một vài dòng CSS bổ sung sẽ khiến ai đó cảm thấy được chú ý (theo nghĩa là chúng tôi đang tôn trọng lựa chọn chuyển động của họ một cách thấu đáo). Tôi rất thích.

@scroll-timeline

Trong phần trên, tôi đã cho bạn thấy cách xử lý các kiểu chuyển động mờ dần và trong phần này, tôi sẽ cho bạn biết cách tôi liên kết chỉ báo và một vùng cuộn với nhau. Đây là một số nội dung thử nghiệm thú vị trong tương lai. Tôi hy vọng bạn cũng quan tâm 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 chỉ số này là false, nghĩa là người dùng thích 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 nội dung này, trình duyệt hỗ trợ @scroll-timeline là không có. Đây là thông số nháp chỉ áp dụng cho các phương thức triển khai thử nghiệm. Tuy nhiên, mã này có một polyfill là 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 dòng thời gian 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 có 1 nội dung tuân theo vị trí cuộn của một nội dung khác và bằng cách tạo ScrollTimeline, tôi xác định trình điều khiển trình điều khiển đường liên kết cuộn, scrollSource. Thông thường, ảnh động trên web sẽ chạy dựa trên kim đánh dấu nhịp độ khung thời gian chung, 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 tìm hiểu về khung hình chính của ảnh động, tôi nghĩ điều quan trọng là phải chỉ ra người theo dõi thao tác cuộn, tabindicator, sẽ là ảnh động dựa trên dòng thời gian tuỳ chỉnh, tức là cuộn trong phần của chúng ta. Thao tác này sẽ hoàn tất quá trình liên kết, nhưng 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òn gọi là khung hình chính.

Khung hình chính linh hoạt

Có một cách CSS thuần tuý khai báo thực sự mạnh mẽ để tạo ảnh động bằng @scroll-timeline, nhưng ảnh động tôi chọn làm quá độ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ự động tạo số khung hình chính dựa trên độ dài thành phần con.

Tuy nhiên, JavaScript biết cách lấy thông tin đó, vì vậy, chúng tôi 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. Cách thực hiện tương tự đối với chiều rộng, mỗi chiều rộng được hỏi về chiều rộng động và sau đó được dùng làm giá trị khung hình chính.

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

Khung hình chính của 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 chính có 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, giờ đây, chỉ báo thẻ sẽ tạo ảnh động trên 4 khung hình chính tuỳ thuộc vào vị trí chụp nhanh của thanh cuộn mục. 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 của chúng ta và thực sự tăng thêm cảm giác được đồng bộ hoá của ảnh động.

thẻ 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 số tương phản vượt qua cho cả hai

Người dùng điều khiển ảnh động khi 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ề sự 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 đã bỏ chọn có vẻ như bị đẩy lùi nhiều 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 cấp độ tiếp theo là chuyển đổi màu đó khi cuộn, được đồng bộ hoá với chỉ báo gạch chân.

Đâ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ẻ đều cần ảnh động màu mới này, theo dõi cùng một dòng thời gian cuộn như chỉ báo gạch chân. Tôi sử dụng tiến trình tương tự như trước đây: vì vai trò của nó là phát ra kim đánh dấu nhịp độ khung hình khi cuộn, nên chúng ta có thể sử dụng kim đánh dấu nhịp độ khung hình trong bất kỳ loại ảnh động nào mình muốn. Như đã làm trước đây, tôi tạo 4 khung hình chính trong vòng lặp và trả về màu.

[...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) sẽ làm nổi bật đường liên kết và đây là màu văn bản tiêu chuẩn. Vòng lặp lồng nhau ở đó 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à khung hình chính cá nhân 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 vòng lặp bên trong hay không và dùng phần tử đó để biết thời điểm phần tử này được chọn.

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

Nhiều cải tiến JavaScript khác

Xin nhắc lại rằng điểm cốt lõi mà tôi trình bày ở đây sẽ hoạt động mà không cần đến JavaScript. Do đó, hãy xem cách chúng ta có thể cải thiện đoạn mã này khi JS có sẵn.

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ĩ mục đích của liên kết sâu được đề cập ở đây với các thẻ trong đó bạn có thể chia sẻ URL trực tiếp đến nội dung của thẻ. Trong trang, trình duyệt sẽ chuyển đến mã nhận dạng được so khớp trong hàm băm URL. Tôi nhận thấy trình xử lý onload này có hiệu quả 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

Không phải lúc nào người dùng của chúng tôi cũng nhấp hoặc sử dụng bàn phím, đôi khi họ chỉ cần cuộn thoải mái là có thể. Khi thanh cuộn phần ngừng cuộn, tại bất cứ vị trí nào nó đến, bạn cần phải so khớp trong thanh điều hướng trên cùng.

Đây là cách tôi đợi quá trình kết thúc cuộn: js tabsection.addEventListener('scroll', () => { clearTimeout(tabsection.scrollEndTimer); tabsection.scrollEndTimer = setTimeout(determineActiveTabSection, 100); });

Bất cứ khi nào người dùng cuộn các mục, hãy xoá thời gian chờ của mục (nếu có) rồi bắt đầu một mục 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 kích hoạt, hàm gọi sẽ tìm cách tìm ra vị trí người dùng đã dừng lại.

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

  matchingNavItem && setActiveTab(matchingNavItem);
};

Giả sử thanh cuộn được đóng lại, việc chia vị trí cuộn hiện tại từ chiều rộng của vùng cuộn sẽ cho kết quả là 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 mục điều hướng từ bộ nhớ đệm thông qua chỉ mục đã tính toán này và nếu tìm thấy mục nào đó, tôi sẽ gửi kết quả trùng khớp để đặt thành hoạt động.

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

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

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

.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 chụp nhanh cuộn ngang, chúng tôi đã lồng một truy vấn nội dung nghe nhì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 khung hiển thị và CSS có thể quản lý trải nghiệm người dùng theo cách khai báo. Đôi khi họ tạo ra những trận đấu nho nhỏ thật thú vị.

Kết luận

Giờ bạn đã biết tôi làm việc đó như thế nào, bạn sẽ làm thế nào?! Đ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 với các ô trong khung yêu thích của họ? 🙂

Hãy đa dạng hoá phương pháp tiếp cận của chúng ta và tìm hiểu tất cả các cách xây dựng trên web. Tạo một video Glitch, tweet tôi phiên bản của bạn, rồi 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