Xây dựng thành phần chuyển đổi

Tổng quan cơ bản về cách tạo thành phần chuyển đổi thích ứng và dễ tiếp cận.

Trong bài đăng này, tôi muốn chia sẻ cách xây dựng các thành phần nút chuyển. 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

Nút chuyển hoạt động tương tự như hộp đánh dấu nhưng thể hiện rõ ràng trạng thái bật và tắt boolean.

Bản minh hoạ này sử dụng <input type="checkbox" role="switch"> cho phần lớn , có lợi thế là không cần phải có CSS hoặc JavaScript đầy đủ chức năng và dễ tiếp cận. Việc tải CSS giúp hỗ trợ từ phải sang trái ngôn ngữ, độ dọc, ảnh động, v.v. Việc tải JavaScript sẽ thực hiện việc chuyển đổi có thể kéo và hữu hình.

Thuộc tính tuỳ chỉnh

Các biến sau đây đại diện cho các phần của nút chuyển và . Là lớp cấp cao nhất, .gui-switch chứa các thuộc tính tuỳ chỉnh được sử dụng xuyên suốt các thành phần con và các điểm truy cập cho phần tuỳ chỉnh.

Theo dõi

Độ dài (--track-size), khoảng đệm và hai màu:

.gui-switch {
  --track-size: calc(var(--thumb-size) * 2);
  --track-padding: 2px;

  --track-inactive: hsl(80 0% 80%);
  --track-active: hsl(80 60% 45%);

  --track-color-inactive: var(--track-inactive);
  --track-color-active: var(--track-active);

  @media (prefers-color-scheme: dark) {
    --track-inactive: hsl(80 0% 35%);
    --track-active: hsl(80 60% 60%);
  }
}

Thumb

Kích thước, màu nền và màu sắc tương tác được làm nổi bật:

.gui-switch {
  --thumb-size: 2rem;
  --thumb: hsl(0 0% 100%);
  --thumb-highlight: hsl(0 0% 0% / 25%);

  --thumb-color: var(--thumb);
  --thumb-color-highlight: var(--thumb-highlight);

  @media (prefers-color-scheme: dark) {
    --thumb: hsl(0 0% 5%);
    --thumb-highlight: hsl(0 0% 100% / 25%);
  }
}

Giảm chuyển động

Để thêm bí danh rõ ràng và giảm tình trạng lặp lại, hãy giảm bớt lựa chọn ưu tiên về chuyển động cho người dùng truy vấn phương tiện truyền thông có thể được đặt vào thuộc tính tùy chỉnh bằng mã PostCSS trình bổ trợ dựa trên bản nháp này thông số kỹ thuật trong Truy vấn phương tiện 5:

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

Markup (note: đây là tên ứng dụng)

Tôi đã chọn gói phần tử <input type="checkbox" role="switch"> của mình bằng một <label>, nhóm mối quan hệ của chúng lại để tránh liên kết hộp đánh dấu và nhãn sự không rõ ràng, đồng thời giúp người dùng có thể tương tác với nhãn để bật/tắt phương thức nhập.

Đáp
nhãn và hộp đánh dấu tự nhiên, không định kiểu.

<label for="switch" class="gui-switch">
  Label text
  <input type="checkbox" role="switch" id="switch">
</label>

<input type="checkbox"> được tạo sẵn bằng APItiểu bang. Chiến lược phát hành đĩa đơn trình duyệt quản lý checked thuộc tính và thông tin đầu vào sự kiện chẳng hạn như oninputonchanged.

Bố cục

Flexbox, lướituỳ chỉnh thuộc tính rất quan trọng trong việc duy trì kiểu của thành phần này. Chúng tập trung các giá trị, đặt tên để tính toán hoặc diện tích mơ hồ và bật thuộc tính tuỳ chỉnh nhỏ API để tuỳ chỉnh thành phần dễ dàng.

.gui-switch

Bố cục cấp cao nhất cho nút chuyển là flexbox. Lớp .gui-switch chứa thuộc tính tuỳ chỉnh riêng tư và công khai mà con sử dụng để tính toán của bạn.

