Thông tin tổng quan cơ bản về cách tạo một thành phần công tắc thích ứng và hỗ trợ tiếp cận.
Trong bài đăng này, tôi muốn chia sẻ suy nghĩ về cách tạo các thành phần công tắc. Dùng thử bản minh hoạ.
Nếu bạn thích xem video, thì đây là phiên bản video của bài đăng này trên YouTube:
Tổng quan
Công tắc 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 của boolean.
Bản minh hoạ này sử dụng <input type="checkbox" role="switch">
cho phần lớn chức năng của mình. Điều này có ưu điểm là không cần CSS hoặc JavaScript để hoạt động đầy đủ và dễ truy cập. CSS tải mang đến sự hỗ trợ cho các ngôn ngữ viết từ phải sang trái, tính dọc, ảnh động và nhiều tính năng khác. Việc tải JavaScript giúp công tắc có thể kéo và hữu hình.
Thuộc tính tuỳ chỉnh
Các biến sau đây biểu thị nhiều phần của công tắc và các lựa chọn của chúng. Là lớp cấp cao nhất, .gui-switch
chứa các thuộc tính tuỳ chỉnh được dùng trong các thành phần con và điểm truy cập để tuỳ chỉnh tập trung.
Theo dõi
Chiều dài (--track-size
), khoảng đệm và 2 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 đánh dấu tương tác:
.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 một bí danh rõ ràng và giảm sự lặp lại, bạn có thể đặt truy vấn nội dung nghe nhìn ưu tiên giảm chuyển động của người dùng vào một thuộc tính tuỳ chỉnh bằng trình bổ trợ PostCSS dựa trên bản nháp đặc tả này trong Truy vấn nội dung nghe nhìn 5:
@custom-media --motionOK (prefers-reduced-motion: no-preference);
Markup (note: đây là tên ứng dụng)
Tôi chọn bao bọc phần tử <input type="checkbox" role="switch">
bằng <label>
, liên kết mối quan hệ của chúng để tránh sự mơ hồ về mối liên kết giữa hộp đánh dấu và nhãn, đồng thời cho phép người dùng tương tác với nhãn để bật/tắt dữ liệu đầu vào.
<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 API và trạng thái. Trình duyệt quản lý thuộc tính checked
và các sự kiện đầu vào như oninput
và onchanged
.
Bố cục
Flexbox, lưới và thuộc tính tuỳ chỉnh là những yếu 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 cho các phép tính hoặc khu vực không rõ ràng và cho phép một API thuộc tính tuỳ chỉnh nhỏ để dễ dàng tuỳ chỉnh thành phần.
.gui-switch
Bố cục cấp cao nhất cho công tắc là flexbox. Lớp .gui-switch
chứa các thuộc tính tuỳ chỉnh riêng tư và công khai mà các thành phần con dùng để tính toán bố cục của chúng.
.gui-switch {
display: flex;
align-items: center;
gap: 2ch;
justify-content: space-between;
}
Việc mở rộng và sửa đổi bố cục hộp linh hoạt cũng giống như việc thay đổi bất kỳ bố cục hộp linh hoạt nào.
Ví dụ: để đặt nhãn ở phía trên hoặc phía dưới một công tắc, hoặc để thay đổi flex-direction
:
<label for="light-switch" class="gui-switch" style="flex-direction: column">
Default
<input type="checkbox" role="switch" id="light-switch">
</label>
Theo dõi
Đầu vào hộp đánh dấu được tạo kiểu dưới dạng một đường chuyển đổi bằng cách xoá appearance: checkbox
thông thường và cung cấp kích thước riêng:
.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;
}
Đường dẫn này cũng tạo ra một vùng đường dẫn lưới một ô duy nhất để một ngón tay cái có thể yêu cầu.
Thumb
Kiểu appearance: none
cũng xoá dấu đánh dấu trực quan do trình duyệt cung cấp. Thành phần này sử dụng một phần tử giả và :checked
lớp giả trên dữ liệu đầu vào để thay thế chỉ báo trực quan này.
Ngón tay cái là một phần tử con giả được đính kèm vào input[type="checkbox"]
và xếp chồng lên trên đường theo dõi thay vì bên dưới bằng cách xác nhận vùng lưới track
:
.gui-switch > input::before {
content: "";
grid-area: track;
inline-size: var(--thumb-size);
block-size: var(--thumb-size);
}
Kiểu
Các thuộc tính tuỳ chỉnh cho phép một thành phần công tắc linh hoạt thích ứng với các bảng phối màu, ngôn ngữ từ phải sang trái và lựa chọn ưu tiên về chuyển động.
Kiểu tương tác bằng thao tác chạm
Trên thiết bị di động, các trình duyệt sẽ thêm tính năng đánh dấu khi nhấn và chọn văn bản vào nhãn và dữ liệu đầu vào. Những yếu tố này ảnh hưởng tiêu cực đến kiểu và phản hồi tương tác trực quan mà nút chuyển này cần. Với một vài dòng CSS, tôi có thể xoá những hiệu ứng đó và thêm kiểu cursor: pointer
của riêng mình:
.gui-switch {
cursor: pointer;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
Bạn không nên xoá các kiểu đó vì chúng có thể là thông tin phản hồi hữu ích về tương tác trực quan. Hãy nhớ cung cấp các lựa chọn thay thế tuỳ chỉnh nếu bạn xoá các lựa chọn này.
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ử này truy cập từ .gui-switch
mẹ thông qua cascade (thác).
.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);
}
Có nhiều lựa chọn tuỳ chỉnh cho đường chuyển đổi đến từ 4 thuộc tính tuỳ chỉnh. border: none
được thêm vào vì appearance: none
không xoá đường viền khỏi hộp đánh dấu trên tất cả các trình duyệt.
Thumb
Phần tử ngón tay cái đã nằm ở bên phải track
nhưng cần có kiểu hình tròn:
.gui-switch > input::before {
background: var(--thumb-color);
border-radius: 50%;
}
Tương tác
Sử dụng các thuộc tính tuỳ chỉnh để chuẩn bị cho các lượt tương tác sẽ cho thấy điểm nổi bật khi di chuột và các thay đổi về vị trí ngón tay cái. Lựa chọn ưu tiên của người dùng cũng được kiểm tra trước khi chuyển đổi kiểu chuyển động hoặc kiểu làm nổi bật khi 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 tay cái
Các thuộc tính tuỳ chỉnh cung cấp một cơ chế nguồn duy nhất để đặt ngón tay cái vào vị trí trong bản nhạc. Chúng ta có kích thước của thanh trượt và hình thu nhỏ. Chúng ta sẽ dùng các kích thước này trong các phép tính để giữ cho hình thu nhỏ được bù đắp đúng cách và nằm trong thanh trượt: 0%
và 100%
.
Phần tử input
sở hữu biến vị trí --thumb-position
và phần tử giả thumb sử dụng biến này 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ể 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
có điều kiện cho phần tử này trước đó, nên những 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ĩ việc điều phối tách rời này đã hoạt động hiệu quả. Phần tử ngón tay cái chỉ liên quan đến một kiểu, đó là vị trí translateX
. Đầu vào có thể quản lý tất cả độ phức tạp và các phép tính.
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 một vòng xoay bằng các phép biến đổi CSS vào phần tử input
.
Tuy nhiên, một phần tử xoay 3D không làm thay đổi chiều cao tổng thể của thành phần, điều này có thể làm sai lệch bố cục khối. Hãy tính đến điều này bằng cách sử dụng các biến --track-size
và --track-padding
. Tính toán lượng không gian tối thiểu cần thiết để một nút dọc có thể chuyển động 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 đã cùng nhau tạo mẫu trình đơn bên trượt ra bằng cách sử dụng các biến đổi CSS xử lý các ngôn ngữ từ phải sang trái bằng cách lật một biến duy nhất. Chúng tôi đã làm như vậy vì không có các biến đổi thuộc tính logic trong CSS và có thể sẽ không bao giờ có. Elad đã có ý tưởng tuyệt vời là sử dụng một giá trị thuộc tính tuỳ chỉnh để đảo ngược tỷ lệ phần trăm, nhằm cho phép quản lý một vị trí duy nhất của logic tuỳ chỉnh của chúng tôi cho các phép biến đổi logic. Tôi đã sử dụng chính kỹ thuật này trong công tắc này và tôi nghĩ nó hoạt động 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 --isLTR
sẽ giữ giá trị 1
, tức là true
vì bố cục của chúng ta theo mặc định là từ trái sang phải. Sau đó, bằng cách sử dụng lớp giả CSS :dir()
, giá trị được đặt thành -1
khi thành phần nằm trong bố cục từ phải sang trái.
Đưa --isLTR
vào hoạt động 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));
}
Giờ đây, việc xoay công tắc dọc sẽ tính đến vị trí bên đối diện mà bố cục từ phải sang trái yêu cầu.
Bạn cũng cần cập nhật các phép biến đổi translateX
trên phần tử giả ngón tay cái để tính đến yêu cầu về phía đối diện:
.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 không giải quyết được mọi nhu cầu liên quan đến một khái niệm như biến đổi CSS logic, nhưng nó cung cấp một số nguyên tắc DRY cho nhiều trường hợp sử dụng.
Tiểu bang
Việc sử dụng input[type="checkbox"]
tích hợp sẽ không hoàn chỉnh nếu không xử lý các trạng thái mà thành phần này có thể ở trong đó: :checked
, :disabled
, :indeterminate
và :hover
. :focus
được cố ý giữ nguyên, chỉ điều chỉnh độ bù trừ; vòng tiêu điểm trông rất đẹp 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 biểu thị trạng thái on
. Ở trạng thái này, nền "track" đầu vào được đặt thành màu đang hoạt động và vị trí ngón tay cái được đặt thành "cuối".
.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 phải làm cho phần tử không thay đổi.Khả năng tương tác không thay đổi không phụ thuộc vào trình duyệt, nhưng các trạng thái trực quan cần có kiểu do việc 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%);
}}
}
}
Trạng thái này khá phức tạp vì cần có giao diện tối và giao diện sáng với cả trạng thái đã tắt và đã đánh dấu. Tôi đã chọn phong cách tối giản cho các trạng thái này để giảm bớt gánh nặng duy trì các tổ hợp phong cách.
Không xác định
Một trạng thái thường bị bỏ quên là :indeterminate
, trong đó hộp đánh dấu không được đánh dấu hoặc bỏ đánh dấu. Đây là một trạng thái vui vẻ, hấp dẫn và khiêm tốn. Một lời nhắc hữu ích rằng các trạng thái boolean có thể có các trạng thái trung gian khó nhận biết.
Bạn khó có thể đặt hộp đánh dấu ở trạng thái không xác định, chỉ JavaScript mới có thể đặt hộp đánh dấu ở trạng thái này:
<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>
Vì trạng thái này đối với tôi là khiêm tốn và hấp dẫn, nên tôi cảm thấy phù hợp khi đặt vị trí ngón tay cái trên nút bật/tắt ở 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
Tương tác di chuột phải hỗ trợ trực quan cho giao diện người dùng được kết nối, đồng thời cung cấp hướng dẫn cho giao diện người dùng tương tác. Công tắc này làm nổi bật ngón tay cái bằng một vòng bán trong suốt khi nhãn hoặc đầu vào được di chuột qua. Sau đó, hiệu ứng di chuột này sẽ cung cấp hướng dẫn về phần tử hình thu nhỏ có thể tương tác.
Hiệu ứng "làm nổi bật" được thực hiện bằng box-shadow
. Khi di chuột lên một thành phần đầu vào không bị vô hiệu hoá, hãy tăng kích thước của --highlight-size
. Nếu người dùng không gặp vấn đề với chuyển động, chúng ta sẽ chuyển đổi box-shadow
và thấy nó phát triển. Nếu người dùng gặp vấn đề với chuyển động, phần nổi bật 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ủa công tắc có thể tạo cảm giác kỳ lạ khi cố gắng mô phỏng một giao diện thực, đặc biệt là loại giao diện có một vòng tròn bên trong một đường kẻ. iOS đã làm đúng điều này với công tắc của họ, bạn có thể kéo công tắc từ bên này sang bên kia và cảm thấy rất hài lòng khi có lựa chọn này. Ngược lại, một thành phần trên giao diện người dùng có thể trông không hoạt động nếu người dùng cố gắng thực hiện cử chỉ kéo nhưng không có gì xảy ra.
Hình thu nhỏ có thể kéo
Phần tử giả hình thu nhỏ nhận vị trí từ .gui-switch > input
var(--thumb-position)
có phạm vi, JavaScript có thể cung cấp giá trị kiểu nội tuyến trên đầu vào để cập nhật động vị trí hình thu nhỏ, khiến hình thu nhỏ xuất hiện theo cử chỉ con trỏ. Khi con trỏ được nhả ra, hãy xoá các kiểu nội tuyến và xác định xem thao tác kéo gần với trạng thái tắt hay bật bằng cách sử dụng thuộc tính tuỳ chỉnh --thumb-position
. Đây là nền tảng của giải pháp; các 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 này đã hoạt động 100% trước khi tập lệnh này xuất hiện, nên bạn cần phải bỏ ra khá nhiều công sức để duy trì hành vi hiện có, chẳng hạn như nhấp vào nhãn để bật/tắt dữ liệu đầu vào. JavaScript của chúng tôi không được thêm các tính năng làm ảnh hưởng đến các tính năng hiện có.
touch-action
Kéo là một cử chỉ tuỳ chỉnh, nên đây là lựa chọn lý tưởng cho các lợi ích của touch-action
. Trong trường hợp 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 sẽ được ghi lại cho biến thể công tắc dọc. Với touch-action
, chúng ta có thể cho trình duyệt biết những cử chỉ cần xử lý trên phần tử này, nhờ đó, một tập lệnh có thể xử lý một cử chỉ mà không bị xung đột.
CSS sau đây hướng dẫn trình duyệt rằng khi một cử chỉ bằng con trỏ bắt đầu từ trong đường chuyển đổi này, hãy xử lý các cử chỉ dọc, không làm gì với các cử chỉ ngang:
.gui-switch > input {
touch-action: pan-y;
}
Kết quả mong muốn là một cử chỉ ngang mà không đồng thời di chuyển hoặc cuộn trang. Con trỏ có thể cuộn theo chiều dọc bắt đầu từ bên trong dữ liệu đầu vào và cuộn trang, nhưng các con trỏ ngang được xử lý tuỳ chỉnh.
Tiện ích kiểu giá trị pixel
Trong quá trình thiết lập và kéo, bạn sẽ cần lấy nhiều giá trị số được tính toán từ các phần tử. Các hàm JavaScript sau đây trả về các giá trị pixel được tính toán cho một thuộc tính CSS. Nó đượ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,
}
Lưu ý cách window.getComputedStyle()
chấp nhận đối số thứ hai, một phần tử giả mục tiêu. JavaScript có thể đọc rất nhiều giá trị từ các phần tử, ngay cả từ các phần tử giả.
dragging
Đây là thời điểm cốt lõi cho logic kéo và có một vài điều cần lưu ý từ 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`)
}
Thành phần chính của tập lệnh là state.activethumb
, vòng tròn nhỏ mà tập lệnh này đang định vị cùng với một con trỏ. Đối tượng switches
là một Map()
, trong đó các khoá là .gui-switch
và các giá trị là kích thước và ranh giới được lưu vào bộ nhớ đệm giúp tập lệnh hoạt động hiệu quả. Hướng từ phải sang trái được xử lý bằng cách sử dụng cùng một thuộc tính tuỳ chỉnh mà CSS --isLTR
, đồng thời có thể sử dụng thuộc tính này để đảo ngược logic và tiếp tục hỗ trợ hướng từ phải sang trái. event.offsetX
cũng có giá trị vì chứa giá trị delta hữu ích cho việc định vị ngón tay cái.
state.activethumb.style.setProperty('--thumb-position', `${track + pos}px`)
Dòng CSS cuối cùng này đặt thuộc tính tuỳ chỉnh mà phần tử hình thu nhỏ sử dụng. Nếu không, việc chỉ định giá này sẽ chuyển đổi theo thời gian, nhưng một sự kiện con trỏ trước đó đã tạm thời đặt --thumb-transition-duration
thành 0s
, loại bỏ những gì lẽ ra là một hoạt động 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ả ra, cần đăng ký một sự kiện cửa sổ chung:
window.addEventListener('pointerup', event => {
if (!state.activethumb) return
dragEnd(event)
})
Tôi nghĩ điều rất quan trọng là người dùng có quyền tự do kéo một cách thoải mái và giao diện đủ thông minh để tính đến điều đó. Không mất nhiều thời gian để xử lý bằng nút chuyển này, nhưng bạn cần cân nhắc kỹ lưỡng trong quá trình phát triển.
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()
}
Tương tác với phần tử đã hoàn tất, đã đến lúc đặt thuộc tính đầu vào đã kiểm tra và xoá tất cả các sự kiện cử chỉ. Hộp đánh dấu được thay đổi 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 tay cái trong phạm vi của đường theo dõi và trả về giá trị true nếu vị trí đó bằng hoặc lớn hơn một nửa đường theo dõi:
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
}
Suy nghĩ thêm
Cử chỉ kéo gây ra một chút nợ mã do cấu trúc HTML ban đầu được chọn, đáng chú ý nhất là việc gói đầu vào trong một nhãn. Nhãn này là một phần tử mẹ, sẽ nhận được các lượt tương tác nhấp sau khi người dùng nhập. Ở cuối sự kiện dragEnd
, có thể bạn đã nhận thấy padRelease()
là một hàm có vẻ lạ.
const padRelease = () => {
state.recentlyDragged = true
setTimeout(_ => {
state.recentlyDragged = false
}, 300)
}
Điều này là để tính đến nhãn nhận được lượt nhấp này sau đó, vì nhãn sẽ bỏ chọn hoặc chọn hoạt động tương tác mà người dùng đã thực hiện.
Nếu làm lại việc này, tôi có thể cân nhắc điều chỉnh DOM bằng JavaScript trong quá trình nâng cấp trải nghiệm người dùng, để tạo một phần tử tự xử lý các lượt nhấp vào nhãn và không xung đột với hành vi tích hợp.
Đây là loại JavaScript mà tôi ít thích viết nhất, tôi không muốn quản lý tính năng truyền 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 nhỏ xíu này hoá ra lại là thành phần tốn nhiều công sức nhất trong tất cả các Thử thách về giao diện người dùng cho đến nay! Giờ bạn đã biết cách tôi làm, vậy bạn sẽ làm như thế nào‽ 🙂
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
- @KonstantinRouda với một phần tử tuỳ chỉnh: demo và code.
- @jhvanderschee bằng một nút: Codepen.
Tài nguyên
Tìm .gui-switch
mã nguồn trên GitHub.