:checked伪类本身不支持动画,需通过兄弟选择器(如+或~)作用于可动画的兄弟元素(如.slider),并统一在非checked和checked状态下声明transition,推荐用transform: translateX()实现平滑位移。

伪类 :checked 本身不支持动画,必须配合兄弟选择器 + transition
直接对 :checked 元素加 transition 是无效的,因为伪类不改变元素自身可动画属性(比如它不改变 input[type="checkbox"] 的尺寸或位置)。真正能动的是它的兄弟元素(通常是 label 或 span),靠 input:checked + label 或 input:checked ~ .thumb 触发样式切换。
关键点:
-
input必须是type="checkbox",且隐藏(display: none或position: absolute; opacity: 0) - 滑块(thumb)和轨道(track)需用兄弟/后代元素实现,不能依赖
input自身渲染 - 触发位移的属性必须可过渡:常用
transform: translateX()或left(但后者需设position: relative/absolute) - 务必在非
:checked和:checked状态下都声明transition,否则只有一半有动画
滑块位移推荐用 transform: translateX() 而不是 left
transform 触发合成层,性能更好,尤其在移动端不会引发重排;left 在未设 position 时无效,设了又可能干扰布局流。
典型结构示例:
立即学习“前端免费学习笔记(深入)”;
<label class="switch"> <input type="checkbox"> <span class="slider"></span> </label>
CSS 中这样写:
.switch input {
display: none;
}
.switch .slider {
display: inline-block;
width: 40px;
height: 20px;
background: #ccc;
border-radius: 10px;
position: relative;
transition: background 0.2s, transform 0.2s;
}
.switch input:checked + .slider {
background: #4CAF50;
transform: translateX(20px);
}
注意:translateX(20px) 的值 = 轨道宽度 − 滑块宽度,这里轨道宽 40px、滑块宽 20px,所以位移 20px 刚好到右端。
移动端点击区域小?用 label 包裹 + cursor: pointer
原生 input 点击热区极小,用户容易点不中。必须让 label 承担点击行为,并扩大可点击范围。
- 确保
input和label关联(for/id或包裹关系) - 给
label设display: inline-flex或inline-block,并加cursor: pointer - 滑块容器(如
.slider)建议设user-select: none,避免误选文字 - 为兼容旧版 iOS Safari,
transform动画建议加-webkit-transform前缀(虽然现代已基本不需要)
状态同步与 JS 干预的边界在哪?
纯 CSS 开关完全可控,但遇到以下情况就得引入 JS:
- 需要初始状态由后端或 localStorage 决定(CSS 无法读取 JS 变量)
- 切换后要触发 API 请求、埋点或联动其他组件(
:checked无事件机制) - 需要禁用状态(
disabled)——此时:checked仍可匹配,但视觉上应灰掉,得额外写input:disabled + .slider - 无障碍要求高时,仅靠 CSS 无法提供
aria-checked,必须用 JS 同步
最易被忽略的是:很多人把 transition 只写在 :checked 状态里,导致“关→开”有动画、“开→关”瞬间跳变——两个方向都得声明过渡属性,且参数一致。


















