【样式效果】Vue3实现仿制iOS按钮动态效果

iOS开关效果

定义变量:

vue 复制代码
<style scoped lang="scss">
    .layout {
        // 按钮宽度
        $button-width: 500px;
        //  按钮高度
        $button-height: 250px;
        //  按钮里面圆形直径
        $circle-diameter: 200px;
        //  按钮背景与里面圆形间距
        $button-circle-offset:calc(($button-height - $circle-diameter) / 2);
        //  里面圆形阴影大小
        $circle-shadow-size: 10px;
        //  里面圆形在长按情况下的宽度
        $circle-wider-size: 350px;
        //  浅灰色
        $light-gray: #e0e0e0;
        //  深灰色
        $dark-gray: #616161;
        //  绿色
        $green: #4caf50;
    }
</style>

绘制按钮图形:

vue 复制代码
<template>
  <div class="layout">
    <div class="button">
    </div>
  </div>
</template>

<style scoped lang="scss">
.layout {
  // 上面👆定义的变量
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;

  .button{
    width: $button-width;
    height: $button-height;
    background: $light-gray;
    border-radius:calc($button-width / 2);
  }
}
</style>

里面的圆形使用伪类选择器

vue 复制代码
&:after{
    content: '';
    display: block;
    width: $circle-diameter;
    height: $circle-diameter;
    border-radius: $circle-diameter;
    background-color: #fff;
}

给元素添加上定位让圆形固定到指定位置:

vue 复制代码
  .button{
   // ...其他效果;
    position: relative;
    &:after{
    // ...其他效果;
      position: absolute;
      top:$button-circle-offset;
      transform: translateX($button-circle-offset); // 为了后面方便做动画,所以使用translateX
box-shadow:$button-circle-offset 0 calc($circle-shadow-size * 4) $dark-gray ;
    }
  }

处理点击效果:

vue 复制代码
<template>
<div class="layout">
    <div class="button" @click="clickBtn" :class="{ 'btnClick': isActive }" >
    </div>
    </div>
</template>

<script setup lang="ts">
    import {ref} from "vue";
    const isActive = ref(false)

    const clickBtn = () => {
        isActive.value = !isActive.value
    }
</script>

<style scoped lang="scss">
    .btnClick {
        background: $green;
        &:after{
            transform: translateX( calc( $button-width - $circle-diameter - $button-circle-offset ) );
            box-shadow: calc( $button-circle-offset * -1 ) 0 calc($circle-shadow-size * 4) $dark-gray ;
        }
    }
</style>

加入transition动画:

vue 复制代码
.button{
    transition:.3s all ease-in-out;
    &:after{
        transition:.3s all ease-in-out;
    }
}

接下来处理长按效果:

vue 复制代码
.button{
	&:active:after{
      width: $circle-wider-size;
    }
}
.btnClick {
    &:active:after{
          transform: translateX( calc( $button-width - $circle-wider-size - $button-circle-offset ) );
        }
}

这样按钮效果就实现啦🎉

完整代码:

vue 复制代码
<template>
  <div class="layout">
    <div class="button" @click="clickBtn" :class="{ 'btnClick': isActive }" >
    </div>
  </div>
</template>

<script setup lang="ts">
import {ref} from "vue";
const isActive = ref(false)

const clickBtn = () => {
    isActive.value = !isActive.value
}
</script>

<style scoped lang="scss">
.layout {
  // 按钮宽度
  $button-width: 500px;
  //  按钮高度
  $button-height: 250px;
  //  按钮里面圆形直径
    $circle-diameter: 200px;
  //  按钮背景与里面圆形间距
  $button-circle-offset:calc(($button-height - $circle-diameter) / 2);
  //  里面圆形阴影大小
  $circle-shadow-size: 10px;
  //  里面圆形在长按情况下的宽度
  $circle-wider-size: 350px;
  //  浅灰色
  $light-gray: #e0e0e0;
  //  深灰色
  $dark-gray: rgba(97, 97, 97, 0.34);
  //  绿色
  $green: #71d575;

  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;

  .button{
    width: $button-width;
    height: $button-height;
    background: $light-gray;
    border-radius:calc($button-width / 2);
    position: relative;
    transition:.3s all ease-in-out;
    &:after{
      content: '';
      display: block;
      width: $circle-diameter;
      height: $circle-diameter;
      border-radius: $circle-diameter;
      background-color: #fff;
      position: absolute;
      top:$button-circle-offset;
      transform: translateX($button-circle-offset); // 为了后面方便做动画,所以使用translateX
      box-shadow:$button-circle-offset 0 calc($circle-shadow-size * 4) $dark-gray ;
      transition:.3s all ease-in-out;
    }
    &:active:after{
      width: $circle-wider-size;
    }
  }
  .btnClick {
    background: $green;
    &:after{
      transform: translateX( calc( $button-width - $circle-diameter - $button-circle-offset ) );
      box-shadow: calc( $button-circle-offset * -1 ) 0 calc($circle-shadow-size * 4) $dark-gray ;
    }
    &:active:after{
      transform: translateX( calc( $button-width - $circle-wider-size - $button-circle-offset ) );
    }
  }
}
</style>
相关推荐
Surmon37 分钟前
彻底搞懂大模型 Temperature、Top-p、Top-k 的区别!
前端·人工智能
木斯佳3 小时前
前端八股文面经大全:bilibili生态技术方向二面 (2026-03-25)·面经深度解析
前端·ai·ssd·sse·rag
不会写DN3 小时前
Gin 日志体系详解
前端·javascript·gin
冬夜戏雪3 小时前
实习面经记录(十)
java·前端·javascript
和沐阳学逆向4 小时前
iOS逆向_古法逆向_Instagram最新版抓包
macos·ios·cocoa
爱学习的程序媛5 小时前
【Web前端】JavaScript设计模式全解析
前端·javascript·设计模式·web
小码哥_常5 小时前
从SharedPreferences到DataStore:Android存储进化之路
前端
老黑5 小时前
开源工具 AIDA:给 AI 辅助开发加一个数据采集层,让 AI 从错误中自动学习(Glama 3A 认证)
前端·react.js·ai·nodejs·cursor·vibe coding·claude code
jessecyj5 小时前
Spring boot整合quartz方法
java·前端·spring boot
苦瓜小生5 小时前
【前端】|【js手撕】经典高频面试题:手写实现function.call、apply、bind
java·前端·javascript