当前位置: 首页 > news >正文

日常学习开发记录-switch组件

日常学习开发记录-switch组件

  • 实现思路与过程
    • 第一阶段:基础开关
    • 第二阶段:增强功能
    • 第三阶段:高级功能
  • 技术要点总结
  • 渐进式开发流程

实现思路与过程

我们将从简单到复杂逐步实现这个Switch组件,展示整个实现过程和思路。
最终实现效果:
在这里插入图片描述

第一阶段:基础开关

最基础的开关组件实现了以下核心功能:

  1. HTML结构设计

    • 外层容器div作为组件主体
    • 内部一个span作为滑动轨道
    • 一个内嵌的span作为滑块按钮
  2. CSS样式设计

    • 设置基础样式(大小、颜色、边框)
    • 为轨道设置圆角和背景色
    • 为滑块设置圆形样式和初始位置
    • 添加过渡效果实现平滑切换
  3. 基础交互逻辑

    • 使用v-model双向绑定值
    • 添加点击事件切换状态
    • 根据状态动态改变样式类名
    • 添加disabled属性支持
<!-- 基础结构 -->
<div class="my-switch" @click="handleClick">
  <span class="my-switch__core">
    <span class="my-switch__button"></span>
  </span>
</div>
  1. my-switch__core: 是开关的轨道部分,作为滑块的容器和运动轨迹。它提供了:
    一个背景区域来表示开关的状态(开/关)
    圆角边框形成开关的外观
    提供了滑块移动的轨道
    当状态改变时变换颜色(从inactive颜色到active颜色)
  2. my-switch__button: 是可见的滑块按钮,它:
    在用户切换开关状态时移动
    通过transform属性平滑地从左侧移动到右侧
    视觉上指示当前状态
    通常为圆形,有光滑的阴影效果增强立体感
    当开关状态改变时会有平滑的动画效果

实现重点:

  • 使用CSS类名修改不同状态的样式
  • 通过transform来移动滑块按钮
  • 添加transition实现平滑过渡效果
  • 在这里插入图片描述

第二阶段:增强功能

在基础功能的基础上,添加以下增强功能:

  1. 文本标签

    • 增加activeText和inactiveText属性
    • 根据状态显示对应文本
    • 添加适当的样式和间距
  2. 自定义颜色

    • 添加activeColor和inactiveColor属性
    • 使用CSS变量传递颜色值
    • 根据状态动态改变颜色
  3. 自定义宽度

    • 添加width属性控制组件宽度
    • 动态计算滑块位置

实现代码扩展:

实现重点:

  • 使用CSS变量实现动态颜色
  • 使用计算属性处理样式逻辑
  • 根据文本内容动态调整布局

第三阶段:高级功能

最后实现更高级的功能,使组件更完善:

  1. 表单支持
    • 添加hidden input元素支持表单提交
    • 增加name属性绑定到input上
    • 处理input的change事件

待定。

  1. 键盘可访问性

    • 添加tabindex属性使组件可以获取焦点
    • 监听键盘事件(空格键)触发状态切换
    • 添加focus和blur状态样式
  2. 自定义值支持

    • 从仅支持布尔值扩展到支持字符串、数字等值
    • 添加activeValue和inactiveValue属性
    • 使用计算属性处理值的转换

实现重点:

  • 使用计算属性和侦听器处理复杂的数据流
  • 添加辅助样式增强可访问性
  • 处理键盘事件提高用户体验

技术要点总结

  1. 响应式数据处理

    • 使用Vue的计算属性处理状态转换
    • 使用侦听器同步内外部数据
    • 处理自定义值的双向绑定
  2. CSS技巧

    • 使用CSS变量实现动态样式
    • 结合transform和transition实现平滑动画
    • 使用嵌套选择器组织样式代码
    • 添加焦点状态增强可访问性
  3. 交互优化

    • 支持键盘导航
    • 添加过渡效果使切换平滑
    • 保持表单功能完整性
