HTML+CSS:滑动按钮

本文详细描述了一段CSS代码,如何实现一个类似iOS或Android系统的单选按钮组滑动效果,包括选择框的动态显示和隐藏以及在不同选项之间的切换动画。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

效果演示

27-滑动按钮.gif

实现了一个单选按钮组,用户可以选择其中一个选项。当用户选择一个选项时,会出现一个选择框,选择框会从选项的上方滑入,并在选项上方显示一个白色的背景。当用户再次选择一个选项时,选择框会从选项的上方滑出,并在新的选项上方显示一个白色的背景。整个效果类似于 iOS 或 Android 系统中的单选按钮组。

Code

<main>
    <form>
        <label>
            <input type="radio" name="inventor" value="bell">
            <span class="truncate">Alexander Graham Bell</span>
        </label>
        <label>
            <input type="radio" name="inventor" value="morse">
            <span class="truncate">Samuel Morse</span>
        </label>
        <label>
            <input type="radio" name="inventor" value="edison">
            <span class="truncate">Thomas Edison</span>
        </label>
        <div class="selection"></div>
    </form>
</main>
* {
    border: 0;
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

:root {
    --hue: 223;
    --bg: hsl(var(--hue), 90%, 90%);
    --fg: hsl(var(--hue), 90%, 10%);
    --primary: hsl(var(--hue), 90%, 50%);
    --trans-dur: 0.3s;
    --trans-timing: cubic-bezier(0.65, 0, 0.35, 1);
    font-size: calc(16px + (24 - 16) * (100vw - 320px) / (2560 - 320));
}

body,
input {
    color: var(--fg);
    font: 1em/1.5 "DM Sans", sans-serif;
}

body {
    background-color: var(--bg);
    display: flex;
    height: 100vh;
    transition:
        background-color var(--trans-dur),
        color var(--trans-dur);
}

main {
    margin: auto;
    padding: 1.5em 0;
    width: 100%;
}

form {
    margin: auto;
    max-width: 20em;
    position: relative;
    width: calc(100% - 3em);
}

label,
input[type="radio"] {
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

label {
    background-color: hsla(0, 0%, 100%, 0);
    border-radius: 0.75em;
    display: flex;
    align-items: center;
    padding: 1.5em;
    transition: background-color var(--trans-dur);
}

input[type="radio"] {
    background-color: hsl(0, 0%, 100%);
    border-radius: 50%;
    box-shadow:
        0 0 0 0.0625em hsl(var(--hue), 90%, 80%),
        0 0.125em 0.125em 0.0625em hsla(var(--hue), 90%, 10%, 0.3);
    flex-shrink: 0;
    margin-inline-end: 1em;
    outline: transparent;
    position: relative;
    width: 1.5em;
    height: 1.5em;
    transition:
        background-color var(--trans-dur),
        box-shadow var(--trans-dur);
    -webkit-appearance: none;
    appearance: none;
}

input[type="radio"]:before,
input[type="radio"]:after {
    border-radius: 50%;
    content: "";
    display: block;
    position: absolute;
    transform: scale(0);
    transition: transform var(--trans-dur) var(--trans-timing);
}

input[type="radio"]:before {
    box-shadow:
        0 0 0 0.5em var(--primary) inset,
        0 0 0 0.0625em var(--primary);
    top: -0.0625em;
    left: -0.0625em;
    width: calc(100% + 0.125em);
    height: calc(100% + 0.125em);
}

input[type="radio"]:after {
    background-color: hsl(0, 0%, 100%);
    top: 25%;
    left: 25%;
    width: 50%;
    height: 50%;
}

input[type="radio"]:checked:before,
input[type="radio"]:checked:after {
    transform: scale(1);
}

label:has(input[type="radio"]:checked),
label:has(input[type="radio"]:not(:checked)):hover {
    background-color: hsla(0, 0%, 100%, 0.5);
}

.selection {
    border-radius: 0.75em;
    box-shadow: 0 0 0 0.125em var(--primary) inset;
    display: none;
    pointer-events: none;
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 4.5em;
    transition: transform var(--trans-dur) var(--trans-timing);
}

label:has(input[type="radio"]:checked)~.selection {
    animation: fade-in var(--trans-dur) var(--trans-timing);
    display: inherit;
}

label:nth-of-type(2):has(input[type="radio"]:checked)~.selection {
    transform: translateY(100%);
}

label:nth-of-type(3):has(input[type="radio"]:checked)~.selection {
    transform: translateY(200%);
}

.truncate {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Dark theme */
@media (prefers-color-scheme: dark) {
    :root {
        --bg: hsl(var(--hue), 90%, 10%);
        --fg: hsl(var(--hue), 90%, 90%);
    }

    label {
        background-color: hsla(var(--hue), 90%, 30%, 0);
    }

    label:has(input[type="radio"]:checked),
    label:has(input[type="radio"]:not(:checked)):hover {
        background-color: hsla(var(--hue), 90%, 30%, 0.5);
    }

    input[type="radio"] {
        background-color: hsl(var(--hue), 90%, 30%);
        box-shadow:
            0 0 0 0.0625em hsl(var(--hue), 90%, 50%),
            0 0.125em 0.125em 0.0625em hsla(var(--hue), 90%, 10%, 0.3);
    }
}

/* Animations */
@keyframes fade-in {
    from {
        opacity: 0;
    }

    to {
        opacity: 1;
    }
}

实现思路拆分

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

这段代码是用来重置所有元素的默认样式的。具体来说,它将所有元素的边框、盒模型、外边距和内边距都设置为 0。

:root {
  --hue: 223;
  --bg: hsl(var(--hue), 90%, 90%);
  --fg: hsl(var(--hue), 90%, 10%);
  --primary: hsl(var(--hue), 90%, 50%);
  --trans-dur: 0.3s;
  --trans-timing: cubic-bezier(0.65, 0, 0.35, 1);
  font-size: calc(16px + (24 - 16) * (100vw - 320px) / (2560 - 320));
}

这段代码定义了一些 CSS 变量,用于在整个文档中重复使用相同的颜色和过渡效果。具体来说,它定义了一个名为 --hue 的 CSS 变量,用于存储当前的色相值。然后,它定义了 --bg--fg--primary 三个 CSS 变量,分别用于存储背景色、文本颜色和主要颜色。最后,它定义了一个名为 --trans-dur 的 CSS 变量,用于存储过渡效果的持续时间,以及一个名为 --trans-timing 的 CSS 变量,用于存储过渡效果的动画函数。

body,
input {
  color: var(--fg);
  font: 1em/1.5 "DM Sans", sans-serif;
}

这段代码定义了整个文档的文本颜色和字体。具体来说,它将 bodyinput 元素的文本颜色设置为 --fg,字体设置为 1em/1.5 "DM Sans", sans-serif

body {
  background-color: var(--bg);
  display: flex;
  height: 100vh;
  transition:
    background-color var(--trans-dur),
    color var(--trans-dur);
}

这段代码定义了整个文档的背景色和高度。具体来说,它将 body 元素的背景色设置为 --bg,并将 display 属性设置为 flex,以便在文档中使用 flexbox 布局。它还定义了一个过渡效果,用于在背景色和文本颜色之间平滑地过渡。

main {
  margin: auto;
  padding: 1.5em 0;
  width: 100%;
}

这段代码定义了 main 元素的样式。具体来说,它将 main 元素的水平居中,并将其上下内边距设置为 1.5em,左右内边距设置为 0。它还将 main 元素的宽度设置为 100%。

form {
  margin: auto;
  max-width: 20em;
  position: relative;
  width: calc(100% - 3em);
}

这段代码定义了 form 元素的样式。具体来说,它将 form 元素的水平居中,并将其最大宽度设置为 20em。它还将 form 元素的位置设置为相对定位,以便在文档中使用绝对定位。最后,它将 form 元素的宽度设置为文档宽度减去 3em

label,
input[type="radio"] {
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

这段代码定义了 labelinput[type="radio"] 元素的样式。具体来说,它将 labelinput[type="radio"] 元素的光标设置为指针,并将 -webkit-tap-highlight-color 属性设置为透明,以便在 iOS 上隐藏点击高亮效果。

label {
  background-color: hsla(0, 0%, 100%, 0);
  border-radius: 0.75em;
  display: flex;
  align-items: center;
  padding: 1.5em;
  transition: background-color var(--trans-dur);
}

这段代码定义了 label 元素的样式。具体来说,它将 label 元素的背景色设置为半透明的白色,边框半径设置为 0.75em,文本垂直居中对齐,内边距设置为 1.5em。它还定义了一个过渡效果,用于在鼠标悬停时平滑地过渡背景色。

input[type="radio"] {
  background-color: hsl(0, 0%, 100%);
  border-radius: 50%;
  box-shadow:
    0 0 0 0.0625em hsl(var(--hue), 90%, 80%),
    0 0.125em 0.125em 0.0625em hsla(var(--hue), 90%, 10%, 0.3);
  flex-shrink: 0;
  margin-inline-end: 1em;
  outline: transparent;
  position: relative;
  width: 1.5em;
  height: 1.5em;
  transition:
    background-color var(--trans-dur),
    box-shadow var(--trans-dur);
  -webkit-appearance: none;
  appearance: none;
}

这段代码定义了 input[type="radio"] 元素的样式。具体来说,它将 input[type="radio"] 元素的背景色设置为白色,边框半径设置为 50%,阴影设置为两个半透明的黑色圆圈,并将 flex-shrink 属性设置为 0,以便在文档中使用 flexbox 布局。它还将 margin-inline-end 属性设置为 1em,以便在 label 元素和 input[type="radio"] 元素之间添加一些间距。最后,它将 outline 属性设置为透明,以便在 iOS 上隐藏点击高亮效果。

input[type="radio"]:before,
input[type="radio"]:after {
  border-radius: 50%;
  content: "";
  display: block;
  position: absolute;
  transform: scale(0);
  transition: transform var(--trans-dur) var(--trans-timing);
}

这段代码定义了 input[type="radio"]:beforeinput[type="radio"]:after 元素的样式。具体来说,它们都是 input[type="radio"] 元素的伪元素,用于创建圆圈的外观。它们的 border-radius 属性设置为 50%content 属性设置为一个空字符串,display 属性设置为 blockposition 属性设置为绝对定位,transform 属性设置为缩放为 0,transition 属性设置为过渡效果。

input[type="radio"]:before {
  box-shadow:
    0 0 0 0.5em var(--primary) inset,
    0 0 0 0.0625em var(--primary);
  top: -0.0625em;
  left: -0.0625em;
  width: calc(100% + 0.125em);
  height: calc(100% + 0.125em);
}

这段代码定义了 input[type="radio"]:before 元素的样式。具体来说,它将 input[type="radio"]:before 元素的阴影设置为两个半透明的 var(--primary) 颜色,并将其上下左右内边距设置为 -0.0625em,以便在圆圈周围创建一些间距。它还将其宽度和高度设置为 calc(1000% + 0.125em),以便在圆圈周围创建一些间距。

input[type="radio"]:after {
  background-color: hsl(0, 0%, 100%);
  top: 25%;
  left: 25%;
  width: 50%;
  height: 50%;
}

这段代码定义了 input[type="radio"]:after 元素的样式。具体来说,它将 input[type="radio"]:after 元素的背景色设置为白色,并将其上下左右内边距设置为 25%,以便在圆圈内部居中对齐。它还将其宽度和高度设置为 50%,以便在圆圈内部创建一个半径为 50% 的圆。

input[type="radio"]:checked:before,
input[type="radio"]:checked:after {
  transform: scale(1);
}

这段代码定义了 input[type="radio"]:checked:beforeinput[type="radio"]:checked:after 元素的样式。具体来说,它们都是 input[type="radio"] 元素的伪元素,用于在选中时放大圆圈。它们的 transform 属性设置为缩放为 1,以便在选中时放大圆圈。

label:has(input[type="radio"]:checked),
label:has(input[type="radio"]:not(:checked)):hover {
  background-color: hsla(0, 0%, 100%, 0.5);
}

这段代码定义了 label:has(input[type="radio"]:checked)label:has(input[type="radio"]:not(:checked):hover) 元素的样式。具体来说,它们都是 label 元素的伪类选择器,用于在选中和悬停时改变背景色。它们的 background-color 属性设置为半透明的白色,以便在选中时高亮显示。

.selection {
  border-radius: 0.75em;
  box-shadow: 0 0 0 0.125em var(--primary) inset;
  display: none;
  pointer-events: none;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 4.5em;
  transition: transform var(--trans-dur) var(--trans-timing);
}

这段代码定义了 .selection 元素的样式。具体来说,它是一个绝对定位的元素,用于显示选中选项的背景色。它将 border-radius 属性设置为 0.75embox-shadow 属性设置为一个半透明的 var(--primary) 颜色的黑色阴影,并将其位置设置为绝对定位,以便在文档中使用绝对定位。它还将 display 属性设置为 none,以便在文档中隐藏它,并将 pointer-events 属性设置为 none,以便在文档中禁用指针事件。它还将 widthheight 属性设置为 100%,以便覆盖整个文档,并将 transition 属性设置为过渡效果。

label:has(input[type="radio"]:checked)~.selection {
  animation: fade-in var(--trans-dur) var(--trans-timing);
  display: inherit;
}

这段代码定义了 label:has(input[type="radio"]:checked)~.selection 元素的样式。具体来说,它是一个伪类选择器,用于在选中选项时显示选择框。它将 animation 属性设置为一个名为 fade-in 的动画,用于在显示选择框时平滑地过渡。它还将 display 属性设置为 inherit,以便在文档中显示选择框。

label:nth-of-type(2):has(input[type="radio"]:checked)~.selection {
  transform: translateY(100%);
}

这段代码定义了 label:nth-of-type(2):has(input[type="radio"]:checked)~.selection 元素的样式。具体来说,它是一个伪类选择器,用于在选中第二个选项时将选择框向下移动。它将 transform 属性设置为 translateY(100%),以便将选择框向下移动。

label:nth-of-type(2):has(input[type="radio"]:checked)~.selection {
  transform: translateY(100%);
}

这行代码选择了第二个 label 元素,并检查该元素是否包含一个选中的单选按钮。如果是,则选择该元素的后代元素 .selection,并将其 translateY 属性设置为 100%,从而将选择框从选项的上方滑入。

label:nth-of-type(3):has(input[type="radio"]:checked)~.selection {
  transform: translateY(200%);
}

这行代码选择了第三个 label 元素,并检查该元素是否包含一个选中的单选按钮。如果是,则选择该元素的后代元素 .selection,并将其 translateY 属性设置为 200%,从而将选择框从选项的上方滑入。

.truncate {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

这段代码定义了一个名为 .truncate 的类,用于限制文本的宽度并添加省略号。当文本超出限制宽度时,会自动添加省略号。

@media (prefers-color-scheme: dark) {
  :root {
    --bg: hsl(var(--hue), 90%, 10%);
    --fg: hsl(var(--hue), 90%, 90%);
  }

  label {
    background-color: hsla(var(--hue), 90%, 30%, 0);
  }

  label:has(input[type="radio"]:checked),
  label:has(input[type="radio"]:not(:checked)):hover {
    background-color: hsla(var(--hue), 90%, 30%, 0.5);
  }

  input[type="radio"] {
    background-color: hsl(var(--hue), 90%, 30%);
    box-shadow:
      0 0 0 0.0625em hsl(var(--hue), 90%, 50%),
      0 0.125em 0.125em 0.0625em hsla(var(--hue), 90%, 10%, 0.3);
  }
}

这段代码定义了一个媒体查询,用于在用户的设备设置为深色模式时应用不同的样式。在深色模式下,背景颜色、文本颜色、单选按钮的背景颜色和阴影颜色会发生变化。

@keyframes fade-in {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

这段代码定义了一个名为 fade-in 的动画,用于将元素从透明度为 0 淡入到透明度为 1。在这个动画中,元素的 opacity 属性从 0 淡入到 1。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值