Tạo thành phần Cài đặt

Thông tin tổng quan cơ bản về cách tạo thành phần cài đặt của các thanh trượt và hộp đánh dấu.

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 Cài đặt 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 nhiều trình duyệt. Dùng thử bản minh hoạ.

Bản minh hoạ

Nếu bạn thích xem video hoặc muốn xem trước giao diện người dùng/trải nghiệm người dùng mà chúng tôi đang xây dựng, hãy xem hướng dẫn ngắn gọn này trên YouTube:

Tổng quan

Tôi đã chia các khía cạnh của thành phần này thành các phần sau:

  1. Bố cục
  2. Màu
  3. Dữ liệu đầu vào của dải ô tuỳ chỉnh
  4. Đầu vào hộp đánh dấu tuỳ chỉnh
  5. Những điểm cần cân nhắc về khả năng hỗ trợ tiếp cận
  6. JavaScript

Bố cục

Đây là bản minh hoạ đầu tiên của Thử thách về giao diện người dùng hoàn toàn bằng Lưới CSS! Dưới đây là từng lưới được làm nổi bật bằng Công cụ của Chrome cho nhà phát triển đối với lưới:

Đường viền đầy màu sắc và lớp phủ khoảng cách giữa các hộp giúp hiển thị tất cả các hộp tạo nên bố cục cài đặt

Chỉ dành cho khoảng trống

Bố cục phổ biến nhất:

foo {
  display: grid;
  gap: var(--something);
}

Tôi gọi bố cục này là "chỉ dành cho khoảng trống" vì bố cục này chỉ sử dụng lưới để thêm khoảng trống giữa các khối.

5 bố cục sử dụng chiến lược này, sau đây là tất cả các bố cục đó:

Bố cục lưới dọc được làm nổi bật bằng đường viền và khoảng trống được điền vào

Phần tử fieldset chứa từng nhóm đầu vào (.fieldset-item), đang sử dụng gap: 1px để tạo đường viền siêu mảnh giữa các phần tử. Không có giải pháp nào phức tạp về đường viền!

Khoảng trống đã được lấp đầy
.grid {
  display: grid;
  gap: 1px;
  background: var(--bg-surface-1);

  & > .fieldset-item {
    background: var(--bg-surface-2);
  }
}
Thủ thuật đường viền
.grid {
  display: grid;

  & > .fieldset-item {
    background: var(--bg-surface-2);

    &:not(:last-child) {
      border-bottom: 1px solid var(--bg-surface-1);
    }
  }
}

Tự xuống dòng theo lưới tự nhiên

Bố cục phức tạp nhất là bố cục vĩ mô, hệ thống bố cục logic giữa <main><form>.

Căn giữa nội dung bao bọc

Cả flexbox và lưới đều cung cấp khả năng align-items hoặc align-content, và khi xử lý các phần tử bao bọc, các chế độ căn chỉnh bố cục content sẽ phân phối không gian giữa các phần tử con dưới dạng một nhóm.

main {
  display: grid;
  gap: var(--space-xl);
  place-content: center;
}

Phần tử chính đang sử dụng cú pháp rút gọn căn chỉnh place-content: center để các phần tử con được căn giữa theo chiều dọc và chiều ngang trong cả bố cục một và hai cột.

Trong video trên, hãy xem cách "nội dung" vẫn nằm ở giữa, ngay cả khi xảy ra hiện tượng xuống dòng.

Lặp lại minmax tự động điều chỉnh

<form> sử dụng bố cục lưới thích ứng cho từng phần. Bố cục này chuyển từ một cột sang hai cột dựa trên không gian có sẵn.

form {
  display: grid;
  gap: var(--space-xl) var(--space-xxl);
  grid-template-columns: repeat(auto-fit, minmax(min(10ch, 100%), 35ch));
  align-items: flex-start;
  max-width: 89vw;
}