<template>
  <div
    class="my-switch"
    :class="{
      'is-checked': value === activeValue,
      'is-disabled': disabled,
      'is-focus': focus,
    }"
    @click="handleClick"
    :style="{
      '--active-color': activeColor,
      '--inactive-color': inactiveColor,
      '--switch-width': width + 'px',
      '--button-translate-x': buttonTranslateX + 'px',
    }"
    tabindex="0"
    @keydown.space.prevent="handleClick"
    @focus="handleFocus"
    @blur="handleBlur"
    role="switch"
    :aria-checked="value === activeValue"
    :aria-disabled="disabled"
  >
    <span v-if="inactiveText" class="my-switch__label my-switch__label--left">
      {{ inactiveText }}
    </span>
    <span class="my-switch__core">
      <span class="my-switch__button"></span>
    </span>
    <span v-if="activeText" class="my-switch__label my-switch__label--right">
      {{ activeText }}
    </span>
  </div>
</template>

<script>
  export default {
    name: 'MySwitch',
    props: {
      value: {
        type: [Boolean, String, Number],
        default: false,
      },
      activeValue: {
        type: [Boolean, String, Number],
        default: true,
      },
      inactiveValue: {
        type: [Boolean, String, Number],
        default: false,
      },
      activeText: {
        type: String,
        default: '',
      },
      inactiveText: {
        type: String,
        default: '',
      },
      activeColor: {
        type: String,
        default: '#409eff',
      },
      inactiveColor: {
        type: String,
        default: '#dcdfe6',
      },
      disabled: {
        type: Boolean,
        default: false,
      },
      width: {
        type: [Number, String],
        default: 40,
      },
    },
    data() {
      return {
        focus: false,
      }
    },
    computed: {
      buttonTranslateX() {
        const buttonWidth = 16
        const widthNumber = Number(this.width)
        return widthNumber - buttonWidth - 2 // 2px是按钮到边缘的距离
      },
    },
    methods: {
      handleClick() {
        if (this.disabled) return
        const newValue = this.value === this.activeValue ? this.inactiveValue : this.activeValue
        this.$emit('input', newValue)
        this.$emit('change', newValue)
      },
      handleFocus(event) {
        this.focus = true
        this.$emit('focus', event)
      },
      handleBlur(event) {
        this.focus = false
        this.$emit('blur', event)
      },
    },
  }
</script>

