說明如何建構回應式及無障礙切換元件的基本總覽。
在這篇文章中,我想分享如何建構切換元件的想法。 立即試用。
如果您喜歡看影片,請參考這篇文章的 YouTube 版本:
總覽
切換函式與核取方塊類似 但明確代表「開啟」和「關閉」狀態的布林值
此示範內容的大多數使用 <input type="checkbox" role="switch">
它的優點是不需要 CSS 或 JavaScript
且功能完全可用載入 CSS 支援從右至左
語言、產業、動畫等載入 JavaScript 可立即切換
可拖曳和實體
自訂屬性
下列變數代表切換鈕的各個部分,以及
只要設定成「自動重新啟動」
和「在主機維護期間」選項即可做為頂層類別,.gui-switch
包含使用的自訂屬性
整個元件子項,以及集中化的進入點
。
追蹤
長度 (--track-size
)、邊框間距和兩種顏色:
.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%);
}
}
縮圖
大小、背景顏色和互動的醒目顯示顏色:
.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%);
}
}
減少動態效果
降低動作偏好設定,以新增明確的別名並減少重複情形 媒體查詢可透過 PostCSS 加進自訂屬性 外掛程式以這個草稿 媒體查詢的規格 5:
@custom-media --motionOK (prefers-reduced-motion: no-preference);
標記
我選擇用<input type="checkbox" role="switch">
元素來包裝
<label>
,合併關係,避免使用核取方塊和標籤關聯
同時讓使用者能與標籤互動
來切換輸入鈕
<label for="switch" class="gui-switch">
Label text
<input type="checkbox" role="switch" id="switch">
</label>
<input type="checkbox">
預先建構了
API
和狀態。
會管理
checked
敬上
屬性和 input
事件
例如 oninput
和 onchanged
。
版面配置
Flexbox、 Grid 和 custom 資源 維持元件的樣式能以集中方式處理值、提供名稱 以便進行模稜兩可的計算或區域,並啟用小型自訂屬性 可輕鬆自訂元件的 API。
.gui-switch
切換按鈕的頂層版面配置為 Flexbox。.gui-switch
類別包含
子項用來計算其依附元件的私有和公開自訂屬性
版面配置
.gui-switch {
display: flex;
align-items: center;
gap: 2ch;
justify-content: space-between;
}
擴充和修改 Flexbox 版面配置,就如同變更任何 Flexbox 版面配置。
例如,將標籤放在切換鈕上方或下方,或是
flex-direction
:
<label for="light-switch" class="gui-switch" style="flex-direction: column">
Default
<input type="checkbox" role="switch" id="light-switch">
</label>
追蹤
核取方塊輸入會移除一般開關樣式,並將其樣式設為開關軌
appearance: checkbox
,改為提供其自己的大小:
.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;
}
同時會建立一個賽道以一個格狀格線軌跡區域建立路徑 聲明。
縮圖
樣式 appearance: none
也會移除
。此元件會使用
pseudo-element 和 :checked
對輸入值套用虛擬類別
會替換這個視覺指標
拇指是附加至 input[type="checkbox"]
的虛擬元素子項,
堆疊在曲目上方,而非顯示在曲目下方,聲明格線區域
track
:
.gui-switch > input::before {
content: "";
grid-area: track;
inline-size: var(--thumb-size);
block-size: var(--thumb-size);
}
樣式
自訂屬性提供可配合顏色調整的多功能切換元件 語言配置、由右至左的語言和動作偏好設定。
觸控互動樣式
在行動裝置上,瀏覽器會為標籤和
輸入內容這些做法對影片樣式和視覺互動回饋造成負面影響
並需要這個開關只要幾行 CSS,我就能移除這些效果
自己的 cursor: pointer
樣式:
.gui-switch {
cursor: pointer;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
建議不見得移除這些樣式,因為它們具有寶貴的視覺元素 互動意見如果要移除自訂替代文字,請務必提供這項資訊。
追蹤
這項元素的樣式主要是關於其形狀和顏色,可供存取
透過父項「.gui-switch
」
「cascade」。
.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);
}
切換測試群組的各種自訂選項如下:
自訂屬性已新增 border: none
,因為 appearance: none
不會
取消勾選所有瀏覽器核取方塊的邊框。
縮圖
拇指元素已位於右側 track
,但需要圓形樣式:
.gui-switch > input::before {
background: var(--thumb-color);
border-radius: 50%;
}
互動
使用自訂屬性來為會懸停顯示的互動做好準備 高亮度和拇指位置改變。使用者偏好設定 轉換前檢查功能 動態或懸停醒目顯示樣式
.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;
}}
}
喜歡位置
自訂屬性提供用來將拇指置於唯一位置的來源機制
音軌。我們會用到的軌道和拇指大小
以便讓拇指在軌道中正確偏移,並在軌道中保持:
《0%
》和《100%
》。
input
元素擁有位置變數 --thumb-position
,且指標皆擁有
虛擬元素會用做 translateX
位置:
.gui-switch > input {
--thumb-position: 0%;
}
.gui-switch > input::before {
transform: translateX(var(--thumb-position));
}
我們現在可免費變更 CSS 和虛擬類別中的 --thumb-position
核取方塊元素中。由於我們先前已有條件地在這個元素上設定 transition: transform
var(--thumb-transition-duration) ease
,因此這些變更會:
變更後可能會動畫:
/* 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)
);
}
我認為這種分離自動化調度管理法的成效良好。thumb 元素為
只有一種樣式,也就是 translateX
位置。輸入內容可以管理
降低複雜程度與運算能力
產業
支援透過修飾符類別 -vertical
新增旋轉函式
CSS 會轉換為 input
元素。
不過,3D 旋轉元素不會改變元件的整體高度
這可能會捨棄區塊版面配置使用 --track-size
和
--track-padding
變數。計算
垂直按鈕會正常在版面配置中流動:
.gui-switch.-vertical {
min-block-size: calc(var(--track-size) + calc(var(--track-padding) * 2));
& > input {
transform: rotate(-90deg);
}
}
(RTL) 由右至左
CSS 好友 Elad Schecter 使用 CSS 轉換功能處理從右至左,並拼出側邊選單 切換單一語言 變數。之所以這麼做,是因為 CSS 中沒有邏輯屬性轉換 也可能永遠小艾想要使用自訂屬性值 反轉百分比,讓使用者能管理我們自訂的營業地點 邏輯轉換的邏輯我在這次切換機會也使用相同的技巧 結果非常好:
.gui-switch {
--isLTR: 1;
&:dir(rtl) {
--isLTR: -1;
}
}
名為 --isLTR
的自訂屬性一開始包含 1
的值,表示該屬性為
true
,因為預設版面配置是從左到右。接著使用 CSS
虛擬類別 :dir()
當元件位於從右到左的版面配置中時,值會設為 -1
。
在轉換的 calc()
內使用 --isLTR
,將 --isLTR
用於行動:
.gui-switch.-vertical > input {
transform: rotate(-90deg);
transform: rotate(calc(90deg * var(--isLTR) * -1));
}
現在,垂直切換鈕應採用相反方向的旋轉角度 都是由右至左的版面配置所設計
拇指虛擬元素上的 translateX
轉換也需要更新為
違反相反規定:
.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)
);
}
雖然這種方法無法解決所有需求,無法滿足邏輯 CSS 等概念 但具備一些特徵 DRY 原則 具體來說,您可以設計提示來解決業務工作
州
否則就無法使用內建 input[type="checkbox"]
完成
會處理其可能處於以下狀態的各種狀態::checked
、:disabled
、
:indeterminate
和:hover
。「:focus
」刻意單獨退出,其中
僅對其偏移量做出調整而 Firefox 上的對焦環看起來很不錯
Safari:
已勾選
<label for="switch-checked" class="gui-switch">
Default
<input type="checkbox" role="switch" id="switch-checked" checked="true">
</label>
這個狀態代表 on
狀態。在這個狀態下,輸入內容「track」
背景設為使用中的色彩,而拇指位置設為
。
.gui-switch > input:checked {
background: var(--track-color-active);
--thumb-position: calc((var(--track-size) - 100%) * var(--isLTR));
}
已停用
<label for="switch-disabled" class="gui-switch">
Default
<input type="checkbox" role="switch" id="switch-disabled" disabled="true">
</label>
:disabled
按鈕不僅外觀不同,而且應加上
元素 immutable.Interaction 的不變性是不受瀏覽器限制,但
由於使用 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%);
}}
}
}
這個狀態相當棘手,因為在停用和停用背景主題的情況下,必須使用深色和淺色主題 已勾選狀態。我很擅長為這些州選擇極簡風格,方便我 樣式組合的維護負擔
不確定
經常被遺忘的狀態是 :indeterminate
,表示核取方塊並非
已勾選或取消勾選。這個狀態充滿趣味,氣氛歡樂。不錯
請注意,布林狀態可能會在狀態之間出現突兀。
很難將核取方塊設定為「不確定」,只有 JavaScript 可以設定它:
<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>
既然如此,對我來說很親切且友善,所以我認為 切換鈕位於中間的位置:
.gui-switch > input:indeterminate {
--thumb-position: calc(
calc(calc(var(--track-size) / 2) - calc(var(--thumb-size) / 2))
* var(--isLTR)
);
}
懸停
懸停互動應為連線 UI 提供視覺支援功能 提供前往互動式 UI 的方向這個開關標明瞭 也就是在標籤或輸入項目懸停時,顯示半透明的圓圈。此懸停操作 接著提供互動拇指元素的方向。
「醒目顯示」box-shadow
的效果。懸停在非停用的輸入項目上,增加 --highlight-size
的大小。如果使用者覺得動作效果不錯,我們會轉換 box-shadow
並看到其變大,如果對動作效果不好,就會立即顯示醒目顯示內容:
.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
對我來說,切換介面可能會覺得要模擬的是 特別是在軌跡裡面有圓圈的這種例子。iOS 答對了 也可以左右拖曳,這種做法非常好用 可以選擇。相反地,如果拖曳手勢 而不做任何反應
可拖曳的拇指
縮圖虛擬元素會從 .gui-switch > input
取得位置
範圍為 var(--thumb-position)
,JavaScript 可以在以下位置提供內嵌樣式值:
即可動態更新指紋位置,讓它顯示跟隨
指標手勢。指標釋放後,移除內嵌樣式並
利用自訂屬性判斷拖曳位置是關閉還是開啟。
--thumb-position
。此為解決方案的骨幹;指標事件
有條件地追蹤指標位置,以修改 CSS 自訂屬性。
因為在顯示這段指令碼之前,元件就已能 100% 正常運作 但要維持既有的運作模式,需要花費不少心力,例如 按一下標籤即可切換輸入模式我們的 JavaScript 不應在 導致現有功能無法負擔
touch-action
拖曳是一種自訂手勢,非常適合用來
touch-action
福利。切換到橫向手勢時
交由指令碼處理,或是擷取到垂直切換的垂直手勢
變數。透過 touch-action
,我們可以告訴瀏覽器要處理哪些手勢
以便指令碼在不競爭的情況下處理手勢。
下列 CSS 會指示瀏覽器在指標手勢從 在這個切換軌中,處理垂直手勢、只針對橫向執行任何操作 對象:
.gui-switch > input {
touch-action: pan-y;
}
所需結果為水平手勢,且不會同時平移或捲動 頁面。指標可以從輸入內容中垂直捲動,並捲動 但橫向是自訂的
像素值樣式公用程式
在設定和拖曳期間,需要擷取各種計算的數字值
從元素擷取出來下列 JavaScript 函式會傳回計算出的像素值
某個 CSS 屬性傳回的值這會用於設定指令碼,就像這樣
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,
}
請注意 window.getComputedStyle()
如何接受第二個引數 (目標虛擬元素)。十分簡潔,JavaScript 可從元素讀取許多值,即使是虛擬元素也一樣。
dragging
這是拖曳邏輯的核心時刻,有不少值得注意之處 從函式事件處理常式呼叫:
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`)
}
指令碼英雄是 state.activethumb
,這個指令碼的小圓圈
以及指標位置與指標switches
物件是 Map()
,其中
索引鍵為 .gui-switch
,而值則是快取邊界和大小
有效率地執行指令碼由右至左皆以相同的自訂屬性處理
CSS 為 --isLTR
,且能用來反轉邏輯並繼續
支援 RTL 格式event.offsetX
也很有價值,而且包含差異值
以便設定指標位置。
state.activethumb.style.setProperty('--thumb-position', `${track + pos}px`)
這一行 CSS 是用來設定拇指元素使用的自訂屬性。這個
否則會隨著時間經過轉換,不過先前的指標
活動已將--thumb-transition-duration
暫時設為 0s
,正在移除內容
那樣會是很緩慢的互動
dragEnd
為了讓使用者能夠拖曳到靠近開關的位置再放開, 必須註冊的全域視窗事件:
window.addEventListener('pointerup', event => {
if (!state.activethumb) return
dragEnd(event)
})
我認為在確保使用者能自由拖曳或 相關介面的智慧功能已充分考量這一點沒什麼時間也沒關係 切換後,在開發階段需要審慎評估 上傳資料集之後,您可以運用 AutoML 自動完成部分資料準備工作
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()
}
與元素的互動已完成,該設定已勾選輸入項目
屬性並移除所有手勢事件。核取方塊的變更方式為
state.activethumb.checked = determineChecked()
。
determineChecked()
此函式由 dragEnd
呼叫,可決定拇指目前的位置
會在其軌跡的範圍內傳回 true,如果等於或超過
賽道一半的進度:
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
}
其他想法
由於初始 HTML 結構的關係,拖曳手勢造成一些程式碼債
主要來說是將輸入內容納入標籤中標籤,即父項
元素就會在輸入完成後收到點擊互動。結尾處
dragEnd
事件,您或許會注意到「padRelease()
」有奇怪的聲音
函式。
const padRelease = () => {
state.recentlyDragged = true
setTimeout(_ => {
state.recentlyDragged = false
}, 300)
}
由於標籤稍後會取消勾選 或檢查使用者所執行的互動
如果我再次進行這項作業,我「可能」會考慮用 JavaScript 調整 DOM 使用者體驗升級期間的另一個好處,就是要建立能處理標籤點擊 也不會與內建行為有所衝突
這種 JavaScript 是我最不喜歡寫入的,我不想管理 條件式事件泡泡:
const preventBubbles = event => {
if (state.recentlyDragged)
event.preventDefault() && event.stopPropagation()
}
結論
這個青少年換機組最終成了所有 GUI 挑戰 到目前為止!現在你知道我怎麼了,這樣會如何 🙂?
讓我們來體驗多元的方法,瞭解透過網路建立內容的所有方式。 建立示範、將 Twitter 推文連結,我們就會為您新增 前往下方的社群重混專區!
社群重混作品
- @KonstantinRouda,其中包含自訂元素:示範和程式碼。
- @jhvanderschee,其中包含 Codepen 按鈕。
資源
從以下網站找出 .gui-switch
原始碼:
GitHub。