Lưới này có giá trị khác cho row-gap (--space-xl) so với column-gap (--space-xxl) để tạo nét tuỳ chỉnh cho bố cục thích ứng. Khi các cột xếp chồng lên nhau, chúng ta muốn có một khoảng trống lớn, nhưng không lớn bằng khi ở trên màn hình rộng.

Thuộc tính grid-template-columns sử dụng 3 hàm CSS: repeat(), minmax()min(). Una Kravets có một bài đăng tuyệt vời trên blog về bố cục liên quan đến vấn đề này, trong đó gọi đây là RAM.

Có 3 điểm đặc biệt trong bố cục của chúng tôi, nếu bạn so sánh với bố cục của Una:

  • Chúng ta truyền một hàm min() bổ sung.
  • Chúng tôi chỉ định align-items: flex-start.
  • Có một kiểu max-width: 89vw.

Hàm min() bổ sung được Evan Minto mô tả rõ ràng trên blog của họ trong bài đăng Lưới CSS phản hồi tự nhiên với minmax() và min(). Bạn nên đọc bài đăng đó. Việc điều chỉnh căn chỉnh flex-start là để loại bỏ hiệu ứng kéo giãn mặc định, nhờ đó các thành phần con của bố cục này không cần có chiều cao bằng nhau, mà có thể có chiều cao tự nhiên, nội tại. Video trên YouTube này có phần giải thích ngắn gọn về việc bổ sung chế độ căn chỉnh này.

max-width: 89vw đáng được phân tích ngắn gọn trong bài đăng này. Hãy để tôi cho bạn xem bố cục khi áp dụng và không áp dụng kiểu:

Chuyện gì đang xảy ra? Khi max-width được chỉ định, tức là bạn đang cung cấp bối cảnh, kích thước rõ ràng hoặc kích thước xác định để thuật toán bố cục auto-fit biết có thể vừa bao nhiêu lần lặp lại vào không gian. Mặc dù có vẻ như không gian này là "chiều rộng đầy đủ", nhưng theo quy cách lưới CSS, bạn phải cung cấp kích thước hoặc kích thước tối đa xác định. Tôi đã cung cấp một kích thước tối đa.

Vậy tại sao nên chọn 89vw? Vì bố cục của tôi "đã hoạt động". Tôi và một vài người khác trong nhóm Chrome đang điều tra lý do khiến một giá trị hợp lý hơn, chẳng hạn như 100vw, không đủ và liệu đây có thực sự là một lỗi hay không.

Giãn cách

Phần lớn sự hài hoà của bố cục này đến từ một bảng khoảng cách giới hạn, chính xác là 7.

:root {
  --space-xxs: .25rem;
  --space-xs:  .5rem;
  --space-sm:  1rem;
  --space-md:  1.5rem;
  --space-lg:  2rem;
  --space-xl:  3rem;
  --space-xxl: 6rem;
}

Việc sử dụng các luồng này diễn ra rất suôn sẻ với lưới, CSS @nestcú pháp cấp 5 của @media. Sau đây là ví dụ về bộ kiểu bố cục <main> đầy đủ.

main {
  display: grid;
  gap: var(--space-xl);
  place-content: center;
  padding: var(--space-sm);

  @media (width >= 540px) {
    & {
      padding: var(--space-lg);
    }
  }

  @media (width >= 800px) {
    & {
      padding: var(--space-xl);
    }
  }
}

Một lưới có nội dung ở giữa, được đệm vừa phải theo mặc định (như trên thiết bị di động). Nhưng khi có thêm không gian khung nhìn, nó sẽ trải rộng ra bằng cách tăng khoảng đệm. CSS năm 2021 có vẻ khá ổn!

Bạn còn nhớ bố cục trước đó, "chỉ để tạo khoảng trống" không? Sau đây là phiên bản đầy đủ hơn về cách các nút này xuất hiện trong thành phần này:

header {
  display: grid;
  gap: var(--space-xxs);
}

