如何仅使用 CSS 创建动画切换开关并实现暗模式功能

2025-06-04

如何仅使用 CSS 创建动画切换开关并实现暗模式功能

众所周知,拨动开关允许我们在两种相反的状态之间进行选择,例如开/关,无论是打开灯泡还是关闭微波炉。拨动开关无处不在,因为我们每天都在使用它们。因此,在网站上使用数字拨动开关,能够赋予网站更现代的感觉,并促进用户与网站之间的微交互,也就不足为奇了。以 Hashnode 为例,它有一个动画切换按钮,允许我们将整个网站的主题从亮模式切换到暗模式,反之亦然。👇

Hashnode 切换

你可能在其他网站上也见过这种或更酷炫的动画切换按钮,并好奇这些切换按钮是如何制作的,以及这些网站主题是如何随时更改的。因为作为一名开发者/设计师,你必须承认……在你的网站和项目中拥有这个功能确实很酷炫。这就是我们在本文中要学习的内容。读完本文后,你不仅会学到如何制作切换按钮,还会掌握它以及实现亮/暗模式的艺术。

先决条件

  • 您只需要具备基本的 HTML 和 CSS 知识,即我不需要解释什么是输入、标签、宽度、高度等。
  • 如果这还不明显的话,当然是文本编辑器和浏览器。
  • 您喜欢针对 5 岁儿童的解释 :)

更好地理解 CSS

乍一看,仅用 CSS 创建一个切换开关似乎令人望而生畏,你通常会认为需要一个库,或者至少需要 JavaScript 来完成这项任务。虽然你的想法并非完全错误,因为大多数人都使用这些库来创建切换开关,但这其实是一个 CSS 可以处理的小任务。在深入研究代码之前,我想先解释一下 CSS 中两个我们经常忽略的特性。由于我们的代码依赖于这两个特性,所以我们需要先稍微讨论一下。

第一个与表单中的 input 和 label 元素有关。我们不会在这里创建任何表单,但为了便于说明,我将在 HTML 文档中创建一个 label 和一个 input 元素,并将 type 设置为 checkbox。

 <label>A confused label</label>
 <input type="checkbox"/>
Enter fullscreen mode Exit fullscreen mode

输出👇

未链接的标签

从语义上讲,我们创建了一个,旁边label有一个元素,只需在浏览器中查看它,您就可以轻松知道这用于我们的复选框,甚至可以将两者的样式设置为看起来相关。这并没有错,因为单击复选框会按预期将其状态切换为选中/未选中。但对于它本身,它不知道它在该文档中做什么,也不知道它应该与该复选框相关联,仅从外观上看它们可能看起来相关,但在编程上它们并不相关,因此单击此标签绝对不会执行任何操作。但 CSS 有一个属性,您可以将其分配给 ,以告诉它它与元素的关系,这就是属性,并且对于要将 a 链接到 的属性必须具有与分配给属性的值相同的值。所以让我们修改我们的代码,以便您可以看到这个链接的效果。inputlabellabelinputlabelinputforlabelinputinputidforlabel

 <label for='switch'>A Linked label</label>
 <input id='switch' type="checkbox"/>
Enter fullscreen mode Exit fullscreen mode

输出👇
链接标签.gif

for请注意,在将labelid输入中的属性值设置为相同的值后switch,当单击复选框或标签时,复选框会被切换,因为它们现在已经建立了关系(它们已链接)。

第二个特性与伪类有关。由于大多数人会混淆伪类和伪元素,因此在继续之前,我们需要先明确它们之间的区别。

伪元素:伪元素只是一个选择器,用于在网页上插入/注入虚拟内容(HTML 标记中不存在),并设置元素指定部分的样式。例如::before::after等等……请注意,它们用两个冒号 ie 声明,但由于我们对它们不感兴趣,我就不再赘述了。::placeholder::first-letter::

伪类:另一方面,伪类只是一个选择器,它针对元素的状态,并做更多的事情。一个典型的例子是:hover元素的状态,这意味着当用户用鼠标悬停在元素上时,您希望应用某些 CSS 规则。有趣的是,某些元素有自己独特的伪类,a例如链接有一个:visited状态,Google 在其搜索结果中使用了它,如果您点击链接访问网站,然后返回搜索结果,您访问的链接的颜色将从蓝色变为紫色。就像链接一样,复选框也有自己独特的伪类:checked:unchecked。请注意,伪类用一个冒号声明,即:

这将我们带到这些伪类中最有趣的部分,我们实际上可以使用伪类根据特定元素的状态来定位和设置其他元素的样式,让我们回到我们的标签示例并修改它们出现的顺序,以便输入出现在标签之前。

 <input id='switch' type="checkbox"/>
 <label for='switch'>A Linked label</label>
Enter fullscreen mode Exit fullscreen mode

现在,让我们在复选框切换(选中)时更改此标签的颜色,然后我将进一步解释。

input:checked  + label {
  color: blue;
}
Enter fullscreen mode Exit fullscreen mode

输出👇

切换状态 1.gif
理解代码

  • input:checked:我们只是使用伪类来定位输入元素的选中状态,并表示我们想在选中该元素时执行某些操作。

  • +:这是一个相邻的兄弟选择器,意味着我们想要定位紧邻输入元素的元素。

  • label:最后,如果我们的输入元素的状态变为:checked,则将标签元素的颜色从初始颜色更改为蓝色;

非常简单吧?但是使用一个元素的状态来设置另一个元素的样式有两个注意事项(限制),这就是为什么我们必须确保输入位于标签上方:

  • 您无法根据子元素的状态来设置父元素或根元素的样式,因为 CSS 本质上是一种层叠样式表,这意味着样式会从文档的根元素(族谱的顶部)向下应用到嵌套的子元素。换句话说,只有子元素可以从父元素继承属性,而父元素不能从子元素继承属性。仍然感到困惑?不妨将 CSS 想象成一条瀑布,它只能从喷泉顶部向下流动,您无法要求它通过向上移动来扰乱这种流动,从而根据子元素状态的变化来更改元素的样式。

  • 您可以根据同级元素的状态来设置元素的样式,但前提是该同级元素是较老的同级元素,也就是说,要使用其状态的元素必须位于标记中目标元素的上方。使用上面的例子,如果我们的标签位于输入元素的上方,我们就无法根据输入元素的变化来设置标签的样式,因为输入元素是按排列顺序排列的,是标签的较年轻的同级元素。CSS 不会向上传递样式,只会向下传递,这意味着只有较老的同级元素(位于之前的元素)的状态才能影响较年轻的同级元素(位于之后的元素)。

现在您对 CSS 有了更好的了解,您可以仅使用 CSS 来创建切换开关。

如何仅使用 CSS 创建切换开关

拨动开关 1.gif

HTML

那么,让我们从 HTML 标记开始吧。由于我们需要至少两个图标来实现切换的动画部分,所以我们将从 Font Awesome 中获取它们。如果您恰好是新手,并且对使用 Font Awesome 图标经验不足,这是一个图标库,可以为您提供项目中使用的图标集合。我们将首先将 HTML 文档链接到这个库以获取访问权限。最快捷的方法是谷歌搜索“Font Awesome CDN”或访问https://cdnjs.com/libraries/font-awesome

https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta2/css/all.min.css
Enter fullscreen mode Exit fullscreen mode

将获取的代码行添加到文档头部,紧邻 CSS 样式表链接之前。好了,让我们继续编写 HTML 标记。

我们将首先重新创建我们一直在使用的输入和标签示例,但这次我们将两个兄弟放在一个充当包装器的 div 容器中,而不是在标签中放置实际文本,而是放置两个图标和一个跨度来替换标签文本。

<body>
     <div class="switch-container">
         <input type="checkbox" id="switch" />
         <label for="switch">
            <i class="fas fa-sun"></i>
            <i class="fas fa-moon"></i>
            <span class="ball"></span>
         </label>
      </div>
</body>
Enter fullscreen mode Exit fullscreen mode

输出👇

切换.gif

这就是我们实现这个开关所需的全部 HTML 代码,但你有没有注意到,当点击标签标签中的任何内容(任何图标)时,它也会切换复选框,就像我们点击复选框本身一样?希望这看起来不像是魔术,因为我们之前已经解释过这种行为了。

  • 提示: 标签for属性与输入相关联id

CSS

我们将首先重置文档,然后使用 flexbox 快速将所有内容置于 DOM 的中心。

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
}
Enter fullscreen mode Exit fullscreen mode