Công cụ cho nhà phát triển hộp linh hoạt phủ lên trên một nhãn và nút chuyển theo chiều ngang, cho thấy bố cục của chúng
phân phối không gian.

.gui-switch {
  display: flex;
  align-items: center;
  gap: 2ch;
  justify-content: space-between;
}

Mở rộng và sửa đổi bố cục hộp linh hoạt cũng giống như thay đổi bố cục hộp linh hoạt. Ví dụ: để đặt nhãn phía trên hoặc phía dưới nút chuyển hoặc để thay đổi flex-direction:

Công cụ cho nhà phát triển hộp linh hoạt phủ lên trên một nhãn dọc và công tắc.

<label for="light-switch" class="gui-switch" style="flex-direction: column">
  Default
  <input type="checkbox" role="switch" id="light-switch">
</label>

Theo dõi

Hộp đánh dấu đầu vào được tạo kiểu là kênh chuyển đổi bằng cách bỏ thông tin đầu vào appearance: checkbox và cung cấp kích thước riêng:

Công cụ cho nhà phát triển lưới phủ lên đường chuyển đổi, cho thấy đường lưới được đặt tên
các khu vực có tên &#39;theo dõi&#39;.

.gui-switch > input {
  appearance: none;

  inline-size: var(--track-size);
  block-size: var(--thumb-size);
  padding: var(--track-padding);

  flex-shrink: 0;
  display: grid;
  align-items: center;
  grid: [track] 1fr / [track] 1fr;
}

Bản nhạc cũng tạo ra một vùng theo dõi lưới ô riêng lẻ để thu thập xác nhận quyền sở hữu.

Thumb

Kiểu appearance: none cũng sẽ xoá dấu kiểm trực quan do trình duyệt. Thành phần này sử dụng một phần tử giả:checked pseudo-class trên đầu vào để thay thế chỉ báo trực quan này.

Thumb là một phần tử con giả (pseudo-phần tử con) được đính kèm vào input[type="checkbox"] và xếp chồng ở trên cùng thay vì bên dưới bản nhạc bằng cách xác nhận quyền sở hữu vùng lưới track:

Công cụ cho nhà phát triển cho thấy nút thumb của phần tử giả được đặt bên trong lưới CSS.

.gui-switch > input::before {
  content: "";
  grid-area: track;
  inline-size: var(--thumb-size);
  block-size: var(--thumb-size);
}

Kiểu

Thuộc tính tuỳ chỉnh cung cấp thành phần nút chuyển linh hoạt có khả năng điều chỉnh theo màu sắc giao thức, ngôn ngữ từ phải sang trái và tuỳ chọn chuyển động.

Ảnh so sánh song song giữa giao diện sáng và tối của nút chuyển và giao diện tối
các trạng thái.

Kiểu tương tác chạm

Trên thiết bị di động, trình duyệt sẽ thêm các tính năng nhấn làm nổi bật và chọn văn bản vào nhãn và đầu vào. Các vấn đề này ảnh hưởng tiêu cực đến phong cách và phản hồi tương tác trực quan cần công tắc này. Với một vài dòng CSS, tôi có thể loại bỏ các hiệu ứng đó và thêm kiểu cursor: pointer của riêng tôi:

.gui-switch {
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

Không phải lúc nào cũng nên xoá các kiểu đó vì chúng có thể mang lại giá trị hình ảnh và tương tác. Hãy nhớ cung cấp các lựa chọn thay thế tuỳ chỉnh nếu bạn xoá chúng.

Theo dõi

Kiểu của phần tử này chủ yếu là về hình dạng và màu sắc mà phần tử truy cập từ phần tử mẹ .gui-switch thông qua thác.

Các biến thể chuyển đổi có kích thước và màu sắc của kênh tuỳ chỉnh.

.gui-switch > input {
  appearance: none;
  border: none;
  outline-offset: 5px;
  box-sizing: content-box;

  padding: var(--track-padding);
  background: var(--track-color-inactive);
  inline-size: var(--track-size);
  block-size: var(--thumb-size);
  border-radius: var(--track-size);
}

Nhiều tuỳ chọn tuỳ chỉnh cho kênh chuyển đổi có 4 lựa chọn thuộc tính tuỳ chỉnh. border: none được thêm vì appearance: none không xoá đường viền khỏi hộp đánh dấu trên tất cả trình duyệt.

Thumb

Phần tử thumb đã có ở bên phải track nhưng cần kiểu vòng tròn:

.gui-switch > input::before {
  background: var(--thumb-color);
  border-radius: 50%;
}

Công cụ cho nhà phát triển cho thấy phần tử giả ngón cái vòng tròn được làm nổi bật.

Tương tác

Sử dụng thuộc tính tuỳ chỉnh để chuẩn bị cho những lượt tương tác sẽ hiển thị thao tác di chuột được đánh dấu và thay đổi vị trí ngón cái. Lựa chọn ưu tiên của người dùng cũng là đã chọn trước khi chuyển đổi kiểu đánh dấu chuyển động hoặc di chuột.

.gui-switch > input::before {
  box-shadow: 0 0 0 var(--highlight-size) var(--thumb-color-highlight);

  @media (--motionOK) { & {
    transition:
      transform var(--thumb-transition-duration) ease,
      box-shadow .25s ease;
  }}
}

Vị trí ngón cái

Thuộc tính tuỳ chỉnh cung cấp một cơ chế nguồn duy nhất để định vị nút thumb trong bản nhạc. Chúng tôi sẽ tuỳ ý sử dụng kích thước thumb và track sẽ sử dụng trong các phép tính khác để đảm bảo độ lệch phù hợp và giữa các nội dung nằm trong kênh: 0%100%.

Phần tử input sở hữu biến vị trí --thumb-position và ngón cái phần tử giả sử dụng nó làm vị trí translateX:

.gui-switch > input {
  --thumb-position: 0%;
}

.gui-switch > input::before {
  transform: translateX(var(--thumb-position));
}

Giờ đây, chúng ta có thể thoải mái thay đổi --thumb-position từ CSS và các lớp giả được cung cấp trên các phần tử hộp đánh dấu. Vì chúng ta đặt transition: transform var(--thumb-transition-duration) ease sớm hơn trên phần tử này theo cách có điều kiện nên các thay đổi này có thể tạo ảnh động khi thay đổi:

/* positioned at the end of the track: track length - 100% (thumb width) */
.gui-switch > input:checked {
  --thumb-position: calc(var(--track-size) - 100%);
}

/* positioned in the center of the track: half the track - half the thumb */
.gui-switch > input:indeterminate {
  --thumb-position: calc(
    (var(--track-size) / 2) - (var(--thumb-size) / 2)
  );
}

Tôi nghĩ rằng sự sắp xếp tách rời này có kết quả tốt. Phần tử thumb là chỉ liên quan đến một kiểu, vị trí translateX. Dữ liệu đầu vào có thể quản lý tất cả độ phức tạp và tính toán.

Dọc

Việc hỗ trợ được thực hiện bằng một lớp đối tượng sửa đổi -vertical, lớp này sẽ thêm chế độ xoay với CSS chuyển đổi thành phần tử input.

Tuy nhiên, phần tử được xoay 3D không thay đổi chiều cao tổng thể của thành phần, có thể loại bỏ bố cục khối. Tính đến việc này bằng cách sử dụng --track-size và Biến --track-padding. Tính dung lượng tối thiểu cần thiết cho một nút dọc để chạy trong bố cục như mong đợi:

.gui-switch.-vertical {
  min-block-size: calc(var(--track-size) + calc(var(--track-padding) * 2));

  & > input {
    transform: rotate(-90deg);
  }
}

(RTL) từ phải sang trái

Một người bạn CSS, Elad Schecter và tôi đã tạo nguyên mẫu cùng một trình đơn bên trượt ra bằng cách sử dụng các biến đổi CSS được xử lý từ phải sang trái bằng cách lật một biến. Chúng tôi làm điều này vì không có biến đổi thuộc tính logic trong CSS, và có thể không bao giờ có. Elad có ý tưởng tuyệt vời về việc sử dụng giá trị thuộc tính tuỳ chỉnh để đảo ngược tỷ lệ phần trăm, để cho phép quản lý một vị trí theo logic cho phép biến đổi logic. Tôi đã sử dụng chính kỹ thuật này trong quá trình chuyển đổi này và tôi tôi nghĩ rằng mọi việc đã diễn ra rất tốt:

.gui-switch {
  --isLTR: 1;

  &:dir(rtl) {
    --isLTR: -1;
  }
}

Ban đầu, một thuộc tính tuỳ chỉnh có tên là --isLTR chứa giá trị 1, có nghĩa là true vì theo mặc định, bố cục của chúng ta là từ trái sang phải. Sau đó, sử dụng CSS lớp giả :dir(), giá trị được đặt thành -1 khi thành phần này nằm trong bố cục từ phải sang trái.

Đưa --isLTR vào thực tế bằng cách sử dụng nó trong calc() ở bên trong một phép biến đổi:

.gui-switch.-vertical > input {
  transform: rotate(-90deg);
  transform: rotate(calc(90deg * var(--isLTR) * -1));
}

Bây giờ, hướng xoay của công tắc dọc tài khoản cho vị trí phía đối diện theo yêu cầu của bố cục từ phải sang trái.

translateX biến đổi trên phần tử giả thumb cũng cần được cập nhật thành tính đến yêu cầu ở chiều ngược lại:

.gui-switch > input:checked {
  --thumb-position: calc(var(--track-size) - 100%);
  --thumb-position: calc((var(--track-size) - 100%) * var(--isLTR));
}

.gui-switch > input:indeterminate {
  --thumb-position: calc(
    (var(--track-size) / 2) - (var(--thumb-size) / 2)
  );
  --thumb-position: calc(
   ((var(--track-size) / 2) - (var(--thumb-size) / 2))
    * var(--isLTR)
  );
}

Mặc dù phương pháp này sẽ không hoạt động để giải quyết tất cả các nhu cầu về một khái niệm như CSS logic chuyển đổi, nó cung cấp một số Nguyên tắc về DRY (DRY) đối với nhiều các trường hợp sử dụng.

Tiểu bang

Việc sử dụng input[type="checkbox"] tích hợp sẵn sẽ không hoàn chỉnh nếu không có xử lý các trạng thái khác nhau như :checked, :disabled, :indeterminate:hover. :focus cố tình để lại một mình với điều chỉnh chỉ được thực hiện đối với khoản bù trừ; vòng lấy nét trông rất tuyệt trên Firefox và Safari:

Ảnh chụp màn hình vòng lấy nét lấy tiêu điểm vào một nút chuyển trên Firefox và Safari.

Đã chọn

<label for="switch-checked" class="gui-switch">
  Default
  <input type="checkbox" role="switch" id="switch-checked" checked="true">
</label>

Trạng thái này thể hiện trạng thái on. Ở trạng thái này, đầu vào "theo dõi" nền được đặt thành màu hoạt động và vị trí ngón cái được đặt thành " kết thúc".

.gui-switch > input:checked {
  background: var(--track-color-active);
  --thumb-position: calc((var(--track-size) - 100%) * var(--isLTR));
}

Đã tắt

<label for="switch-disabled" class="gui-switch">
  Default
  <input type="checkbox" role="switch" id="switch-disabled" disabled="true">
</label>

Nút :disabled không chỉ có hình thức khác biệt mà còn khiến cho Phần tử không thể thay đổi.Tính không biến đổi của tương tác là không có trong trình duyệt, nhưng trạng thái hình ảnh cần kiểu do sử dụng appearance: none.

.gui-switch > input:disabled {
  cursor: not-allowed;
  --thumb-color: transparent;

  &::before {
    cursor: not-allowed;
    box-shadow: inset 0 0 0 2px hsl(0 0% 100% / 50%);

    @media (prefers-color-scheme: dark) { & {
      box-shadow: inset 0 0 0 2px hsl(0 0% 0% / 50%);
    }}
  }
}

Nút chuyển kiểu tối đang ở trạng thái tắt, đã đánh dấu và bỏ đánh dấu
các trạng thái.

Trạng thái này khá phức tạp vì cần có giao diện tối và sáng khi bật cả hai tuỳ chọn trạng thái đã đánh dấu. Tôi đã chọn kiểu tối giản cho những trạng thái này sao cho dễ chịu gánh nặng duy trì của việc kết hợp kiểu.

Không xác định

Một trạng thái thường bị quên là :indeterminate, trong đó không có hộp đánh dấu đã chọn hoặc bỏ chọn. Đây là một trạng thái thú vị, hấp dẫn và khiêm tốn. Tốt để nhắc rằng các trạng thái boolean có thể có trạng thái lén lút giữa các trạng thái.

Việc đặt hộp đánh dấu là không xác định, chỉ JavaScript mới có thể đặt hộp đánh dấu:

<label for="switch-indeterminate" class="gui-switch">
  Indeterminate
  <input type="checkbox" role="switch" id="switch-indeterminate">
  <script>document.getElementById('switch-indeterminate').indeterminate = true</script>
</label>

Trạng thái không xác định có nút thumb trong bảng điều khiển
ở giữa, để cho biết rằng họ chưa quyết định.

Với tôi, tiểu bang không hề thân thiện và hấp dẫn nên tôi cảm thấy rất phù hợp để đưa vị trí ngón cái của công tắc ở giữa:

.gui-switch > input:indeterminate {
  --thumb-position: calc(
    calc(calc(var(--track-size) / 2) - calc(var(--thumb-size) / 2))
    * var(--isLTR)
  );
}

Khoảng cách di

Hoạt động tương tác khi di chuột sẽ cung cấp hình ảnh trực quan cho giao diện người dùng được kết nối, đồng thời cung cấp hướng tới giao diện người dùng tương tác. Nút chuyển này làm nổi bật nút thumb vòng bán trong suốt khi di chuột qua nhãn hoặc dữ liệu đầu vào. Thao tác di chuột này ảnh động sau đó cung cấp hướng về phần tử thumb tương tác.

"Điểm nổi bật" hiệu ứng được thực hiện bằng box-shadow. Khi di chuột, của một đầu vào không bị tắt, hãy tăng kích thước của --highlight-size. Nếu người dùng vẫn ổn với chuyển động, chúng ta sẽ chuyển đổi box-shadow và thấy nó lớn lên, nếu họ không ổn với chuyển động, thành phần được đánh dấu sẽ xuất hiện ngay lập tức:

.gui-switch > input::before {
  box-shadow: 0 0 0 var(--highlight-size) var(--thumb-color-highlight);

  @media (--motionOK) { & {
    transition:
      transform var(--thumb-transition-duration) ease,
      box-shadow .25s ease;
  }}
}

.gui-switch > input:not(:disabled):hover::before {
  --highlight-size: .5rem;
}

JavaScript

Đối với tôi, giao diện công tắc có thể gây bất tiện khi cố gắng mô phỏng một giao diện giao diện, đặc biệt là loại này với một vòng tròn bên trong một đường đi. iOS đã khắc phục được vấn đề này bằng công tắc của họ, bạn có thể kéo họ từ bên này sang bên kia, và thật hài lòng khi có tuỳ chọn. Ngược lại, một phần tử trên giao diện người dùng có thể cảm thấy không hoạt động nếu cử chỉ kéo đã cố gắng và không có gì xảy ra.

Ngón tay cái có thể kéo

Phần tử giả ngón cái nhận vị trí từ .gui-switch > input trong phạm vi var(--thumb-position), JavaScript có thể cung cấp một giá trị kiểu cùng dòng trên đầu vào để cập nhật động vị trí ngón tay cái, khiến vị trí đó dường như theo sau cử chỉ con trỏ. Khi thả con trỏ, hãy xoá các kiểu cùng dòng và xác định xem lực kéo đó gần với vị trí tắt hơn hay bật hơn bằng cách sử dụng thuộc tính tùy chỉnh --thumb-position. Đây là trọng tâm của giải pháp; sự kiện con trỏ theo dõi có điều kiện vị trí con trỏ để sửa đổi các thuộc tính tuỳ chỉnh CSS.

Vì thành phần đã hoạt động 100% trước khi tập lệnh này hiển thị thì sẽ mất khá nhiều công sức để duy trì hành vi hiện tại, như nhấp vào một nhãn để bật/tắt mục nhập. JavaScript của chúng tôi không nên thêm tính năng tại những tính năng hiện có.

touch-action

Kéo là một cử chỉ tuỳ chỉnh, khiến nó trở thành một cử chỉ tuyệt vời Lợi ích của touch-action. Trong trường hợp công tắc này, một cử chỉ ngang sẽ được tập lệnh của chúng tôi xử lý hoặc một cử chỉ dọc được ghi lại cho nút chuyển dọc biến thể. Với touch-action, chúng ta có thể cho trình duyệt biết cần xử lý cử chỉ nào phần tử này, để một tập lệnh có thể xử lý một cử chỉ mà không cần cạnh tranh.

CSS sau đây hướng dẫn trình duyệt rằng khi cử chỉ con trỏ bắt đầu từ trong kênh chuyển đổi này, xử lý các cử chỉ dọc, không làm gì với thao tác ngang đơn vị:

.gui-switch > input {
  touch-action: pan-y;
}

Kết quả mong muốn là một cử chỉ theo chiều ngang mà không xoay hoặc cuộn . Con trỏ có thể cuộn theo chiều dọc bắt đầu từ bên trong giá trị nhập và cuộn nhưng các trang ngang được xử lý tuỳ chỉnh.

Tiện ích kiểu của giá trị pixel

Khi thiết lập và trong quá trình kéo, bạn cần lấy nhiều giá trị số đã tính từ các phần tử. Các hàm JavaScript sau đây trả về giá trị pixel đã tính toán cho một thuộc tính CSS. Mã này được dùng trong tập lệnh thiết lập như thế này getStyle(checkbox, 'padding-left').

​​const getStyle = (element, prop) => {
  return parseInt(window.getComputedStyle(element).getPropertyValue(prop));
}

const getPseudoStyle = (element, prop) => {
  return parseInt(window.getComputedStyle(element, ':before').getPropertyValue(prop));
}

export {
  getStyle,
  getPseudoStyle,
}

Hãy lưu ý cách window.getComputedStyle() chấp nhận đối số thứ hai là phần tử giả mục tiêu. Khá thú vị khi JavaScript có thể đọc rất nhiều giá trị từ các phần tử, thậm chí từ các phần tử giả.

dragging

Đây là thời điểm cốt lõi trong logic kéo và có một vài điều cần lưu ý qua trình xử lý sự kiện hàm:

const dragging = event => {
  if (!state.activethumb) return

  let {thumbsize, bounds, padding} = switches.get(state.activethumb.parentElement)
  let directionality = getStyle(state.activethumb, '--isLTR')

  let track = (directionality === -1)
    ? (state.activethumb.clientWidth * -1) + thumbsize + padding
    : 0

  let pos = Math.round(event.offsetX - thumbsize / 2)

  if (pos < bounds.lower) pos = 0
  if (pos > bounds.upper) pos = bounds.upper

  state.activethumb.style.setProperty('--thumb-position', `${track + pos}px`)
}

Nội dung chính của tập lệnh là state.activethumb, vòng tròn nhỏ của tập lệnh này là cùng với một con trỏ. Đối tượng switchesMap() trong đó khoá là .gui-switch, còn giá trị là giới hạn và kích thước được lưu vào bộ nhớ đệm để giữ cho cho tập lệnh một cách hiệu quả. Từ phải sang trái được xử lý bằng cùng một thuộc tính tùy chỉnh CSS đó là --isLTR và có thể sử dụng CSS này để đảo ngược logic và tiếp tục hỗ trợ RTL. event.offsetX cũng có giá trị vì chứa delta giá trị hữu ích để định vị ngón cái.

state.activethumb.style.setProperty('--thumb-position', `${track + pos}px`)

Dòng cuối cùng này của CSS đặt thuộc tính tùy chỉnh mà phần tử ngón cái sử dụng. Chiến dịch này nếu không gán giá trị sẽ chuyển đổi theo thời gian, nhưng con trỏ trước đó sự kiện đã tạm thời đặt --thumb-transition-duration thành 0s, xóa những gì có thể là một tương tác chậm chạp.

dragEnd

Để người dùng được phép kéo ra ngoài công tắc và thả công tắc, sự kiện cửa sổ toàn cầu cần được đăng ký:

window.addEventListener('pointerup', event => {
  if (!state.activethumb) return

  dragEnd(event)
})

Tôi nghĩ rằng điều quan trọng là người dùng phải được tự do kéo và thả lỏng giao diện đủ thông minh để giải thích. Tôi không mất nhiều công sức để xử lý với việc chuyển đổi này, nhưng cần xem xét cẩn thận trong quá trình phát triển của chúng tôi.

const dragEnd = event => {
  if (!state.activethumb) return

  state.activethumb.checked = determineChecked()

  if (state.activethumb.indeterminate)
    state.activethumb.indeterminate = false

  state.activethumb.style.removeProperty('--thumb-transition-duration')
  state.activethumb.style.removeProperty('--thumb-position')
  state.activethumb.removeEventListener('pointermove', dragging)
  state.activethumb = null

  padRelease()
}

Đã hoàn tất tương tác với phần tử, đã đến lúc đặt giá trị đầu vào thuộc tính và xoá tất cả các sự kiện cử chỉ. Đã thay đổi hộp đánh dấu bằng state.activethumb.checked = determineChecked().

determineChecked()

Hàm này (do dragEnd gọi), xác định vị trí hiện tại của ngón cái trong giới hạn của kênh và trả về true nếu giá trị đó bằng hoặc lớn hơn ở giữa đường đua:

const determineChecked = () => {
  let {bounds} = switches.get(state.activethumb.parentElement)

  let curpos =
    Math.abs(
      parseInt(
        state.activethumb.style.getPropertyValue('--thumb-position')))

  if (!curpos) {
    curpos = state.activethumb.checked
      ? bounds.lower
      : bounds.upper
  }

  return curpos >= bounds.middle
}

Ý kiến khác

Thao tác kéo phát sinh một chút thiếu mã do cấu trúc HTML ban đầu đã chọn, chủ yếu là gói thông tin đầu vào trong một nhãn. Nhãn, là cha mẹ , sẽ nhận được lượt tương tác nhấp chuột sau khi nhập. Ở cuối Sự kiện dragEnd, có thể bạn đã thấy padRelease() có vẻ kỳ lạ .

const padRelease = () => {
  state.recentlyDragged = true

  setTimeout(_ => {
    state.recentlyDragged = false
  }, 300)
}

Việc này là để tính đến việc nhãn nhận được lần nhấp sau này, vì nó sẽ bỏ chọn, hoặc kiểm tra, tương tác mà một người dùng đã thực hiện.

Nếu phải làm lại điều này, tôi có thể xem xét điều chỉnh DOM bằng JavaScript trong quá trình nâng cấp trải nghiệm người dùng, chẳng hạn như để tạo một phần tử tự xử lý các lượt nhấp vào nhãn và không chiến đấu bằng hành vi tích hợp sẵn.

Loại JavaScript này là loại khó viết nhất của tôi, tôi không muốn quản lý Hiển thị bong bóng sự kiện có điều kiện:

const preventBubbles = event => {
  if (state.recentlyDragged)
    event.preventDefault() && event.stopPropagation()
}

Kết luận

Thành phần chuyển đổi dành cho trẻ em này rốt cuộc trở thành thành phần quan trọng nhất trong tất cả Thử thách về giao diện người dùng đồ hoạ cho đến nay! Giờ bạn đã biết cách tôi thực hiện điều đó, bạn sẽ làm cách nào‽ 🙂

Hãy đa dạng hoá phương pháp tiếp cận và tìm hiểu tất cả các cách xây dựng ứng dụng trên web. Tạo một bản minh hoạ, tweet cho tôi các đường liên kết và tôi sẽ thêm vào vào phần bản phối lại của cộng đồng dưới đây!

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

Tài nguyên

Tìm mã nguồn .gui-switch trên GitHub.