section {
  display: grid;
  gap: var(--space-md);
}

Màu

Việc sử dụng màu sắc có kiểm soát đã giúp thiết kế này nổi bật vì vừa biểu cảm vừa tối giản. Tôi làm như sau:

:root {
  --surface1: lch(10 0 0);
  --surface2: lch(15 0 0);
  --surface3: lch(20 0 0);
  --surface4: lch(25 0 0);

  --text1: lch(95 0 0);
  --text2: lch(75 0 0);
}

Tôi đặt tên cho màu bề mặt và màu văn bản bằng các con số thay vì các tên như surface-darksurface-darker vì trong một truy vấn nội dung nghe nhìn, tôi sẽ lật chúng và ánh sáng và bóng tối sẽ không có ý nghĩa.

Tôi lật chúng trong một truy vấn phương tiện ưu tiên như sau:

:root {
  ...

  @media (prefers-color-scheme: light) {
    & {
      --surface1: lch(90 0 0);
      --surface2: lch(100 0 0);
      --surface3: lch(98 0 0);
      --surface4: lch(85 0 0);

      --text1: lch(20 0 0);
      --text2: lch(40 0 0);
    }
  }
}

Điều quan trọng là bạn phải nắm bắt được tổng thể và chiến lược trước khi chúng ta tìm hiểu chi tiết về cú pháp màu. Nhưng vì tôi đã đi hơi xa, nên hãy để tôi quay lại một chút.

LCH?

Không cần đi sâu vào lý thuyết về màu sắc, LCH là một cú pháp hướng đến con người, phù hợp với cách chúng ta cảm nhận màu sắc, chứ không phải cách chúng ta đo lường màu sắc bằng toán học (như 255). Điều này mang lại cho nó một lợi thế riêng biệt vì con người có thể viết dễ dàng hơn và những người khác sẽ điều chỉnh theo những thay đổi này.

Ảnh chụp màn hình trang web pod.link/csspodcast, với tập Color 2: Perception (Màu sắc 2: Cảm nhận) được mở lên
Tìm hiểu về màu sắc cảm quan (và nhiều điều khác!) trên CSS Podcast

Trong bản minh hoạ hôm nay, hãy tập trung vào cú pháp và các giá trị mà tôi đang chuyển đổi để tạo chế độ sáng và tối. Hãy xem xét 1 màu nền và 1 màu văn bản:

:root {
  --surface1: lch(10 0 0);
  --text1:    lch(95 0 0);

  @media (prefers-color-scheme: light) {
    & {
      --surface1: lch(90 0 0);
      --text1:    lch(40 0 0);
    }
  }
}

--surface1: lch(10 0 0) chuyển thành độ sáng 10%, 0 độ bão hoà và 0 sắc độ: một màu xám rất tối không có màu. Sau đó, trong truy vấn phương tiện cho chế độ sáng, độ sáng sẽ được chuyển thành 90% bằng --surface1: lch(90 0 0);. Và đó là ý chính của chiến lược này. Bắt đầu bằng cách chỉ thay đổi độ sáng giữa 2 giao diện, duy trì tỷ lệ tương phản mà thiết kế yêu cầu hoặc tỷ lệ có thể duy trì khả năng hỗ trợ tiếp cận.

Điểm cộng của lch() ở đây là độ sáng hướng đến con người và chúng ta có thể cảm thấy hài lòng về việc thay đổi % đối với độ sáng, vì độ sáng sẽ khác biệt một cách nhất quán và theo cảm nhận là %. Ví dụ: hsl() không đáng tin cậy.

Bạn có thể tìm hiểu thêm về không gian màu và lch() nếu quan tâm. Tính năng này sắp ra mắt!

