css动画库
纯 CSS 实现 iOS 风格丝滑交互开关
Dec 20, 2025
175 阅读
官方团队
今天分享一个细节满满的 Switch 开关组件。
别看只是个小开关,这段代码最精髓的地方在于它对 **CSS 变量** 的运用,以及那个模拟真实物理按压反馈的 `active` 状态——当你按住不放时,滑块会变宽,交互感极强。
### 效果预览
实际运行效果如下,试着点击并**按住不放**,感受一下那个变宽的细节:
***donghua
<style>
.switch {
--button-width: 3.5em;
--button-height: 2em;
--toggle-diameter: 1.5em;
--button-toggle-offset: calc((var(--button-height) - var(--toggle-diameter)) / 2);
--toggle-shadow-offset: 10px;
--toggle-wider: 3em;
--color-grey: #cccccc;
--color-green: #4296f4;
}
.slider {
display: inline-block;
width: var(--button-width);
height: var(--button-height);
background-color: var(--color-grey);
border-radius: calc(var(--button-height) / 2);
position: relative;
transition: 0.3s all ease-in-out;
cursor: pointer;
}
.slider::after {
content: "";
display: inline-block;
width: var(--toggle-diameter);
height: var(--toggle-diameter);
background-color: #fff;
border-radius: calc(var(--toggle-diameter) / 2);
position: absolute;
top: var(--button-toggle-offset);
transform: translateX(var(--button-toggle-offset));
box-shadow: var(--toggle-shadow-offset) 0 calc(var(--toggle-shadow-offset) * 4) rgba(0, 0, 0, 0.1);
transition: 0.3s all ease-in-out;
}
.switch input[type="checkbox"]:checked + .slider {
background-color: var(--color-green);
}
.switch input[type="checkbox"]:checked + .slider::after {
transform: translateX(calc(var(--button-width) - var(--toggle-diameter) - var(--button-toggle-offset)));
box-shadow: calc(var(--toggle-shadow-offset) * -1) 0 calc(var(--toggle-shadow-offset) * 4) rgba(0, 0, 0, 0.1);
}
.switch input[type="checkbox"] {
display: none;
}
.switch input[type="checkbox"]:active + .slider::after {
width: var(--toggle-wider);
}
.switch input[type="checkbox"]:checked:active + .slider::after {
transform: translateX(calc(var(--button-width) - var(--toggle-wider) - var(--button-toggle-offset)));
}
</style>
<div style="display: flex; justify-content: center; padding: 20px;">
<label class="switch">
<input type="checkbox">
<span class="slider"></span>
</label>
</div>
***
### 代码亮点解析
这段代码虽然短,但有几个点非常值得学习:
#### 1. CSS 变量驱动布局
作者没有把尺寸写死,而是定义了一套变量:
```css
.switch {
--button-width: 3.5em;
--button-height: 2em;
--toggle-diameter: 1.5em;
/* 自动计算间距,改尺寸时不用重新算数学题 */
--button-toggle-offset: calc((var(--button-height) - var(--toggle-diameter)) / 2);
}
```
这样做的好处是,如果你想把开关变大一点,只需要改 `font-size` 或者那几个基础变量,所有的间距、圆角都会自动适配,非常优雅。
#### 2. 拟物化的阴影
注意看滑块(白色圆球)的阴影:
```css
box-shadow: var(--toggle-shadow-offset) 0 ...;
```
当开关在左边时,阴影向右投射;当开关在右边时,阴影向左投射(`calc(var(--toggle-shadow-offset) * -1)`)。这个细节模拟了光源在正上方的物理效果,立体感一下就出来了。
#### 3. 极致的交互细节:`active` 状态
这是最骚的操作。通常我们只写 `:checked`,但这里加了 `:active`:
```css
.switch input[type="checkbox"]:active + .slider::after {
width: var(--toggle-wider); /* 变宽 */
}
```
当你按住开关不松手时,滑块会变长(类似果冻被压扁的效果)。这种微交互(Micro-interaction)是提升用户体验的关键,让用户感觉这个界面是“活”的。
### 完整代码
拿去直接用,记得放在一个容器里,或者直接给 `.switch` 加个定位。
```html
<style>
/* 核心变量定义 */
.switch {
--button-width: 3.5em;
--button-height: 2em;
--toggle-diameter: 1.5em;
--button-toggle-offset: calc((var(--button-height) - var(--toggle-diameter)) / 2);
--toggle-shadow-offset: 10px;
--toggle-wider: 3em; /* 按住时的宽度 */
--color-grey: #cccccc;
--color-green: #4296f4;
}
.slider {
display: inline-block;
width: var(--button-width);
height: var(--button-height);
background-color: var(--color-grey);
border-radius: calc(var(--button-height) / 2);
position: relative;
transition: 0.3s all ease-in-out;
cursor: pointer; /* 加上手型光标体验更好 */
}
/* 滑块本体 */
.slider::after {
content: "";
display: inline-block;
width: var(--toggle-diameter);
height: var(--toggle-diameter);
background-color: #fff;
border-radius: calc(var(--toggle-diameter) / 2);
position: absolute;
top: var(--button-toggle-offset);
transform: translateX(var(--button-toggle-offset));
box-shadow: var(--toggle-shadow-offset) 0 calc(var(--toggle-shadow-offset) * 4) rgba(0, 0, 0, 0.1);
transition: 0.3s all ease-in-out;
}
/* 选中状态:背景变色 */
.switch input[type="checkbox"]:checked + .slider {
background-color: var(--color-green);
}
/* 选中状态:滑块移动 + 阴影反向 */
.switch input[type="checkbox"]:checked + .slider::after {
transform: translateX(calc(var(--button-width) - var(--toggle-diameter) - var(--button-toggle-offset)));
box-shadow: calc(var(--toggle-shadow-offset) * -1) 0 calc(var(--toggle-shadow-offset) * 4) rgba(0, 0, 0, 0.1);
}
/* 隐藏原生 checkbox */
.switch input[type="checkbox"] {
display: none;
}
/* 按下状态:滑块变宽 */
.switch input[type="checkbox"]:active + .slider::after {
width: var(--toggle-wider);
}
/* 选中且按下状态:位置修正 */
.switch input[type="checkbox"]:checked:active + .slider::after {
transform: translateX(calc(var(--button-width) - var(--toggle-wider) - var(--button-toggle-offset)));
}
</style>
<label class="switch">
<input type="checkbox">
<span class="slider"></span>
</label>
```