Material Design Lite Switch 开关组件:从标记结构、配置类到源码实现
【免费下载链接】material-design-liteMaterial Design Components in HTML/CSS/JS项目地址: https://gitcode.com/gh_mirrors/ma/material-design-lite
MDL(Material Design Lite)的switch 开关组件是对原生 HTML<input type="checkbox">的视觉与交互增强:它把复选框改造成一条横向"轨道"加圆形"滑块"的形态,用"左侧/灰色表示关闭、右侧/主色表示开启"的直观隐喻承载二值状态。本文以 src/switch/README.md 为主线,完整讲解开关组件的标记结构、配置类、禁用态与涟漪效果,并结合 switch.js、_switch.scss 与单元测试 test/unit/switch.js 深入其升级机制与状态同步原理,读完即可在任意页面中落地一个可访问、可编程控制的开关组件。
组件定位:为什么用 Switch 取代部分 Checkbox
在 Material Design 的交互体系里,开关(switch)与复选框(checkbox)都属于"选择控件",但适用场景不同。开关更适合表达"即时生效、二元对立"的设置项——比如"声音开/关"、"Wi‑Fi 启用/停用",用户点击后状态立即切换;复选框则更多用于批量选择或提交后才生效的选项。
MDL 的 switch 组件具备以下特征(见 src/switch/README.md):
- 由标准
<input type="checkbox">增强而来,保持原生语义与键盘可达性; - 视觉上由"轨道 + 滑块"构成:off 时滑块居左且轨道为灰色,on 时滑块居右且轨道、滑块染上主题主色;
- 支持单个或成组使用,彼此独立选中/取消选中;
- 可附加ripple 点击涟漪效果;
- 支持初始或编程方式的disabled 禁用态。
<!-- 未开启的开关 --> <label for="switch-2" class="mdl-switch mdl-js-switch mdl-js-ripple-effect"> <input type="checkbox" id="switch-2" class="mdl-switch__input"> <span class="mdl-switch__label">关闭</span> </label> <!-- 已开启的开关(input 带 checked) --> <label for="switch-1" class="mdl-switch mdl-js-switch mdl-js-ripple-effect"> <input type="checkbox" id="switch-1" class="mdl-switch__input" checked> <span class="mdl-switch__label">开启</span> </label>上述两个完整可运行的示例分别对应仓库中的 src/switch/snippets/switch-off.html 与 src/switch/snippets/switch-on.html。
四步构建一个 MDL Switch
按 src/switch/README.md 给出的步骤,从零搭建开关组件只需四步。核心原则是:始终用<label>包裹原生 checkbox,并把 MDL 增强类挂在对应的标签、输入框和说明文字上,这样即使 JS 未加载,原生复选框依然可用(渐进增强)。
第 1 步:编写<label>元素,并设置for属性,其值等于将要包含的开关的id:
<label for="switch1"> ... </label>第 2 步:在 label 内部编写<input>元素,type为"checkbox",id与 label 的for保持一致:
<label for="switch1"> <input type="checkbox" id="switch1"> </label>第 3 步:在 checkbox 之后编写<span>元素,作为开关的文字说明(caption):
<label for="switch1"> <input type="checkbox" id="switch1"> <span>Sound off/on</span> </label>第 4 步:为 label、input、span 添加 MDL 类名(用空格分隔的多个类):
<label for="switch1" class="mdl-switch mdl-js-switch"> <input type="checkbox" id="switch1" class="mdl-switch__input"> <span class="mdl-switch__label">Sound off/on</span> </label>完成后组件即可直接使用。原文档还给出了带涟漪点击效果的完整示例:
<label for="switch1" class="mdl-switch mdl-js-switch mdl-js-ripple-effect"> <input type="checkbox" id="switch1" class="mdl-switch__input"> <span class="mdl-switch__label">Sound off/on</span> </label>标记要点
for/id配对不是装饰:它让点击 label 任意位置(包括说明文字)都能切换复选框,也保证屏幕阅读器能正确朗读;mdl-js-switch是升级触发器:组件注册后,MDL 的componentHandler会扫描 DOM 中带该类的元素并执行升级,向 label 内动态注入轨道、滑块等视觉节点(详见下文源码分析);- 不要忘记给
mdl-switch__label填充实际文字,否则视觉上只剩轨道与滑块,可参照 snippets 目录中的示例结构。
配置选项:五个 MDL 类的职责与放置位置
下表完整摘录自 src/switch/README.md 的配置说明,列出了全部可用类及其作用:
| MDL 类 | 作用 | 备注 |
|---|---|---|
mdl-switch | 将 label 定义为 MDL 组件 | 必须加在 label 元素上 |
mdl-js-switch | 为 label 赋予基础的 MDL 行为(触发升级) | 必须加在 label 元素上 |
mdl-switch__input | 将基础 MDL 行为应用到开关本体 | 必须加在 input 元素(复选框)上 |
mdl-switch__label | 将基础 MDL 行为应用到说明文字 | 必须加在 span 元素(caption)上 |
mdl-js-ripple-effect | 启用涟漪点击效果 | 可选;加在 label 元素上,而非 input 元素(开关)上 |
注意事项:
mdl-js-ripple-effect只加在 label 上,同时组件会以编程方式在内部追加涟漪容器,渲染时配合 src/ripple/ripple.js 与 src/ripple/_ripple.scss 产生点击波纹;- 缺任何一个"必需"类都会导致样式或行为不完整,例如只加
mdl-switch不加mdl-js-switch时,原生复选框会直接显示,不会被升级成视觉开关。
禁用状态
文档同时强调:所有开关形态都提供禁用版本,通过标准 HTML 布尔属性disabled触发:
<input type="checkbox" id="switch5" class="mdl-switch__input" disabled>该属性既可以在标记中静态声明,也可以通过脚本编程式地添加或移除。结合源码看,禁用状态会被同步为 label 上的is-disabled状态类,进而触发_switch.scss中灰化轨道与滑块、取消指针光标(cursor: auto)的样式。
源码视角:升级过程与状态同步原理
组件注册与升级入口
src/switch/switch.js 定义MaterialSwitch构造器,并通过 MDL 组件设计模式注册:
componentHandler.register({ constructor: MaterialSwitch, classAsString: 'MaterialSwitch', cssClass: 'mdl-js-switch', widget: true });cssClass: 'mdl-js-switch'即升级触发器:当componentHandler.upgradeElement检测到元素带有mdl-js-switch类时,就会实例化MaterialSwitch并挂到元素的MaterialSwitch属性上(对应测试中$(el).data('upgraded', ',MaterialSwitch')的断言)。
init:动态注入视觉节点
init()(src/switch/switch.js 的init方法)会通过querySelector('.mdl-switch__input')找到复选框,然后用 JavaScript 动态创建轨道(mdl-switch__track)、滑块(mdl-switch__thumb)和焦点辅助圆(mdl-switch__focus-helper,追加在滑块内部):
var track = document.createElement('div'); track.classList.add(this.CssClasses_.TRACK); var thumb = document.createElement('div'); thumb.classList.add(this.CssClasses_.THUMB); var focusHelper = document.createElement('span'); focusHelper.classList.add(this.CssClasses_.FOCUS_HELPER); thumb.appendChild(focusHelper); this.element_.appendChild(track); this.element_.appendChild(thumb);如果 label 包含mdl-js-ripple-effect,还会额外创建mdl-switch__ripple-container与mdl-ripple节点并注入 label。最后在inputElement_上监听change、focus、blur,在 label 上监听mouseup,并调用updateClasses_()、追加is-upgraded类。注意:升级后的原生 input 会被 CSS 隐藏(opacity: 0、宽高为 0),但仍参与焦点与表单交互,见 _switch.scss 中.mdl-switch.is-upgraded &规则——这正是"视觉隐藏、语义保留"的关键设计。
状态如何同步到视觉
核心是updateClasses_()(src/switch/switch.js 的updateClasses_方法),它依次调用两个公开方法:
checkDisabled():读取inputElement_.disabled,有则给 label 加is-disabled,否则移除;checkToggleState():读取inputElement_.checked,为真则加is-checked,否则移除。
is-checked状态类驱动_switch.scss中的颜色与位置变换——滑块从left: 0动画滑到left: $switch-track-length - $switch-thumb-size(即 36px − 20px = 16px),轨道与滑块背景色切换为主色,阴影由shadow-2dp升为shadow-3dp,动画时长 0.28s:
.mdl-switch__thumb { @include material-animation-default(0.28s); transition-property: left; .mdl-switch.is-checked & { background: $switch-thumb-color; left: $switch-track-length - $switch-thumb-size; @include shadow-3dp(); } }这段样式位于 src/switch/_switch.scss,尺寸与颜色常量集中在 src/_variables.scss:
| 变量 | 默认值 | 含义 |
|---|---|---|
$switch-track-length | 36px | 轨道长度 |
$switch-track-height | 14px | 轨道高度 |
$switch-thumb-size | 20px | 滑块直径 |
$switch-label-height | 24px | 标签行高 |
$switch-ripple-size | 48px(2×24px) | 涟漪容器直径 |
$switch-thumb-color/$switch-track-color | 主题主色$color-primary | 开启态颜色 |
$switch-off-thumb-color | palette-grey-50 | 关闭态滑块色 |
$switch-off-track-color | 黑色 26% 透明度 | 关闭态轨道色 |
$switch-disabled-thumb-color | palette-grey-400 | 禁用态滑块色 |
$switch-disabled-track-color | 黑色 12% 透明度 | 禁用态轨道色 |
由于$switch-color派生自全局的$color-primary(见 src/_variables.scss 中的$switch-color: rgb(#{$color-primary})),开关开启态颜色会自动跟随主题主色,无需单独配置。
编程式控制:公开 API 一览
MaterialSwitch暴露了 5 个公开方法(src/switch/switch.js),升级完成后可通过element.MaterialSwitch直接调用:
| 方法 | 作用 |
|---|---|
checkDisabled() | 依据 input 的disabled属性同步is-disabled状态类 |
checkToggleState() | 依据 input 的checked属性同步is-checked状态类 |
disable() | 将 input 置为disabled = true并刷新状态类 |
enable() | 将 input 置为disabled = false并刷新状态类 |
on() | 将 input 置为checked = true并刷新状态类 |
off() | 将 input 置为checked = false并刷新状态类 |
典型用法:
var switchEl = document.querySelector('.mdl-switch'); // 开启 switchEl.MaterialSwitch.on(); // 禁用 switchEl.MaterialSwitch.disable(); // 读取当前状态(input 才是状态的真正持有者) var isOn = switchEl.querySelector('.mdl-switch__input').checked;由于on/off直接修改 input 的checked,随后触发的change事件会再次调用updateClasses_(),保证编程操作与用户点击两条路径最终都收敛到同一套状态同步逻辑。blur_()中为绕开"blur 后焦点事件被重新触发"的问题而使用window.setTimeout(..., 0.001)的细节(见 src/switch/switch.js 的blur_方法),也展示了组件对交互边界情况的处理。
单元测试:行为约定的可验证证据
test/unit/switch.js 用 Mocha + Chai 固化了组件的核心行为,可作为接入时的契约参考:
MaterialSwitch在全局可用(expect(MaterialSwitch).to.be.a('function'));componentHandler.upgradeElement(el, 'MaterialSwitch')后元素带upgraded数据标记;- 给 input 置
disabled = true并调用checkDisabled()后,label 类名变为mdl-switch mdl-js-switch is-upgraded is-disabled; - 给 input 置
checked = true并调用checkToggleState()后,label 类名变为mdl-switch mdl-js-switch is-upgraded is-checked。
测试中的createSwitch()构造结构与 README 的标记模板完全一致(label + input + span),说明文档、源码与测试三者的标记契约是统一的,读者可以放心照抄。
小结与实战建议
- 标记即契约:
mdl-switch、mdl-js-switch、mdl-switch__input、mdl-switch__label四个类缺一不可,涟漪类mdl-js-ripple-effect按需附加在 label 上; - 表单友好:底层仍是标准 checkbox,
name/value/checked照常参与表单提交;升级后视觉节点由 JS 注入,无 JS 时退化回原生复选框; - 状态可控:
disabled属性支持静态声明与编程增删;公开 APIon/off/disable/enable可驱动任意业务逻辑; - 主题联动:开启态颜色自动继承主题主色
$color-primary,尺寸常量可在 src/_variables.scss 中按需覆写。
如需查看完整示例,可直接运行仓库中 src/switch/snippets/switch-on.html 与 src/switch/snippets/switch-off.html;想深入组件注册机制,可继续阅读 src/mdlComponentHandler.js 与 src/ripple/ripple.js。
【免费下载链接】material-design-liteMaterial Design Components in HTML/CSS/JS项目地址: https://gitcode.com/gh_mirrors/ma/material-design-lite
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考