CSS hiện tại không thể truy cập vào các màu này. Tôi xin nhắc lại: Chúng ta không thể nhìn thấy 1/3 số màu trên hầu hết các màn hình hiện đại. Đây không chỉ là những màu sắc thông thường mà còn là những màu sắc rực rỡ nhất mà màn hình có thể hiển thị. Các trang web của chúng tôi bị nhạt màu vì phần cứng màn hình phát triển nhanh hơn so với các thông số kỹ thuật CSS và việc triển khai trình duyệt.

Lea Verou

Các chế độ kiểm soát biểu mẫu thích ứng có bảng phối màu

Nhiều trình duyệt có các chế độ kiểm soát giao diện tối, hiện tại là Safari và Chromium, nhưng bạn phải chỉ định trong CSS hoặc HTML rằng thiết kế của bạn sử dụng các chế độ này.

Ở trên là ví dụ minh hoạ hiệu ứng của thuộc tính trong bảng Styles (Kiểu) của Công cụ cho nhà phát triển. Bản minh hoạ sử dụng thẻ HTML. Theo tôi, đây thường là một vị trí phù hợp hơn:

<meta name="color-scheme" content="dark light">

Tìm hiểu tất cả thông tin về vấn đề này trong color-scheme bài viết này của Thomas Steiner. Bạn sẽ nhận được nhiều lợi ích hơn là chỉ có các hộp đánh dấu tối!

CSS accent-color

Gần đây, đã có hoạt động xung quanh accent-color trên các phần tử biểu mẫu, đây là một kiểu CSS duy nhất có thể thay đổi màu sắc được dùng trong phần tử đầu vào của trình duyệt. Đọc thêm về vấn đề này tại đây trên GitHub. Tôi đã thêm nó vào các kiểu cho thành phần này. Khi các trình duyệt hỗ trợ, hộp đánh dấu của tôi sẽ phù hợp hơn với các màu hồng và tím nổi bật.

input[type="checkbox"] {
  accent-color: var(--brand);
}

Ảnh chụp màn hình Chromium trên Linux có các hộp đánh dấu màu hồng

Ảnh nổi bật màu có hiệu ứng chuyển màu cố định và tiêu điểm bên trong

Màu sắc nổi bật nhất khi được sử dụng một cách tiết kiệm và một trong những cách tôi muốn đạt được điều đó là thông qua các hoạt động tương tác đầy màu sắc trên giao diện người dùng.

Có nhiều lớp phản hồi và tương tác trên giao diện người dùng trong video trên, giúp tạo cá tính cho hoạt động tương tác bằng cách:

  • Nêu bật bối cảnh.
  • Cung cấp ý kiến phản hồi về giao diện người dùng về "mức độ đầy" của giá trị trong phạm vi.
  • Cung cấp ý kiến phản hồi về giao diện người dùng rằng một trường đang chấp nhận dữ liệu đầu vào.

Để cung cấp thông tin phản hồi khi người dùng tương tác với một phần tử, CSS đang sử dụng pseudo class :focus-within để thay đổi giao diện của nhiều phần tử. Hãy phân tích .fieldset-item, đây là một phần tử rất thú vị:

.fieldset-item {
  ...

  &:focus-within {
    background: var(--surface2);

    & svg {
      fill: white;
    }

    & picture {
      clip-path: circle(50%);
      background: var(--brand-bg-gradient) fixed;
    }
  }
}

Khi một trong các thành phần con của phần tử này có focus-within:

  1. Nền .fieldset-item được chỉ định màu sắc vùng hiển thị có độ tương phản cao hơn.
  2. svg lồng nhau có màu trắng để tăng độ tương phản.
  3. <picture> clip-path được lồng ghép sẽ mở rộng thành một vòng tròn đầy và nền được tô bằng màu chuyển sắc cố định sáng.

Phạm vi tùy chỉnh

Với phần tử đầu vào HTML sau đây, tôi sẽ cho bạn thấy cách tôi tuỳ chỉnh giao diện của phần tử đó:

<input type="range">

