开发一个仿iOS原生的开关按钮

1,307 阅读2分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

原生效果

cf054fb0a68a0fb93293707572b1a00d.gif

类似这种样式的按钮在手机操作系统中很常见,但如果H5想要实现这样的效果要怎么办呢

元素

首先,我们需要明确这是一个按钮(button)吗?还是开关(switch)?我们可以想想在HTML5中有哪些元素和这个元素的特点比较相近呢。

按钮

在HTML5原生中,有button标签,其默认提供点击特效如下。

test3.gif

但是这好像和我们预期的不太一样,虽然我们的预期看上去是一个按钮,但其实它是一个有着两种状态的开关。但HTML5原生是不提供类似UI库中Switch这样的组件的,所以我们再想想还有没有其他组件有类似的二象性。

单选框、复选框

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <button>确定</button>
  <input type="radio" value="A">
  <input type="radio" value="B">
  <input type="checkbox">
</body>
</html>

test4.gif

test5.gif

可见,复选框相对比较接近,但是也无法达到那种左右切换的“开关”效果,因为复选框的默认使用场景其实是多个选项中对于每个选项的勾选与否。

自定义组件

那么既然原生标签面对这样的需求有点捉襟见肘,那我们只好考虑自己实现一个这样的组件了,用到的技术无非是HTML+CSS

DOM结构

首先,我们使用HTML声明一下组件的大致结构

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .toggle {
      width: 66px;
      height: 40px;
      box-sizing: border-box;
      padding: 3px;
      border-radius: 25px;
      background-color: #53535C;
    }
    .toggle::after {
      content: '';
      display: block;
      height: 34px;
      width: 34px;
      border-radius: 50%;
      background-color: #fff;
    }
  </style>
</head>
<body>
  <div class="toggle"></div>
</body>
</html>

image.png

怎么样,是不是已经有点像样了。这里整个开关就用了一个div标签,其中内部的按钮使用了伪元素进行实现,关于伪元素的使用可以看 CSS选择器:伪元素是怎么回事儿? 这篇文章,里面有详细的解读。

接着,我们需要让按钮“动起来”,从而成为一个开关。

点击事件

由于开关的状态一般是需要被记录下来的,不然单纯的按钮开关交互而不记录开关真实的状态是没有意义的。所以我们这里需要一个变量来记录开关的状态,那就需要用到js进行实现,我们增加如下逻辑

<body>
  <div class="toggle" onclick="handleToggle()"></div>
  <script>
    let myToggleStatus = false;
    function handleToggle() {
      myToggleStatus = !myToggleStatus
    }
  </script>
</body>

样式变化

最后,我们需要根据开关的状态展示不同的样式,即可实现符合期望的开关动效。

<style>
.toggle_on::after {
  margin-left: 44%;
}
</style>
<body>
  <div id="toggle" class="toggle" onclick="handleToggle()"></div>
  <script>
    function changeToggleStyle() {
      const toggle = document.getElementById('toggle');
      if (myToggleStatus) {
        toggle.style.backgroundColor = '#3AFF66';
        toggle.classList.add('toggle_on');
      } else {
        toggle.style.backgroundColor = '#53535C';
        toggle.classList.remove('toggle_on');
      }
    }
  </script>
</body>

test6.gif

样式优化

另外,我们还可以使用CSS中transition属性为样式的变化增加过渡效果,从而使得样式的变化看上去更加的流畅。

.toggle {
  transition: background-color .3s ease;
}
.toggle::after {
  transition: margin-left .3s ease;
}

test7.gif

完整代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .toggle {
      width: 66px;
      height: 40px;
      box-sizing: border-box;
      padding: 3px;
      border-radius: 25px;
      background-color: #53535C;
      transition: background-color .3s ease;
    }
    .toggle::after {
      content: '';
      display: block;
      height: 34px;
      width: 34px;
      border-radius: 50%;
      background-color: #fff;
      transition: margin-left .3s ease;
    }
    .toggle_on::after {
      margin-left: 44%;
    }
  </style>
</head>
<body>
  <div id="toggle" class="toggle" onclick="handleToggle()"></div>
  <script>
    let myToggleStatus = false;
    function handleToggle() {
      myToggleStatus = !myToggleStatus
      changeToggleStyle()
    }
    function changeToggleStyle() {
      const toggle = document.getElementById('toggle');
      if (myToggleStatus) {
        toggle.style.backgroundColor = '#3AFF66';
        toggle.classList.add('toggle_on');
      } else {
        toggle.style.backgroundColor = '#53535C';
        toggle.classList.remove('toggle_on');
      }
    }
  </script>
</body>
</html>