<style lang="scss" scoped>
  .my-switch {
    display: inline-flex;
    align-items: center;
    position: relative;
    font-size: 14px;
    line-height: 20px;
    vertical-align: middle;
    cursor: pointer;
    outline: none;

    &.is-disabled {
      cursor: not-allowed;
      opacity: 0.6;
    }

    &.is-focus {
      .my-switch__core {
        box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.3);
      }
    }

    &.is-checked {
      .my-switch__core {
        border-color: var(--active-color, #409eff);
        background-color: var(--active-color, #409eff);

        .my-switch__button {
          transform: translateX(var(--button-translate-x, 22px));
        }
      }

      .my-switch__label--right {
        color: var(--active-color, #409eff);
      }
    }

    &:not(.is-checked) {
      .my-switch__core {
        border-color: var(--inactive-color, #dcdfe6);
        background-color: var(--inactive-color, #dcdfe6);
      }

      .my-switch__label--left {
        color: var(--inactive-color, #dcdfe6);
      }
    }

    &__label {
      font-size: 14px;
      color: #606266;
      transition: color 0.3s;

      &--left {
        margin-right: 10px;
      }

      &--right {
        margin-left: 10px;
      }
    }

    &__core {
      margin: 0;
      display: inline-block;
      position: relative;
      width: var(--switch-width, 40px);
      height: 20px;
      border-radius: 10px;
      box-sizing: border-box;
      transition: border-color 0.3s, background-color 0.3s, box-shadow 0.3s;

      .my-switch__button {
        position: absolute;
        top: 2px;
        left: 2px;
        border-radius: 100%;
        transition: all 0.3s;
        width: 16px;
        height: 16px;
        background-color: #fff;
        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
      }
    }
  }
</style>

  1. tabindex=“0”:
    使div元素可以获得焦点
    当用户按Tab键时,可以导航到这个开关组件
    默认情况下,div是不能获得焦点的,这个属性让它变成了可聚焦元素
  2. @keydown.space.prevent=“handleClick”:
    监听空格键按下事件
    当组件获得焦点时,按空格键可以触发开关切换
    .prevent阻止空格键的默认行为(页面滚动)
    这样用户就可以完全用键盘操作开关
  3. @focus=“handleFocus” 和 @blur=“handleBlur”:
    处理组件获得和失去焦点的事件
    当组件获得焦点时,会添加一个蓝色阴影效果
    提供视觉反馈,让用户知道当前哪个元素被选中
    这些事件也会触发相应的focus/blur事件通知父组件
  4. role=“switch”:
    告诉屏幕阅读器这是一个开关控件
    让使用屏幕阅读器的用户知道这是一个可以切换的开关
    符合WAI-ARIA规范,提高无障碍性
  5. :aria-checked=“value === activeValue”:
    告诉屏幕阅读器开关的当前状态(开启/关闭)
    当状态改变时,屏幕阅读器会读出新的状态
    帮助视障用户了解开关的当前状态
  6. :aria-disabled=“disabled”:
    告诉屏幕阅读器开关是否被禁用
    当开关被禁用时,屏幕阅读器会提示用户
    帮助视障用户了解开关是否可用
    这些属性的组合确保了:
    键盘用户可以完全操作这个开关
    屏幕阅读器用户可以理解和使用这个开关
    提供了适当的视觉反馈
    符合Web Content Accessibility Guidelines (WCAG)标准

渐进式开发流程

  1. 首先实现最基础的开关功能,确保状态切换正常
  2. 添加样式和过渡效果,使组件看起来美观
  3. 实现v-model双向绑定,保证数据流通
  4. 添加disabled状态,处理禁用逻辑
  5. 增加文本标签显示,提升用户体验
  6. 实现自定义颜色功能,增强组件灵活性
  7. 添加焦点和模糊状态,提高可访问性
  8. 实现键盘导航,支持无鼠标操作
  9. 支持自定义值,增强组件通用性

相关文章:

  • JVM - 垃圾回收基本问题
  • 【力扣hot100题】(009)和为K的子数组
  • 运算放大器(二)运算放大器的选型与应用
  • Xen-cpu@100->cpu@1:Failed to bring up CPU 1 (error -2)
  • Warm-Flow过去、现在和未来都不会有商业版
  • 搭建FTP环境且通过Kali Linux破解FTP用户名密码例子
  • 双指针---《复写零》
  • 使用keepalived结合tomcat和nginx搭建三主热备架构
  • 【零基础学python】python高级语法(四)
  • Java 大视界 -- Java 大数据在智能政务数字身份认证与数据安全共享中的应用(156)
  • Flutter TabBar 右侧渐变遮罩实现中的事件处理问题
  • LeetCode热题100|128.最长连续序列,283.移动零
  • unity 做一个圆形分比图
  • RAG技术的进化:RQ-RAG查询优化/化繁为简Adaptive-RAG智能分类/精准出击
  • 力扣HOT100之普通数组:189. 轮转数组
  • 算法250327题目
  • C语言 —— 此去经年梦浪荡魂音 - 深入理解指针(卷五)
  • 如何快速对比两个不同的excel文件中的单元格的数据是否完全相同 并把不同的单元格的背景颜色更改为红色?
  • MySQL索引优化与应用指南
  • 【电子通识】铅笔硬度简史:从石墨到工业标准
  • 删除wordpress网页无用/福建优化seo
  • 辽宁省城乡与住房建设厅网站/网络推广员的工作内容和步骤
  • 外贸网站建设 杭州/合肥seo排名扣费
  • 北京网站推广怎么做/深圳全网信息流推广公司
  • 即墨疫情最新消息今天封城了/windows优化大师电脑版
  • 怎么自己创建网站免费/沈阳优化网站公司