Có 3 phần trong phần tử này mà chúng ta cần tuỳ chỉnh:

  1. Phần tử / vùng chứa phạm vi
  2. Theo dõi
  3. Ngón tay cái

Kiểu phần tử phạm vi

input[type="range"] {
  /* style setting variables */
  --track-height: .5ex;
  --track-fill: 0%;
  --thumb-size: 3ex;
  --thumb-offset: -1.25ex;
  --thumb-highlight-size: 0px;

  appearance: none;         /* clear styles, make way for mine */
  display: block;
  inline-size: 100%;        /* fill container */
  margin: 1ex 0;            /* ensure thumb isn't colliding with sibling content */
  background: transparent;  /* bg is in the track */
  outline-offset: 5px;      /* focus styles have space */
}

Một vài dòng CSS đầu tiên là các phần tuỳ chỉnh của kiểu. Tôi hy vọng việc gắn nhãn rõ ràng cho các phần này sẽ giúp ích cho bạn. Phần còn lại của các kiểu chủ yếu là kiểu đặt lại, nhằm cung cấp một nền tảng nhất quán để xây dựng các phần phức tạp của thành phần.

Kiểu phụ đề

input[type="range"]::-webkit-slider-runnable-track {
  appearance: none; /* clear styles, make way for mine */
  block-size: var(--track-height);
  border-radius: 5ex;
  background:
    /* hard stop gradient:
        - half transparent (where colorful fill we be)
        - half dark track fill
        - 1st background image is on top
    */
    linear-gradient(
      to right,
      transparent var(--track-fill),
      var(--surface1) 0%
    ),
    /* colorful fill effect, behind track surface fill */
    var(--brand-bg-gradient) fixed;
}

Bí quyết của hiệu ứng này là "làm lộ" màu nền rực rỡ. Việc này được thực hiện bằng cách dùng hiệu ứng chuyển màu có đường phân cách rõ ràng ở trên cùng. Độ dốc trong suốt cho đến tỷ lệ phần trăm lấp đầy và sau đó sử dụng màu nền của phần chưa lấp đầy. Phía sau vùng hiển thị chưa được điền đó là một màu có chiều rộng đầy đủ, đang chờ độ trong suốt để hiển thị.

Kiểu tô của đường theo dõi

Thiết kế của tôi cần có JavaScript để duy trì kiểu tô. Chỉ có các chiến lược CSS nhưng chúng yêu cầu phần tử ngón tay cái có cùng chiều cao với đường dẫn và tôi không thể tìm thấy sự hài hoà trong những giới hạn đó.

/* grab sliders on page */
const sliders = document.querySelectorAll('input[type="range"]')

/* take a slider element, return a percentage string for use in CSS */
const rangeToPercent = slider => {
  const max = slider.getAttribute('max') || 10;
  const percent = slider.value / max * 100;

  return `${parseInt(percent)}%`;
};

/* on page load, set the fill amount */
sliders.forEach(slider => {
  slider.style.setProperty('--track-fill', rangeToPercent(slider));

  /* when a slider changes, update the fill prop */
  slider.addEventListener('input', e => {
    e.target.style.setProperty('--track-fill', rangeToPercent(e.target));
  })
})

Tôi nghĩ điều này sẽ giúp cải thiện đáng kể về mặt hình ảnh. Thanh trượt hoạt động tốt mà không cần JavaScript, thuộc tính --track-fill không bắt buộc, thanh trượt sẽ không có kiểu tô nếu không có thuộc tính này. Nếu có JavaScript, hãy điền thuộc tính tuỳ chỉnh đồng thời theo dõi mọi thay đổi của người dùng, đồng bộ hoá thuộc tính tuỳ chỉnh với giá trị.

Đây là một bài đăng hay trên CSS-Tricks của Ana Tudor, minh hoạ giải pháp chỉ dùng CSS để theo dõi mức độ lấp đầy. Tôi cũng thấy phần tử range này rất truyền cảm hứng.

