Thông tin 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ẻ suy nghĩ về cách xây dựng các thành phần chuyển đổi. Dùng thử bản minh hoạ.
Nếu bạn thích video, đây là phiên bản YouTube của bài đăng này:
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 chức năng, ưu điểm là không cần CSS hoặc JavaScript để có đầy đủ chức năng và dễ truy cập. Khi tải CSS, bạn sẽ hỗ trợ các ngôn ngữ viết từ phải sang trái, độ dọc, ảnh động, v.v. Khi tải JavaScript, bạn có thể kéo và xem nút chuyển.
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à 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 toàn bộ thành phần con và các điểm truy cập để tuỳ chỉnh tập trung.
Track
Độ 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 làm nổi bật 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 tình trạng lặp lại, truy vấn nội dung nghe nhìn của người dùng giảm mức độ ưu tiên chuyển động có thể được đưa 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ố 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 một <label>
, gói mối quan hệ của chúng để tránh sự không rõ ràng về hộp đánh dấu và nhãn liên kết, đồng thời cho phép người dùng tương tác với nhãn để chuyển đổi 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 một API và state. Trình duyệt quản lý thuộc tính checked
và các sự kiện đầu vào, chẳng hạn như oninput
và onchanged
.
Bố cục
Flexbox, grid và các 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. Chúng tập trung vào các giá trị, đặt tên cho các phép tính hoặc khu vực không rõ ràng, đồng thời kích hoạ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 của nút chuyển này là Linh hoạt. Lớp .gui-switch
chứa các thuộc tính tuỳ chỉnh riêng tư và công khai mà trẻ dùng để tính toán bố cục.
.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 một 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>
Track
Đầu vào hộp đánh dấu được tạo kiểu là một kênh chuyển đổi bằng cách xoá appearance: checkbox
thông thường và cung cấp kích thước của chính hộp đánh dấu đó:
.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 này cũng tạo một khu vực theo dõi dạng lưới từng ô đơn để ngón cái xác nhận.
Thumb
Kiểu appearance: none
cũng xoá dấu kiểm hình ảnh do trình duyệt cung cấp. Thành phần này sử dụng một phần tử giả và giả lớp :checked
trên dữ liệu đầu vào để thay thế chỉ báo trực quan này.
Ngón cái là một phần tử con giả lập được đính kèm vào input[type="checkbox"]
và xếp chồng lên đầu kênh thay vì bên dưới bằng cách xác nhận khu vực 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 chuyển đổi linh hoạt thích ứng với bảng phối màu, ngôn ngữ từ phải sang trái cũng như các lựa chọn ưu tiên về chuyển động.
Kiểu tương tác chạm
Trên thiết bị di động, trình duyệt thêm các tính năng làm nổi bật thao tác nhấn và lựa 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 hình ảnh mà việc chuyển đổi này cần đến. 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à phản hồi tương tác hình ảnh có giá trị. 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 đó.
Track
Kiểu của phần tử này chủ yếu dựa trên 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 tầng.
.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 đến từ 4 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 mọi 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 thay đổi về vị trí di chuột và vị trí ngón cái. Lựa chọn ưu tiên của người dùng cũng được 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 tay cái
Các thuộc tính tuỳ chỉnh cung cấp một cơ chế nguồn duy nhất để định vị thumb trong bản nhạc. Hiện tại, chúng ta có các kích thước thumb và track mà chúng ta sẽ sử dụng trong các phép tính để giữ cho thumb đúng cách và ở giữa các track: 0%
và 100%
.
Phần tử input
sở hữu biến vị trí --thumb-position
, còn 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));
}
Chúng tôi hiện miễn phí thay đổi --thumb-position
từ CSS và các lớp giả lập đã cung cấp trong các phần tử hộp đánh dấu. Vì trước đó chúng ta đã đặt transition: transform
var(--thumb-transition-duration) ease
có điều kiện cho phần tử này, nên các 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ự phối hợp tách biệt này làm tốt lắm. Phần tử thumb chỉ liên quan đến một kiểu, vị trí translateX
. Dữ liệu đầu vào có thể quản lý mọi độ phức tạp và phép tính.
Dọc
Bạn đã hỗ trợ xong bằng một lớp đối tượng sửa đổi -vertical
. Lớp này sẽ thêm chế độ xoay với các phép biến đổi CSS cho phần tử input
.
Tuy nhiên, 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 mất 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 để nút dọc hoạt độ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 là Elad Schecter và tôi đã tạo nguyên mẫu cùng nhau một 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ý 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 điều này vì không có sự biến đổi thuộc tính logic trong CSS và có thể sẽ không bao giờ có như vậy. 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 theo logic tuỳ chỉnh của chúng tôi cho các phép biến đổi logic. Tôi đã sử dụng cùng một kỹ thuật này trong lần chuyển đổi 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;
}
}
Ban đầu, thuộc tính tuỳ chỉnh có tên là --isLTR
chứa 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ả lập 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 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ờ, chế độ xoay của nút chuyển dọc sẽ tài khoản cho vị trí bên đối diện theo yêu cầu của bố cục từ phải sang trái.
Bạn cũng cần cập nhật translateX
biến đổi trên phần tử giả thumb để đáp ứng 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 có tác dụng để giải quyết mọi nhu cầu liên quan đến một khái niệm như biến đổi CSS logic, nhưng có một số nguyên tắc DRY (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 tất nếu không xử lý nhiều trạng thái có thể ở trong: :checked
, :disabled
, :indeterminate
và :hover
. :focus
được giữ nguyên một cách có chủ đích, chỉ điều chỉnh phần bù trừ; vòng điều chỉnh tiêu điểm trông rất đẹp trên Firefox và Safari:
Đã đánh dấu
<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 đại diện cho trạng thái on
. Ở trạng thái này, nền "theo dõi" đầu vào được đặt thành màu hoạt động và vị trí ngón cái được đặt là "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ó giao diện khác nhau mà còn khiến cho phần tử không thể thay đổi được.Không thể thay đổi tương tác trong trình duyệt, nhưng các trạng thái trực quan 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 rất khó vì cần có giao diện tối và sáng ở cả trạng thái tắt và trạng thái đã đánh dấu. Tôi đã có phong cách chọn những kiểu tối thiểu cho các trạng thái này để giảm bớt gánh nặng duy trì việc kết hợp các kiểu.
Không xác định
Trạng thái thường 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 tiểu bang thú vị, hấp dẫn và đơn giản. Xin lưu ý rằng các trạng thái boolean có thể lén lút giữa các trạng thái.
Việc đặt hộp đánh dấu thành không xác định là rất khó, 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>
Đối với tôi, trạng thái này đơn giản và hấp dẫn, nên tôi nên đặt vị trí 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, đồng thời cung cấp hướng dẫn về 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 ngón cái bằng một vòng bán trong suốt khi di chuột lên nhãn hoặc mục nhập. Sau đó, ảnh động di chuột này cung cấp hướng về phần tử thumb tương tác.
Hiệu ứng "đánh dấu" được thực hiện bằng box-shadow
. Khi di chuột, của một phương thức nhập không bị tắt, hãy tăng kích thước của --highlight-size
. Nếu người dùng ổn với chuyển động, chúng ta sẽ chuyển đổi box-shadow
và thấy nó tăng lên. Nếu người dùng không hài lòng với chuyển động, phần đá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 chuyển đổi có thể tạo ra cảm giác lạ lẫm khi cố gắng mô phỏng giao diện thực, đặc biệt là loại này với một vòng tròn bên trong một kênh. iOS làm được điều này với công tắc, bạn có thể kéo chúng từ bên này sang bên kia và thật sự hài lòng khi có tuỳ 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ể cảm thấy không hoạt động nếu bạn đã thử dùng cử chỉ kéo mà không có gì xảy ra.
Hình ngón tay cái có thể kéo
Phần tử giả ngón tay cái nhận vị trí từ var(--thumb-position)
trong phạm vi .gui-switch > input
, JavaScript có thể cung cấp một giá trị kiểu cùng dòng trên dữ liệu đầu vào để tự động cập nhật vị trí ngón cái, khiến vị trí đó trông như tuân theo cử chỉ con trỏ. Khi con trỏ được thả ra, hãy xoá các kiểu cùng dòng và xác định xem thao tác kéo đã gần hơn hay đang bật bằng cách sử dụng thuộc tính tuỳ chỉnh --thumb-position
. Đây là xương sống của giải pháp; các sự kiện con trỏ
theo dõi vị trí con trỏ một cách có điều kiện để sửa đổi các thuộc tính tuỳ chỉnh của 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 phải 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 một nhãn để chuyển đổi đầu vào. JavaScript của chúng ta không nên thêm tính năng đánh đổi các tính năng hiện có.
touch-action
Kéo là một cử chỉ tuỳ chỉnh, khiến đây là một cử chỉ tuyệt vời để hưởng các lợi ích của touch-action
. Trong trường hợp nút chuyển này, tập lệnh của chúng tôi sẽ xử lý cử chỉ ngang, 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 cần xử lý những cử chỉ nào 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 cần 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 luồng chuyển đổi nà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ỉ theo chiều 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 giá trị đầu vào và cuộn trang, nhưng con trỏ chiều ngang được xử lý tuỳ chỉnh.
Tiện ích kiểu giá trị pixel
Khi thiết lập và trong quá trình kéo, 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 do có một thuộc tính CSS. Tham số này được dùng trong tập lệnh thiết lập như getStyle(checkbox, 'padding-left')
này.
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ục tiêu). Khá rõ ràng là JavaScript có thể đọc rất nhiều giá trị của các phần tử, thậm chí cả từ các phần tử giả.
dragging
Đây là khoảnh khắc cốt lõi đối với logic kéo và có một vài điều bạn cần lưu ý từ trình xử lý sự kiện của 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`)
}
Hình ảnh chính của tập lệnh là state.activethumb
, vòng tròn nhỏ mà tập lệnh này đang đặt cùng với một con trỏ. Đối tượng switches
là một Map()
, trong đó các khoá là .gui-switch
và 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ả. Thao tác từ phải sang trái được xử lý bằng cùng một thuộc tính tuỳ chỉnh mà CSS là --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ợ RTL. event.offsetX
cũng có giá trị vì chứa giá trị delta hữu ích cho việc định vị thumb.
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 tuỳ chỉnh mà phần tử thumb sử dụng. Nếu không, việc chỉ định giá trị này sẽ chuyển đổi theo thời gian, nhưng sự kiện con trỏ trước đó đã tạm thời đặt --thumb-transition-duration
thành 0s
, loại bỏ những gì có thể là một hoạt động tương tác chậm.
dragEnd
Để người dùng có thể kéo ra ngoài nút chuyển và thả ra, bạn cần đăng ký một sự kiện cửa sổ toàn cầu:
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ó thể tự do kéo thoải mái và có giao diện đủ thông minh để giải quyết vấn đề này. Việc chuyển đổi này không mất nhiều thời gian, nhưng cần xem xét cẩn thận 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()
}
Đã hoàn tất tương tác với phần tử, đã đến lúc đặt thuộc tính đầu vào đã đánh dấu 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 (được gọi bởi dragEnd
) sẽ xác định vị trí dòng thumb nằm trong giới hạn của kênh và trả về giá trị true nếu dòng này bằng hoặc hơn 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ĩ bổ sung
Thao tác kéo đã phát sinh một chút phần nợ mã do cấu trúc HTML ban đầu được chọn, đáng chú ý là việc gói dữ liệu đầu vào trong một nhãn. Nhãn, là phần tử mẹ, sẽ nhận được các lượt tương tác nhấp sau khi nhập. Khi kết thúc sự kiện dragEnd
, bạn có thể nhận thấy padRelease()
là một hàm có âm thanh 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ượt nhấp sau này (vì nhãn này sẽ bỏ đánh dấu hoặc kiểm tra 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ể cân nhắc việ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 xử lý hành vi tích hợp sẵn.
Tôi không thích viết loại JavaScript này nhất, tôi không muốn quản lý 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 công tắc nhỏ này đã trở thành thành phần hiệu quả nhất trong tất cả Thử thách GUI từ trước đến nay! Giờ bạn đã biết tôi làm được như thế nào, bạn sẽ làm thế nào 🙂
Hãy đa dạng hoá phương pháp tiếp cận của chúng ta 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ạ, đường liên kết tweet me và tôi sẽ thêm bản phối lại đó 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.