Tổng quan cơ bản về cách tạo một thành phần nút chia tách dễ tiếp cận.
Trong bài đăng này, tôi muốn chia sẻ cách tạo nút phân tách . Xem 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 chia là các nút che giấu một nút chính và một danh sách các nút bổ sung. Chúng hữu ích để hiển thị một hành động phổ biến trong khi lồng phụ kiện, ít được sử dụng hơn hành động cho đến khi cần thiết. Nút phân tách có thể đóng vai trò quan trọng trong việc hỗ trợ một thiết kế bận rộn mang lại cảm giác tối giản. Nút phân tách nâng cao thậm chí có thể ghi nhớ thao tác gần đây nhất của người dùng và quảng bá ứng dụng đó ở vị trí chính.
Bạn có thể tìm thấy một nút chia tách thông thường trong ứng dụng email. Hành động chính được gửi, nhưng có lẽ bạn có thể gửi sau hoặc lưu thư nháp:
Khu vực hành động được chia sẻ rất đẹp vì người dùng không cần nhìn quanh. Chúng hãy lưu ý rằng các thao tác thiết yếu đối với email nằm trong nút phân tách.
Phụ tùng
Hãy phân tích các phần thiết yếu của nút phân tách trước khi thảo luận về hoạt động điều phối tổng thể và trải nghiệm người dùng cuối cùng. Khả năng hỗ trợ tiếp cận của VisBug công cụ kiểm tra được sử dụng ở đây để hiển thị chế độ xem vĩ mô của thành phần, hiển thị các khía cạnh của HTML, kiểu và khả năng tiếp cận cho từng phần chính.
Vùng chứa nút chia tách cấp cao nhất
Thành phần cấp cao nhất là một hộp linh hoạt cùng dòng với một lớp
gui-split-button
, chứa hành động chính
và .gui-popup-button
.
Nút hành động chính
<button>
có thể nhìn thấy và có thể làm tâm điểm ban đầu nằm vừa trong vùng chứa có
hai hình dạng góc phù hợp cho
lấy nét,
di chuột và
lượt tương tác đang hoạt động với
xuất hiện có trong .gui-split-button
.
Nút bật tắt cửa sổ bật lên
"Nút bật lên" support dành cho việc kích hoạt và ám chỉ danh sách
nút phụ. Xin lưu ý rằng mã này không phải là <button>
và không thể làm tâm điểm. Tuy nhiên,
đó là neo định vị cho .gui-popup
và máy chủ lưu trữ cho :focus-within
được sử dụng
để hiển thị cửa sổ bật lên.
Thẻ bật lên
Đây là một thẻ con nổi trên thẻ neo vào phần neo
.gui-popup-button
, theo vị trí tuyệt đối và
gói danh sách nút theo đúng ngữ nghĩa.
(Các) hành động phụ
<button>
có thể làm tâm điểm có cỡ chữ nhỏ hơn một chút so với chính
nút hành động có một biểu tượng và
tạo kiểu cho nút chính.
Thuộc tính tuỳ chỉnh
Các biến sau đây hỗ trợ tạo ra sự hài hoà màu sắc và là vị trí trung tâm để sửa đổi các giá trị được sử dụng trong toàn bộ thành phần.
@custom-media --motionOK (prefers-reduced-motion: no-preference);
@custom-media --dark (prefers-color-scheme: dark);
@custom-media --light (prefers-color-scheme: light);
.gui-split-button {
--theme: hsl(220 75% 50%);
--theme-hover: hsl(220 75% 45%);
--theme-active: hsl(220 75% 40%);
--theme-text: hsl(220 75% 25%);
--theme-border: hsl(220 50% 75%);
--ontheme: hsl(220 90% 98%);
--popupbg: hsl(220 0% 100%);
--border: 1px solid var(--theme-border);
--radius: 6px;
--in-speed: 50ms;
--out-speed: 300ms;
@media (--dark) {
--theme: hsl(220 50% 60%);
--theme-hover: hsl(220 50% 65%);
--theme-active: hsl(220 75% 70%);
--theme-text: hsl(220 10% 85%);
--theme-border: hsl(220 20% 70%);
--ontheme: hsl(220 90% 5%);
--popupbg: hsl(220 10% 30%);
}
}
Bố cục và màu sắc
Markup (note: đây là tên ứng dụng)
Phần tử bắt đầu dưới dạng <div>
có tên lớp tuỳ chỉnh.
<div class="gui-split-button"></div>
Thêm nút chính và các phần tử .gui-popup-button
.
<div class="gui-split-button">
<button>Send</button>
<span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions"></span>
</div>
Hãy lưu ý các thuộc tính aria aria-haspopup
và aria-expanded
. Những gợi ý này
quan trọng để trình đọc màn hình nhận biết được tính năng
và trạng thái chia tách
trải nghiệm nút. Thuộc tính title
rất hữu ích cho mọi người.
Thêm biểu tượng <svg>
và phần tử vùng chứa .gui-popup
.
<div class="gui-split-button">
<button>Send</button>
<span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
<svg aria-hidden="true" viewBox="0 0 20 20">
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
</svg>
<ul class="gui-popup"></ul>
</span>
</div>
Để có vị trí đặt cửa sổ bật lên đơn giản, .gui-popup
là phần tử con của nút
mở rộng nó. Lợi ích duy nhất mà chiến lược này mang lại là .gui-split-button
vùng chứa không thể sử dụng overflow: hidden
, vì chế độ này sẽ cắt cửa sổ bật lên
trình bày một cách trực quan.
Một <ul>
chứa <li><button>
nội dung sẽ tự thông báo là "nút
danh sách" vào trình đọc màn hình, chính xác là giao diện được trình bày.
<div class="gui-split-button">
<button>Send</button>
<span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
<svg aria-hidden="true" viewBox="0 0 20 20">
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
</svg>
<ul class="gui-popup">
<li>
<button>Schedule for later</button>
</li>
<li>
<button>Delete</button>
</li>
<li>
<button>Save draft</button>
</li>
</ul>
</span>
</div>
Để tinh tế và tạo sự thú vị với màu sắc, tôi đã thêm biểu tượng vào các nút phụ tại https://heroicons.com. Biểu tượng là không bắt buộc cho cả hai nút chính và nút phụ.
<div class="gui-split-button">
<button>Send</button>
<span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
<svg aria-hidden="true" viewBox="0 0 20 20">
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
</svg>
<ul class="gui-popup">
<li><button>
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
Schedule for later
</button></li>
<li><button>
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
Delete
</button></li>
<li><button>
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z" />
</svg>
Save draft
</button></li>
</ul>
</span>
</div>
Kiểu
Khi đã có HTML và nội dung, các kiểu sẽ sẵn sàng để cung cấp màu sắc và bố cục.
Tạo kiểu cho vùng chứa nút phân tách
Loại hiển thị inline-flex
phù hợp với thành phần gói này vì nó
phải vừa với các nút, thao tác hoặc phần tử phân tách khác.
.gui-split-button {
display: inline-flex;
border-radius: var(--radius);
background: var(--theme);
color: var(--ontheme);
fill: var(--ontheme);
touch-action: manipulation;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
Kiểu <button>
Các nút rất hữu ích trong việc nguỵ trang số lượng mã cần thiết. Bạn có thể cần huỷ hoặc thay thế các kiểu mặc định của trình duyệt, nhưng bạn cũng sẽ cần thực thi một số tính kế thừa, thêm trạng thái tương tác cũng như điều chỉnh cho phù hợp với các lựa chọn ưu tiên của người dùng và loại đầu vào. Kiểu nút sẽ tăng lên nhanh chóng.
Các nút này khác với các nút thông thường vì chúng dùng chung một nền có phần tử mẹ. Thông thường, một nút sở hữu màu nền và màu văn bản. Tuy nhiên, những chủ đề này chia sẻ và chỉ áp dụng kiến thức nền tảng của riêng họ về hoạt động tương tác.
.gui-split-button button {
cursor: pointer;
appearance: none;
background: none;
border: none;
display: inline-flex;
align-items: center;
gap: 1ch;
white-space: nowrap;
font-family: inherit;
font-size: inherit;
font-weight: 500;
padding-block: 1.25ch;
padding-inline: 2.5ch;
color: var(--ontheme);
outline-color: var(--theme);
outline-offset: -5px;
}
Thêm trạng thái tương tác qua một vài CSS lớp giả và sử dụng phương pháp so khớp thuộc tính tuỳ chỉnh cho trạng thái:
.gui-split-button button {
…
&:is(:hover, :focus-visible) {
background: var(--theme-hover);
color: var(--ontheme);
& > svg {
stroke: currentColor;
fill: none;
}
}
&:active {
background: var(--theme-active);
}
}
Nút chính cần một vài kiểu đặc biệt để hoàn tất hiệu ứng thiết kế:
.gui-split-button > button {
border-end-start-radius: var(--radius);
border-start-start-radius: var(--radius);
& > svg {
fill: none;
stroke: var(--ontheme);
}
}
Cuối cùng, để tinh tế hơn, nút và biểu tượng giao diện sáng sẽ có bóng:
.gui-split-button {
@media (--light) {
& > button,
& button:is(:focus-visible, :hover) {
text-shadow: 0 1px 0 var(--theme-active);
}
& > .gui-popup-button > svg,
& button:is(:focus-visible, :hover) > svg {
filter: drop-shadow(0 1px 0 var(--theme-active));
}
}
}
Một nút lớn đã chú ý đến các tương tác vi mô và các chi tiết nhỏ.
Ghi chú về :focus-visible
Hãy lưu ý cách các kiểu nút sử dụng :focus-visible
thay vì :focus
. :focus
là một điểm nhấn quan trọng để tạo ra một giao diện người dùng
dễ tiếp cận nhưng cũng có
suy thoái: đây là điều không thông minh về việc người dùng có cần xem ứng dụng hoặc
không, nó sẽ áp dụng cho bất kỳ tiêu điểm nào.
Video dưới đây cố gắng chia nhỏ tương tác vi mô này để trình bày cách
:focus-visible
là giải pháp thay thế thông minh.
Tạo kiểu cho nút bật lên
Hộp linh hoạt 4ch
dùng để căn giữa biểu tượng và cố định danh sách nút bật lên. Thích
nút chính, nút này trong suốt cho đến khi được di chuột hoặc tương tác
và kéo giãn để lấp đầy.
.gui-popup-button {
inline-size: 4ch;
cursor: pointer;
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
border-inline-start: var(--border);
border-start-end-radius: var(--radius);
border-end-end-radius: var(--radius);
}
Tạo lớp trong các trạng thái di chuột, lấy tiêu điểm và đang hoạt động bằng CSS
Lồng ghép và
Bộ chọn chức năng :is()
:
.gui-popup-button {
…
&:is(:hover,:focus-within) {
background: var(--theme-hover);
}
/* fixes iOS trying to be helpful */
&:focus {
outline: none;
}
&:active {
background: var(--theme-active);
}
}
Các kiểu này là nội dung hấp dẫn chính để hiển thị và ẩn cửa sổ bật lên. Khi
.gui-popup-button
có focus
trên bất kỳ phần tử con nào, đặt opacity
, vị trí
và pointer-events
, trên biểu tượng và cửa sổ bật lên.
.gui-popup-button {
…
&:focus-within {
& > svg {
transition-duration: var(--in-speed);
transform: rotateZ(.5turn);
}
& > .gui-popup {
transition-duration: var(--in-speed);
opacity: 1;
transform: translateY(0);
pointer-events: auto;
}
}
}
Sau khi hoàn tất các kiểu vào và ra, bước cuối cùng là có điều kiện biến đổi hiệu ứng chuyển đổi tuỳ thuộc vào lựa chọn ưu tiên về chuyển động của người dùng:
.gui-popup-button {
…
@media (--motionOK) {
& > svg {
transition: transform var(--out-speed) ease;
}
& > .gui-popup {
transform: translateY(5px);
transition:
opacity var(--out-speed) ease,
transform var(--out-speed) ease;
}
}
}
Chú ý đến mã sẽ nhận thấy độ mờ vẫn được chuyển đổi cho người dùng những người thích chuyển động ít hơn.
Tạo kiểu cửa sổ bật lên
Phần tử .gui-popup
là một danh sách nút thẻ nổi sử dụng các thuộc tính tuỳ chỉnh
và đơn vị tương đối nhỏ hơn một cách tinh tế, được kết hợp tương tác với
và trên thương hiệu
bằng cách sử dụng màu sắc. Lưu ý rằng các biểu tượng có độ tương phản thấp hơn
mỏng hơn và bóng có pha chút màu xanh thương hiệu. Giống như với các nút,
những chi tiết nhỏ này xuất hiện trong giao diện người dùng và trải nghiệm người dùng.
.gui-popup {
--shadow: 220 70% 15%;
--shadow-strength: 1%;
opacity: 0;
pointer-events: none;
position: absolute;
bottom: 80%;
left: -1.5ch;
list-style-type: none;
background: var(--popupbg);
color: var(--theme-text);
padding-inline: 0;
padding-block: .5ch;
border-radius: var(--radius);
overflow: hidden;
display: flex;
flex-direction: column;
font-size: .9em;
transition: opacity var(--out-speed) ease;
box-shadow:
0 -2px 5px 0 hsl(var(--shadow) / calc(var(--shadow-strength) + 5%)),
0 1px 1px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 10%)),
0 2px 2px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 12%)),
0 5px 5px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 13%)),
0 9px 9px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 14%)),
0 16px 16px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 20%))
;
}
Các biểu tượng và nút có màu sắc của thương hiệu để tạo kiểu đẹp mắt trong mỗi tối và thẻ theo chủ đề sáng:
.gui-popup {
…
& svg {
fill: var(--popupbg);
stroke: var(--theme);
@media (prefers-color-scheme: dark) {
stroke: var(--theme-border);
}
}
& button {
color: var(--theme-text);
width: 100%;
}
}
Cửa sổ bật lên trong giao diện tối có văn bản và bóng biểu tượng bổ sung, cùng với một chút bóng hộp đậm:
.gui-popup {
…
@media (--dark) {
--shadow-strength: 5%;
--shadow: 220 3% 2%;
& button:not(:focus-visible, :hover) {
text-shadow: 0 1px 0 var(--ontheme);
}
& button:not(:focus-visible, :hover) > svg {
filter: drop-shadow(0 1px 0 var(--ontheme));
}
}
}
Kiểu biểu tượng <svg>
chung
Tất cả biểu tượng đều có kích thước tương đối với nút font-size
mà chúng được dùng
sử dụng đơn vị ch
làm đơn vị
inline-size
. Mỗi đường dẫn cũng được cung cấp một số kiểu để giúp phác thảo các biểu tượng mềm và
mượt mà.
.gui-split-button svg {
inline-size: 2ch;
box-sizing: content-box;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 2px;
}
Bố cục từ phải sang trái
Thuộc tính logic thực hiện tất cả các công việc phức tạp.
Dưới đây là danh sách các thuộc tính logic được sử dụng:
– display: inline-flex
tạo một phần tử linh hoạt cùng dòng.
- padding-block
và padding-inline
theo cặp, thay vì padding
nói ngắn gọn, tận dụng lợi ích của khoảng đệm cho các cạnh logic.
– border-end-start-radius
và
bạn bè sẽ
các góc tròn dựa trên hướng tài liệu.
– inline-size
thay vì width
đảm bảo kích thước không gắn liền với kích thước thực.
– border-inline-start
thêm một đường viền vào phần đầu, đường viền này có thể ở bên phải hoặc bên trái tuỳ theo hướng của tập lệnh.
JavaScript
Hầu như toàn bộ JavaScript sau đây đều dùng để tăng cường khả năng hỗ trợ tiếp cận. Hai trong số các thư viện trợ giúp được dùng để giúp các tác vụ trở nên dễ dàng hơn một chút. BlingBlingJS được sử dụng để ngắn gọn Truy vấn DOM và thiết lập trình nghe sự kiện dễ dàng, trong khi roving-ux giúp người dùng dễ tiếp cận hoạt động tương tác với bàn phím và tay điều khiển trò chơi cho cửa sổ bật lên.
import $ from 'blingblingjs'
import {rovingIndex} from 'roving-ux'
const splitButtons = $('.gui-split-button')
const popupButtons = $('.gui-popup-button')
Sau khi nhập các thư viện ở trên cũng như các thành phần được chọn và lưu vào biến số lớn, nên bạn chỉ cần nâng cấp một vài hàm là hoàn tất.
Chỉ số lưu động
Khi bàn phím hoặc trình đọc màn hình lấy tiêu điểm .gui-popup-button
, chúng ta muốn
chuyển tiếp tiêu điểm vào nút đầu tiên (hoặc được lấy tiêu điểm gần đây nhất) trong
.gui-popup
Thư viện giúp chúng ta thực hiện điều này bằng element
và target
tham số.
popupButtons.forEach(element =>
rovingIndex({
element,
target: 'button',
}))
Phần tử hiện chuyển tiêu điểm đến phần tử con <button>
mục tiêu và cho phép
điều hướng bằng phím mũi tên chuẩn để duyệt qua các lựa chọn.
Bật/tắt aria-expanded
Mặc dù rõ ràng là một cửa sổ bật lên đang hiển thị và đang ẩn, nhưng trình đọc màn hình không chỉ cần chỉ dẫn bằng hình ảnh. JavaScript được dùng ở đây để bổ trợ cho hoạt động tương tác :focus-within
do CSS điều hướng bằng cách bật/tắt một thuộc tính phù hợp của trình đọc màn hình.
popupButtons.on('focusin', e => {
e.currentTarget.setAttribute('aria-expanded', true)
})
popupButtons.on('focusout', e => {
e.currentTarget.setAttribute('aria-expanded', false)
})
Bật khoá Escape
Sự tập trung của người dùng được cố tình đưa vào một cái bẫy, do đó chúng ta cần
đưa ra cách rời đi. Cách phổ biến nhất là cho phép sử dụng khoá Escape
.
Để làm việc này, hãy chú ý đến các lần nhấn phím trên nút bật lên, vì mọi sự kiện bàn phím trên
con sẽ được bong bóng đến gần cha mẹ này.
popupButtons.on('keyup', e => {
if (e.code === 'Escape')
e.target.blur()
})
Nếu nút bật lên thấy bất kỳ lần nhấn phím Escape
nào, thì nút này sẽ xoá tiêu điểm khỏi chính nút đó
thông qua tính năng
blur()
.
Số lần nhấp vào nút phân tách
Cuối cùng, nếu người dùng nhấp, nhấn hoặc bàn phím tương tác với các nút,
ứng dụng cần thực hiện hành động thích hợp. Tính năng bong bóng trò chuyện được sử dụng
ở đây một lần nữa, nhưng lần này là trên vùng chứa .gui-split-button
để bắt
số lượt nhấp từ một cửa sổ bật lên con hoặc hành động chính.
splitButtons.on('click', event => {
if (event.target.nodeName !== 'BUTTON') return
console.info(event.target.innerText)
})
Kết luận
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!