Kiểu hình thu nhỏ

input[type="range"]::-webkit-slider-thumb {
  appearance: none; /* clear styles, make way for mine */
  cursor: ew-resize; /* cursor style to support drag direction */
  border: 3px solid var(--surface3);
  block-size: var(--thumb-size);
  inline-size: var(--thumb-size);
  margin-top: var(--thumb-offset);
  border-radius: 50%;
  background: var(--brand-bg-gradient) fixed;
}

Đa số các kiểu này là để tạo một vòng tròn đẹp. Một lần nữa, bạn sẽ thấy hiệu ứng chuyển màu nền cố định giúp hợp nhất các màu động của hình thu nhỏ, các bản nhạc và các phần tử SVG được liên kết. Tôi tách các kiểu cho hoạt động tương tác để giúp tách biệt kỹ thuật box-shadow đang được dùng cho hiệu ứng làm nổi bật khi di chuột:

@custom-media --motionOK (prefers-reduced-motion: no-preference);

::-webkit-slider-thumb {
  

  /* shadow spread is initally 0 */
  box-shadow: 0 0 0 var(--thumb-highlight-size) var(--thumb-highlight-color);

  /* if motion is OK, transition the box-shadow change */
  @media (--motionOK) {
    & {
      transition: box-shadow .1s ease;
    }
  }

  /* on hover/active state of parent, increase size prop */
  @nest input[type="range"]:is(:hover,:active) & {
    --thumb-highlight-size: 10px;
  }
}

Mục tiêu là tạo ra một điểm nhấn trực quan, dễ quản lý và có hiệu ứng động cho ý kiến phản hồi của người dùng. Bằng cách sử dụng hiệu ứng đổ bóng, tôi có thể tránh kích hoạt bố cục. Tôi làm việc này bằng cách tạo một bóng không bị mờ và khớp với hình tròn của phần tử ngón tay cái. Sau đó, tôi thay đổi và chuyển đổi kích thước lan rộng của nó khi di chuột.

Nếu chỉ có hiệu ứng làm nổi bật trên hộp đánh dấu thì thật dễ dàng…

Bộ chọn trên nhiều trình duyệt

Tôi nhận thấy mình cần các bộ chọn -webkit--moz- này để đạt được tính nhất quán trên nhiều trình duyệt:

input[type="range"] {
  &::-webkit-slider-runnable-track {}
  &::-moz-range-track {}
  &::-webkit-slider-thumb {}
  &::-moz-range-thumb {}
}

Hộp đánh dấu tuỳ chỉnh

Với phần tử đầu vào HTML sau đây, tôi sẽ cho bạn thấy cách tôi tuỳ chỉnh giao diện của phần tử đó:

<input type="checkbox">

Có 3 phần trong phần tử này mà chúng ta cần tuỳ chỉnh:

  1. Phần tử hộp đánh dấu
  2. Nhãn được liên kết
  3. Hiệu ứng làm nổi bật

Phần tử hộp đánh dấu

input[type="checkbox"] {
  inline-size: var(--space-sm);   /* increase width */
  block-size: var(--space-sm);    /* increase height */
  outline-offset: 5px;            /* focus style enhancement */
  accent-color: var(--brand);     /* tint the input */
  position: relative;             /* prepare for an absolute pseudo element */
  transform-style: preserve-3d;   /* create a 3d z-space stacking context */
  margin: 0;
  cursor: pointer;
}

Các kiểu transform-styleposition chuẩn bị cho phần tử giả mà chúng ta sẽ giới thiệu sau để tạo kiểu cho phần đánh dấu. Nếu không, đó thường là những ý kiến nhỏ về phong cách của tôi. Tôi muốn con trỏ là con trỏ, tôi muốn độ lệch đường viền, hộp đánh dấu mặc định quá nhỏ và nếu accent-color được hỗ trợ, hãy đưa những hộp đánh dấu này vào bảng phối màu thương hiệu.