现在来看看实际的代码。我们其实对包裹着输入框和标签的那个开关容器 div 没什么兴趣,我们主要想把它设计label成一个开关,然后利用input元素的状态变化来实现我们的小魔法。所以,在开始代码之前,我们先来设计一下标签的样式。

label {
  display: flex;
  width: 75px;
  height: 35px;
  justify-content: space-between;
  align-items: center;
  padding: 0 6px;
  background: #222;
  border-radius: 50px;
  cursor: pointer;
  position: relative;
}
Enter fullscreen mode Exit fullscreen mode

输出👇

切换 html 标签.png

因为标签是行内元素,而我们希望赋予它固定的宽度/高度,所以我们将其显示设置为行内块元素或弹性框元素。我选择了后者,flex这样我就可以轻松地控制图标在标签中的位置,方法是先将它们垂直居中对齐(align-items),然后将它们分开(justify-content)。注意到太阳元素没有被推到最后吗?如果你还记得的话,我们的标签内有 3 个元素:两个图标和一个带有球类 (ball) 的 span 元素。CSS 只是尊重不可见的 span 元素,因为它是行内元素,所以它本身没有内容可显示。然后,我们在垂直方向(左/右)添加一些内边距,以防止图标接触标签的边缘。我们继续为标签添加深色背景和 50px 的对称半径,使其看起来就像一个按钮/选项卡,最后添加一个光标,这样就很明显它是可点击的。但在结束之前,我添加了一个相对位置 (relative)(现在没用,但我们稍后会用到它。)

让我们把焦点转移到图标上。

label i {
  font-size: 18px;
}
label .fa-sun {
  color: gold;
  transition: 0.8s;
}
label .fa-moon {
  color: #fff;
  transition: 0.8s;
}
label .ball {
  display: none;
}
Enter fullscreen mode Exit fullscreen mode

输出👇

切换 html 标签 2.png

我们简单地将两个图标的大小都设为 18px,然后分别将它们的颜色改为金色和白色。由于我们计划在一秒钟内移动这些图标,因此我们给它们的过渡时间设为 0.8 秒。然后,我们暂时将 span 元素从 DOM 中隐藏起来,因为我们接下来要做一些很酷的事情,我不想让它妨碍操作。结果,太阳被自由地推到了末尾。

  • 注意:过渡属性允许我们在给定的持续时间内平滑地改变元素属性的值。

现在轮到我们施展第一个魔法了。还记得吗?点击标签时,复选框会被触发切换。现在,我们将使用伪类状态来为图标添加动画效果,使其在每次输入切换时移动。

input:checked + label .fa-sun {
  transform: translateX(-43px);
}
input:checked + label .fa-moon {
  transform: translateX(43px);
}
Enter fullscreen mode Exit fullscreen mode

输出👇

