Thông tin tổng quan cơ bản về cách tạo thành phần nút chuyển đổi 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 thành phần nút chuyển. Dùng thử bản minh hoạ.
Nếu bạn thích xem video, hãy xem phiên bản video 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õ 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 chức năng, có ưu điểm là không cần CSS hoặc JavaScript để hoạt động và truy cập đầy đủ. Tính năng tải CSS hỗ trợ các ngôn ngữ từ phải sang trái, chiều dọc, ảnh động và nhiều tính năng khác. Việc tải JavaScript giúp nút chuyển 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 nhiều phần của nút chuyển và các tuỳ chọn của các phần đó. 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 trong tất cả các thành phần con và các điểm truy cập để tuỳ chỉnh tập trung.
Theo dõi
Chiều 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 đá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ể đưa truy vấn nội dung nghe nhìn ưu tiên chuyển động giảm 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 thông số kỹ thuật nháp 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 gói phần tử <input type="checkbox" role="switch">
bằng <label>
, gói mối quan hệ của chúng để tránh sự mơ hồ liên kết 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 với một 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 đóng vai trò quan trọng trong việc duy trì kiểu của thành phần này. Các thành phần này 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à bật 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 nút chuyển 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 cháu sử 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 flexbox cũng giống như thay đổi bất kỳ bố cục flexbox nào.
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
:
<label for="light-switch" class="gui-switch" style="flex-direction: column">
Default
<input type="checkbox" role="switch" id="light-switch">
</label>
Theo dõi
Dữ liệu đầu vào hộp đánh dấu được tạo kiểu dưới dạng kênh 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;
}
Video nhạc cũng tạo một vùng theo dõi lưới ô đơn lẻ để một video thu nhỏ có thể 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 cung cấp. Thành phần này sử dụng một phần tử giả và lớp giả :checked
trên đầu vào để thay thế chỉ báo trực quan này.
Thumb 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 thay vì nằm bên dưới kênh bằng cách xác nhận quyền sở hữu 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 thành phần nút chuyển linh hoạt thích ứng với các giao diện 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 cảm ứng
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 chế độ này ảnh hưởng tiêu cực đến phản hồi tương tác về kiểu và hình ảnh mà nút chuyển đổi cần có. Với một vài dòng CSS, tôi có thể xoá các 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;
}
Không phải lúc nào bạn cũng nên xoá các kiểu đó, vì chúng có thể là thông tin phản hồi có giá trị 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 đó.
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 loạt.
.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 tuỳ chọn tuỳ chỉnh cho kênh chuyển đổi từ 4 thuộc tính tuỳ chỉnh. border: none
được thêm vào vì appearance: none
không loại bỏ đườ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%;
}
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ẽ hiển thị các điểm nổi bật khi di chuột và thay đổi 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
Thuộc tính tuỳ chỉnh cung cấp một cơ chế nguồn duy nhất để định vị con trỏ trong bản nhạc. Chúng ta có thể sử dụng kích thước của bản nhạc và ngón tay cái trong các phép tính để giữ cho ngón tay cái được bù chính xác và nằm trong bản nhạc: 0%
và 100%
.
Phần tử input
sở hữu biến vị trí --thumb-position
và phần tử giả ngón tay cái 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
sớm hơn trên phần tử này theo điều kiện, nên những thay đổi này có thể tạo ảnh động khi được 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ử con trỏ 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à phép tính.
Dọc
Hoạt động 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 với các phép biến đổi CSS vào phần tử input
.
Tuy nhiên, phần tử được xoay 3D không làm thay đổi chiều cao tổng thể của thành phần, nên có thể loại bỏ 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 không gian tối thiểu cần thiết để một nút dọc có thể hiển thị 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
Tôi và một người bạn CSS, Elad Schecter, đã cùng nhau tạo nguyên mẫu trình đơn bên trượt ra bằng cách sử dụng các phép 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ó phép biến đổi thuộc tính logic nào trong CSS và có thể sẽ 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í duy nhất của logic tuỳ chỉnh của chúng ta cho các phép biến đổi logic. Tôi đã sử dụng chính kỹ thuật này trong nút chuyển này và tôi nghĩ rằng nó đã hoạt động rất hiệu quả:
.gui-switch {
--isLTR: 1;
&:dir(rtl) {
--isLTR: -1;
}
}
Một thuộc tính tuỳ chỉnh có tên là --isLTR
ban đầu giữ giá trị 1
, nghĩa là thuộc tính này là true
vì bố cục của chúng ta là từ trái sang phải theo mặc định. Sau đó, 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.
Áp dụng --isLTR
bằng cách sử dụng 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 nút chuyển 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 để 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 tất cả nhu cầu liên quan đến một khái niệm như chuyển đổi CSS logic, nhưng phương pháp này 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ẵn sẽ không hoàn tất nếu không xử lý các trạng thái khác nhau của trạng thái đó: :checked
, :disabled
, :indeterminate
và :hover
. :focus
được cố tình để nguyên, chỉ điều chỉnh độ lệch; vòng lấy nét trông rất tuyệt 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 "đường dẫn" đầ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ỉ khác về mặt hình ảnh mà còn phải làm cho phần tử không thể thay đổi.Tính chất không thể thay đổi của hoạt động tương tác không bị ảnh hưởng bởi trình duyệt, nhưng các trạng thái hình ảnh cần có 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%);
}}
}
}
Trạng thái này khá phức tạp vì cần có giao diện sáng và tối với cả trạng thái tắt và trạng thái đã đánh dấu. Tôi đã chọn kiểu tối thiểu cho các trạng thái này để giảm bớt gánh nặng bảo trì khi 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 đó bạn không đánh dấu hoặc bỏ đánh dấu hộp đánh dấu. Đây là một trạng thái thú vị, hấp dẫn và không phô trương. Xin lưu ý rằng các trạng thái boolean có thể có trạng thái ẩn giữa các trạng thái.
Rất khó để đặt hộp đánh dấu thành không xác định, chỉ JavaScript mới có thể đặt hộp đánh dấu 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à không phô trương và hấp dẫn, nên tôi thấy phù hợp khi đặt vị trí ngón tay cái của nút chuyển ở 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
Các hoạt động tương tác khi di chuột sẽ hỗ trợ trực quan cho giao diện người dùng được kết nối và cũng cung cấp hướng đến 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 tròn bán trong suốt khi bạn di chuột qua nhãn hoặc dữ liệu đầu vào. Sau đó, ảnh động khi di chuột này sẽ cung cấp hướng dẫn đến phần tử ngón tay cái 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 qua một thành phần nhập 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 đồng ý với chuyển động, chúng ta sẽ chuyển đổi box-shadow
và xem nó phát triển, nếu họ không đồng ý với chuyển động, thì điểm 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 nút chuyển có thể gây cảm giác kỳ lạ khi cố gắng mô phỏng giao diện thực tế, đặc biệt là loại giao diện này có một vòng tròn bên trong một kênh. iOS đã làm đúng điều này với nút chuyển, bạn có thể kéo chúng sang hai bên và rất hài lòng khi có lựa chọn này. 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 thử và không có gì xảy ra.
Nút Thích có thể kéo
Phần tử giả ngón tay nhận vị trí từ .gui-switch > input
trong phạm vi var(--thumb-position)
, JavaScript có thể cung cấp giá trị kiểu nội tuyến trên đầu vào để cập nhật linh động vị trí ngón tay cái, khiến nó có vẻ như tuân theo cử chỉ con trỏ. Khi con trỏ được nhả ra, hãy xoá các kiểu cùng dòng và xác định xem thao tác kéo gần với vị trí tắt hay bật hơn bằng cách sử dụng thuộc tính tuỳ chỉnh --thumb-position
. Đây là cốt lõi của giải pháp này; 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 đã hoạt động 100% trước khi tập lệnh này xuất hiện, nên sẽ mất 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 đầu vào. JavaScript của chúng ta không được thêm tính năng mà làm giảm tính năng hiện có.
touch-action
Kéo là một cử chỉ, một cử chỉ tuỳ chỉnh, giúp cử chỉ này trở thành một ứng cử viên tuyệt vời cho các lợi ích của touch-action
. Trong trường hợp nút chuyển này, cử chỉ ngang sẽ do tập lệnh của chúng ta xử lý hoặc cử chỉ dọc được ghi lại cho biến thể nút chuyển 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ờ đó, tập lệnh có thể xử lý một cử chỉ mà không có sự cạnh tranh.
CSS sau đây hướng dẫn trình duyệt rằng khi một cử chỉ con trỏ bắt đầu từ trong kênh 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 không kéo 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ỏ theo chiều 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 toán từ các phần tử. Các hàm JavaScript sau đây trả về giá trị pixel được tính toán dựa trên một thuộc tính CSS. Phương thức này được dùng trong tập lệnh thiết lập như getStyle(checkbox, 'padding-left')
sau.
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, một phần tử giả mạo 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 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`)
}
Nhân vật 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 con trỏ. Đối tượng switches
là một Map()
, trong đó các khoá là .gui-switch
và các giá trị là giới hạn và kích thước đượ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 là --isLTR
và có thể sử dụng thuộc tính này để đảo ngược logic và tiếp tục hỗ trợ RTL. event.offsetX
cũng có giá trị vì chứa giá trị delta hữu ích cho việc định vị ngón 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ử thumb sử dụng. Việc gán giá trị 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
, xoá đi tương tác chậm chạp.
dragEnd
Để người dùng được phép kéo ra ngoài nút chuyển và thả nút ra, một sự kiện cửa sổ chung 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 có thể tự do kéo và giao diện đủ thông minh để tính đến điều đó. Bạn không cần phải mất nhiều thời gian để xử lý vấn đề này 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()
}
Quá trình tương tác với phần tử đã hoàn tất, đã đến lúc đặt thuộc tính đã đánh dấu đầu vào và xoá tất 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í của dòng ngón tay cái nằm trong giới hạn của kênh và trả về true nếu dòng này bằng hoặc lớn hơn một nửa dọc theo kênh:
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ĩ khác
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à gói dữ liệu đầu vào trong một nhãn. Nhãn, là một phần tử mẹ, sẽ nhận được các lượt tương tác nhấp sau khi nhập. Ở cuối sự kiện dragEnd
, bạn có thể 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 việc nhãn nhận được lượt nhấp sau này, vì nhãn sẽ bỏ đánh dấu hoặc đánh dấu hoạt động tương tác mà người dùng đã thực hiện.
Nếu phải làm lại việc 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 với hành vi tích hợp sẵn.
Loại JavaScript này là loại JavaScript mà tôi không thích viết nhất, tôi không muốn quản lý việc tạo 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 nút chuyển nhỏ bé này đã trở thành phần tốn nhiều công sức nhất trong tất cả các Thử thách GUI cho đến nay! Giờ thì bạn đã biết cách tôi làm, còn bạn thì sao‽ 🙂
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. 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 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: bản minh hoạ và mã.
- @jhvanderschee bằng một nút: Codepen.