Nhãn hộp đánh dấu

Bạn cần cung cấp nhãn cho hộp đánh dấu vì 2 lý do. Đầu tiên là để biểu thị giá trị của hộp đánh dấu được dùng cho mục đích gì, để trả lời câu hỏi "bật hoặc tắt cho mục đích gì?" Thứ hai là về trải nghiệm người dùng, người dùng web đã quen với việc tương tác với hộp đánh dấu thông qua các nhãn được liên kết.

input
<input
  type="checkbox"
  id="text-notifications"
  name="text-notifications"
>
nhãn
<label for="text-notifications">
  <h3>Text Messages</h3>
  <small>Get notified about all text messages sent to your device</small>
</label>

Trên nhãn của bạn, hãy đặt một thuộc tính for trỏ đến một hộp đánh dấu theo mã nhận dạng: <label for="text-notifications">. Trên hộp đánh dấu, hãy tăng gấp đôi cả tên và mã nhận dạng để đảm bảo hộp này được tìm thấy bằng nhiều công cụ và công nghệ, chẳng hạn như chuột hoặc trình đọc màn hình: <input type="checkbox" id="text-notifications" name="text-notifications">. :hover, :active và nhiều tính năng khác được cung cấp miễn phí khi bạn kết nối, giúp tăng số cách mà người dùng có thể tương tác với biểu mẫu của bạn.

Làm nổi bật hộp đánh dấu

Tôi muốn giữ cho các giao diện của mình nhất quán và phần tử thanh trượt có một điểm đánh dấu hình thu nhỏ đẹp mà tôi muốn sử dụng với hộp đánh dấu. Hình thu nhỏ có thể sử dụng box-shadow và thuộc tính spread của hình thu nhỏ để tăng và giảm kích thước bóng. Tuy nhiên, hiệu ứng đó không hoạt động ở đây vì hộp đánh dấu của chúng ta có dạng hình vuông và phải có dạng hình vuông.

Tôi có thể đạt được hiệu ứng thị giác tương tự bằng một phần tử giả và một lượng CSS phức tạp đáng tiếc:

@custom-media --motionOK (prefers-reduced-motion: no-preference);

input[type="checkbox"]::before {
  --thumb-scale: .01;                        /* initial scale of highlight */
  --thumb-highlight-size: var(--space-xl);

  content: "";
  inline-size: var(--thumb-highlight-size);
  block-size: var(--thumb-highlight-size);
  clip-path: circle(50%);                     /* circle shape */
  position: absolute;                         /* this is why position relative on parent */
  top: 50%;                                   /* pop and plop technique (https://web.dev/centering-in-css#5-pop-and-plop) */
  left: 50%;
  background: var(--thumb-highlight-color);
  transform-origin: center center;            /* goal is a centered scaling circle */
  transform:                                  /* order here matters!! */
    translateX(-50%)                          /* counter balances left: 50% */
    translateY(-50%)                          /* counter balances top: 50% */
    translateZ(-1px)                          /* PUTS IT BEHIND THE CHECKBOX */
    scale(var(--thumb-scale))                 /* value we toggle for animation */
  ;
  will-change: transform;

  @media (--motionOK) {                       /* transition only if motion is OK */
    & {
      transition: transform .2s ease;
    }
  }
}

/* on hover, set scale custom property to "in" state */
input[type="checkbox"]:hover::before {
  --thumb-scale: 1;
}

Việc tạo một phần tử giả hình tròn là một công việc đơn giản, nhưng việc đặt phần tử đó phía sau phần tử mà nó được đính kèm vào thì khó hơn. Đây là hình ảnh trước và sau khi tôi khắc phục:

Đây chắc chắn là một tương tác vi mô, nhưng đối với tôi, việc duy trì tính nhất quán về mặt hình ảnh là rất quan trọng. Kỹ thuật chia tỷ lệ ảnh động cũng giống như kỹ thuật mà chúng ta đã sử dụng ở những nơi khác. Chúng ta đặt một thuộc tính tuỳ chỉnh thành giá trị mới và cho phép CSS chuyển đổi giá trị đó dựa trên các lựa chọn ưu tiên về chuyển động. Tính năng chính ở đây là translateZ(-1px). Phần tử mẹ đã tạo một không gian 3D và phần tử con giả này đã khai thác không gian đó bằng cách đặt chính nó hơi lùi lại trong không gian z.

Hỗ trợ tiếp cận

Video trên YouTube này minh hoạ rất rõ ràng các thao tác tương tác bằng chuột, bàn phím và trình đọc màn hình cho thành phần cài đặt này. Tôi sẽ nêu ra một số chi tiết ở đây.

Lựa chọn về phần tử HTML

<form>
<header>
<fieldset>
<picture>
<label>
<input>

Mỗi mục này đều chứa các gợi ý và mẹo cho công cụ duyệt web của người dùng. Một số phần tử cung cấp gợi ý tương tác, một số kết nối khả năng tương tác và một số giúp định hình cây hỗ trợ tiếp cận mà trình đọc màn hình điều hướng.

Thuộc tính HTML

Chúng ta có thể ẩn những phần tử mà trình đọc màn hình không cần, trong trường hợp này là biểu tượng bên cạnh thanh trượt:

<picture aria-hidden="true">

Video trên minh hoạ quy trình sử dụng trình đọc màn hình trên hệ điều hành Mac. Lưu ý cách tiêu điểm đầu vào di chuyển thẳng từ thanh trượt này sang thanh trượt tiếp theo. Điều này là do chúng tôi đã ẩn biểu tượng có thể là một điểm dừng trên đường đến thanh trượt tiếp theo. Nếu không có thuộc tính này, người dùng sẽ cần dừng lại, lắng nghe và di chuyển qua bức ảnh mà họ có thể không nhìn thấy.

SVG là một loạt các phép toán, hãy thêm một phần tử <title> để có tiêu đề di chuột chuột miễn phí và một nhận xét dễ đọc về những gì phép toán đang tạo:

<svg viewBox="0 0 24 24">
  <title>A note icon</title>
  <path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/>
</svg>

Ngoài ra, chúng tôi đã sử dụng đủ thẻ HTML được đánh dấu rõ ràng để các bài kiểm thử biểu mẫu hoạt động thực sự hiệu quả trên chuột, bàn phím, tay điều khiển trò chơi điện tử và trình đọc màn hình.

JavaScript

Tôi đã đề cập đến cách màu tô của đường chạy được quản lý từ JavaScript, vì vậy, hãy xem JavaScript liên quan đến <form> ngay bây giờ:

const form = document.querySelector('form');

form.addEventListener('input', event => {
  const formData = Object.fromEntries(new FormData(form));
  console.table(formData);
})

Mỗi khi biểu mẫu được tương tác và thay đổi, bảng điều khiển sẽ ghi nhật ký biểu mẫu dưới dạng một đối tượng vào một bảng để dễ dàng xem xét trước khi gửi đến máy chủ.

Ảnh chụp màn hình kết quả console.table(), trong đó dữ liệu biểu mẫu xuất hiện trong một bảng

Kết luận

Giờ bạn đã biết cách tôi làm, vậy bạn sẽ làm như thế nào?! Điều này tạo nên một cấu trúc thành phần thú vị! Ai sẽ tạo phiên bản đầu tiên có các vị trí 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 bản minh hoạ, gửi đường liên kết cho tôi qua Twitter và tôi sẽ thêm bản minh hoạ đó 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

  • @tomayac với phong cách của họ liên quan đến vùng di chuột cho nhãn hộp đánh dấu! Phiên bản này không có khoảng trống khi di chuột giữa các phần tử: demosource.