动画切换.gif

  • input:checked + label .icons-class:我们再次使用伪类来定位输入的选中状态,然后说当选中此输入时,定位下一个相邻的标签(初级兄弟)并对该标签内的图标执行某些操作。
  • transform: translateX((43px):这里我们使用 transform 属性,它可以改变元素的布局(移动、旋转、缩放、平移、倾斜等),然后应用一个 transformX 属性,使元素水平移动。月亮的坐标设置为 -43px,即向左移动 43px,而太阳的坐标则向右移动 43 度。由于我们已经设置了过渡时长,它们会按照设置的时间平滑移动。

对于下一个技巧,我们将通过将月亮的不透明度设置为 0 来使月亮最初消失,并且仅在检查输入时使其可见,而太阳在其初始状态下可见,但在检查输入时使其消失。

label .fa-sun {
  opacity: 1;
}
input:checked + label .fa-sun {
  opacity: 0;
}
label .fa-moon {
  opacity: 0;
}
input:checked + label .fa-moon {
  opacity: 1;
}
Enter fullscreen mode Exit fullscreen mode

不要对我选择将它们添加为新的 CSS 规则感到困惑,我只是希望您能够遵循添加效果的顺序,而不会被太多的代码弄糊涂。输出👇

动画切换 2.gif

  • 注意:不透明度控制元素的透明度。0 = 完全透明,1 = 完全不透明(可见)。同样,我们的过渡持续时间使整个变化看起来流畅且富有动画效果。现在回想一下,我们在图标旁边有一个带有球类的 span,我们将其设置为 display: none?是时候让它发光了……我们将把它设计成圆球的样式,并赋予它 absolute 的位置……为什么呢?

还记得我们给标签元素设置了相对位置吗?这样做是因为当子元素的定位设置为绝对位置时,它会脱离其自然位置,并浮动到其他具有相对位置的父元素上。因此,当我们将 span 元素的定位设置为绝对位置时,我们会将其从地面拉起,但仍然通过设置标签的相对位置使其保持在标签的边界框内。这样,球就不会影响图标,而是浮动在图标上方。

label .ball {
  position: absolute;
  display: block;
  width: 25px;
  height: 25px;
  top: 5px;
  left: 5px;
  background: #fff;
  border-radius: 50%;
  transition: 0.8s;
}
Enter fullscreen mode Exit fullscreen mode

输出👇

动画切换 3.gif
由于它是一个 span(内联),我们将其设置为块元素,并添加了固定的 /height 属性和 5px 的 top/left 属性,这意味着球应该位于放置它的标签的顶部和左侧 5px 处。然后添加了白色背景,并使用 border-radius 将其设置为圆形。你已经知道这个过渡效果的作用了,当输入切换时,我们会移动这个球。

input:checked + label .ball {
  transform: translateX(40px);
}
Enter fullscreen mode Exit fullscreen mode

输出👇

动画切换 4.gif
注意到我们再也看不到月亮了吗?那是因为它和球的移动方向一致,所以它被球挡住了(重叠)。但这不是我们想要的,所以我们需要修改月亮,让它位于右侧初始位置(不透明度仍然保留为 0),然后在点击输入时将其移动到左侧(不透明度仍然保留为 1)。

label .fa-moon {
  transform: translateX(43px);
}
input:checked + label .fa-moon {
  transform: translateX(0);
}
Enter fullscreen mode Exit fullscreen mode

输出👇

动画切换 5.gif
我们的切换开关几乎完成了,当选中切换开关时,我们将分别更改球和标签的背景颜色。我特意将过渡持续时间设置得较长,以便我们能够看到元素发生的变化,但现在我们将通过将每个过渡设置为 0.3 秒(0.3 秒)来加快速度,最后隐藏悬停在标签上的输入。

label {
  transition: 0.3s;
}
input:checked + label {
  background: #c0c0c0;
}
input:checked + label .ball {
  background: #222;
}
input {
  display: none;
}
//Change every previous transition from .8s to .3s
Enter fullscreen mode Exit fullscreen mode

输出👇

动画切换 6.gif
为了让我们的开关更加生动,让我们添加一个旋转效果,以便切换时两个图标都会旋转出来。

input:checked + label .fa-sun {
  transform: translateX(-43px) rotate(160deg);
}
label .fa-moon {
  transform: translateX(43px) rotate(250deg);
}
input:checked + label .fa-moon {
  transform: translateX(0), rotate(0);
}
Enter fullscreen mode Exit fullscreen mode

最终结果👇

动画切换决赛 (2).gif
太棒了!🥳🎉 我们只用 CSS 就制作了一个精美的动画切换开关,现在你已经学到了足够的知识,能够重新创建你在互联网上遇到的任何切换开关。作为奖励,我会附上第二篇文章的链接,其中我们将这个切换开关转换成两个用 SVG 图标制作的更精美的开关。

现在,这篇文章已经很长了,因此,我们将把它拆分开来,并以一个悬念作为结尾 :)。本文的其余部分将重点介绍如何在现有或未来的网站/项目上实现暗色主题或多个主题。链接将在下面提供,同时还将提供制作其他两种拨动开关变体的链接。

结论

如果您觉得这篇文章对您有帮助(我敢打赌您一定觉得有用😉),或者有疑问?或者发现错误/拼写错误……请在评论区留下您的反馈。最后,如果有人正在为制作切换开关或实现暗黑模式而苦恼,请分享此资源并关注我以获取更多信息。

如果你感觉慷慨(我希望你是 🙂)或者想要鼓励我,你可以给我一杯(或一千杯)咖啡,让我开心一笑。:)

链接到第二篇文章

文章来源:https://dev.to/daveyhert/how-to-create-animated-toggle-switches-with-just-css-and-implement-a-dark-mode-feature-2e3d
PREV
20+ 使用 React.js 构建的精彩网站
NEXT
数据结构和算法对你意味着什么