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. Xem bản minh hoạ.

Bản minh hoạ

Nếu bạn thích xem video hơn, sau đây là phiên bản 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. Ban đầu, chúng tôi có các thẻ dành cho máy tính dựa trên phần tử <frame>. Hiện tại, chúng tôi có các thành phần nhỏ dành cho thiết bị di động giúp 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ới 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 gồm các phong cách thiết kế web thành phần thẻ trong 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 tức thì trong trang
  • Tính năng web @scroll-timeline dưới dạng bản nháp để tự động gạch chân và thay đổi màu sắc cho thẻ đã chọn

Phần tử 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ử trùng khớ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 danh sách các đường liên kết (phù hợp với <nav>) và danh sách các phần tử <article> (rất phù hợp với <section>). 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 phần 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 làm 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 nêu trong nhãn đá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 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 trong bài viết (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 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à 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. 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 (xanh lam).

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 nhau: Tôi sử dụng linh hoạt để 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à phần), 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 hộp cần có chức năng tràn trên trục x, vùng chứa cuộn để bẫy cuộn quá mức, thanh cuộn ẩn cho thiết bị cảm ứng và cuối cùng là cuộn-chụp nhanh để khoá khu vực 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í trong 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 liên kết phải bám sát vùng chứa của nút di chuyển. 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 thẻ <section>

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% sẽ 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 riêng, phần tử này sẽ tạo một loạt cột có kích thước 100% bằng chiều rộng 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 khít với một hộp, vừa tạo 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 cuộn được 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. Ở đây, trình duyệt thực sự xử lý một số thao tác chạm, chuột và bàn phím phức tạp.

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ử bài viết và các phần tử con của nó có lớp phủ màu hồng (hotpink) trên đó, nêu rõ khoảng trống mà chúng chiếm trong thành phần và hướng chú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 nó 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

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ĩ 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 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 tuyệt vời

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 đầy đủ các phần tử neo, bố cục mục lưới có đầy đủ các phần tử bài viết, các phần tử bài viết có đầy đủ các đoạn vă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 dùng thử ở Chế độ thiết bị của Công cụ cho nhà phát triển Chromium bằng cách chọn bất kỳ chế độ nào khác ngoài Thích ứ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ó mặt từ khi Chromium cập nhật phương thức triển khai để phù hợp với thông số kỹ thuật. Sau đây là bài đăng trên blog về việc này.

Hoạt ảnh

Mục tiêu của ả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 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à như vậy). Tôi sẽ thêm chuyển động có chủ đí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. Việc chụp nhanh không chỉ căn chỉnh đẹp mắt mà còn neo điểm bắt đầu và kết thúc ả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 ý sử dụng 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 chéo 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 đã có rất nhiều niềm vui khi xây dựng điều 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 các hoạt động 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 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 đã cho bạn biết cách xử lý các kiểu mờ dần chuyển động 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 không hỗ trợ @scroll-timeline. Đâ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, nó có một polyfill mà tôi sử dụng trong bản minh hoạ này.

ScrollTimeline

Mặc dù 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 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 một 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ề các khung hình chính của ảnh động, tôi nghĩ quan trọng là phải chỉ ra yếu tố tiếp theo 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, thao tác 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 cũng như không có cách nào để tự động tạo một số khung hình chính dựa trên độ dài của khung hình 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 thành phần 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 xuất hiện cùng với lớp phủ VisBug cho thấy các điểm tương phản vượt qua 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ưng tôi rất tự hào về sự chuyển tiếp của 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.

Đâ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 chọn.

Tôi thấy rất vui khi viết được 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 giống như một thuật ngữ dành cho thiết bị di động, nhưng tôi nghĩ mục đích của đường liên kết sâu được đáp ứng ở đây với các thẻ trong đó bạn có thể chia sẻ URL trực tiếp đến nội dung của một 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 của phần ngừng cuộn, bạn phải so khớp với thanh điều hướng trên cùng ở bất cứ nơi nào nó truy cập.

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 người dùng cuộn các phần đó, hãy xoá thời gian chờ của phần (nếu có) rồi bắt đầu 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ử thao tác cuộn bị chụp nhanh, việc chia vị trí cuộn hiện tại với chiều rộng của vùng cuộn sẽ cho ra 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 đã tính này và nếu nó tìm thấy nội dung 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();
};

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 di chuyển nhanh theo chiều ngang, chúng tôi đã lồng vào một truy vấn đa phương tiện áp dụng chức năng cuộn smooth nếu người dùng chịu được 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ờ bạn đã biết cách tôi thực hiện điều đó, 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 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