日常学习开发记录-slider组件
日常学习开发记录-slider组件
- 从零开始实现一个优雅的Slider滑块组件
- 前言
- 一、基础实现
- 1. 组件结构设计
- 2. 基础样式实现
- 3. 基础交互实现
- 二、功能增强
- 1. 添加拖动功能
- 2. 支持范围选择
- 3. 添加垂直模式
- 三、高级特性
- 1. 键盘操作支持
- 2. 禁用状态
- 五、使用示例
- 六、总结
从零开始实现一个优雅的Slider滑块组件
前言
在Web开发中,滑块组件是一个常见的UI控件,用于数值范围的选择。本文将带领大家从零开始实现一个类似Element UI的Slider组件,我们将采用渐进式开发的方式,从基础功能开始,逐步添加更多特性。
一、基础实现
1. 组件结构设计
首先,我们需要设计一个基础的滑块组件结构:
<template><div class="my-slider"><div class="my-slider__runway"><div class="my-slider__bar"></div><div class="my-slider__button-wrapper"><div class="my-slider__button"></div></div></div></div>
</template>
这个结构包含:
my-slider: 组件容器my-slider__runway: 滑块轨道my-slider__bar: 已选择区域的进度条my-slider__button-wrapper: 滑块按钮容器my-slider__button: 可拖动的滑块按钮
2. 基础样式实现
<style lang="scss" scoped>.my-slider {width: 100%;height: 10px;cursor: pointer;&__runway {width: 100%;height: 100%;border-radius: 5px;background-color: #f0f0f0;position: relative;.my-slider__bar {position: absolute;top: 0;left: 0;height: 100%;border-radius: 5px;}.my-slider__button-wrapper {height: 36px;width: 36px;position: absolute;top: -13px;transform: translateX(-50%);display: flex;align-items: center;justify-content: center;.my-slider__button {height: 16px;width: 16px;border-radius: 50%;border: 2px solid #007bff;background-color: #fff;transition: transform 0.2s;}&:hover {cursor: grab;.my-slider__button {transform: scale(1.2);}}}}}
</style>
结果:

3. 基础交互实现
<template><div class="my-slider" :class="{ disabled: disabled }"><div class="my-slider__runway" @click="handleSliderClick" ref="slider"><div class="my-slider__bar" :style="barStyle"></div><div class="my-slider__button-wrapper" :class="{ disabled: disabled }" :style="wrapperStyle"><div class="my-slider__button"></div></div></div></div>
</template><script>export default {name: 'MySlider',props: {min: {type: Number,default: 0,},max: {type: Number,default: 100,},value: {type: [Array, Number],default: 0,},disabled: {type: Boolean,default: false,},step: {type: Number,default: 1,},},data() {return {currentValue: this.value,sliderSize: 1, // 滑块大小}},computed: {// 滑块的样式,高亮展示已移动的区域(单个滑块-左侧,多个滑块-中间高亮)barStyle() {return {width: `${this.currentValue}%`,left: `0%`,}},wrapperStyle() {return {left: `${this.currentValue}%`,}},precision() {//确定 min、max 和 step 中最大的小数位数let precisions = [this.min, this.max, this.step].map(item => {let decimal = ('' + item).split('.')[1]return decimal ? decimal.length : 0})return Math.max.apply(null, precisions)},},mounted() {this.resetSliderSize()},methods: {handleSliderClick(event) {if (this.disabled) returnconst sliderOffsetLeft = this.$refs.slider.getBoundingClientRect().leftthis.setPosition(((event.clientX - sliderOffsetLeft) / this.sliderSize) * 100)},setPosition(percentage) {//percentage为百分比位置this.currentValue = this.min + ((this.max - this.min) * percentage) / 100//每步的步长 max 50 min 0 ,每步步长 100 / 50 = 2const lengthPerStep = 100 / ((this.max - this.min) / this.step)//根据当前滑块的百分比位置(percentage)和每一步的长度(lengthPerStep),计算出当前所在的步数(steps) 四舍五入const steps = Math.round(percentage / lengthPerStep)//当前显示值 步长 * 步数* 每步的步长+最小值let value = steps * lengthPerStep * (this.max - this.min) * 0.01 + this.minvalue = parseFloat(value.toFixed(this.precision))this.currentValue = value//this.$emit('update:value', this.currentValue)//v-model 默认监听的是 input 事件,而不是 update:value 事件this.$emit('input', this.currentValue)},resetSliderSize() {this.sliderSize = this.$refs.slider.offsetWidth},},}
</script>
结果:

实现思路:
1. 模板结构
外层容器:<div class="my-slider">,用于包裹整个滑块组件,支持根据 disabled 属性动态添加禁用样式。
滑道:<div class="my-slider__runway">,表示滑块的背景轨道,点击滑道可以快速定位滑块位置。
滑块高亮区域:<div class="my-slider__bar">,表示滑块已移动的区域,宽度根据 currentValue 动态计算。
滑块按钮:<div class="my-slider__button-wrapper">,包含一个圆形按钮,用于拖动滑块,支持禁用状态样式。
2. Props 属性
min:滑块的最小值,默认 0。
max:滑块的最大值,默认 100。
value:滑块的当前值,支持数字或数组类型,默认 0。
disabled:是否禁用滑块,默认 false。
step:滑块的步长,默认 1。
3. 数据与计算属性
currentValue:滑块的当前值,初始值为 props.value。
sliderSize:滑道的宽度,用于计算滑块的百分比位置。
barStyle:计算滑块的样式,动态设置高亮区域的宽度和位置。
wrapperStyle:计算滑块按钮的样式,动态设置按钮的左侧位置。
precision:计算 min、max 和 step 中最大的小数位数,用于确保数值精度。
4. 方法
handleSliderClick(event):处理滑道点击事件,计算点击位置的百分比并设置滑块位置。
setPosition(percentage):根据百分比位置计算滑块的当前值,并触发 input 事件更新父组件的 v-model 绑定值。
resetSliderSize():在组件挂载时重置滑道的宽度。
5. 样式
滑道:灰色背景,圆角矩形。
高亮区域:蓝色背景,表示滑块已移动的区域。
滑块按钮:圆形按钮,支持悬停放大效果,禁用状态下变为灰色。
禁用状态:滑道和高亮区域变为灰色,按钮不可拖动。
6. 交互逻辑
点击滑道:快速定位滑块到点击位置。
拖动滑块:通过 setPosition 方法动态更新滑块位置,并触发 input 事件。
步长控制:根据 step 属性调整滑块的移动步长,确保滑块位置符合步长要求。
禁用状态:当 disabled 为 true 时,禁止所有交互操作。
7. 事件
input 事件:当滑块值发生变化时触发,用于实现 v-model 双向绑定。
主要是在于动态style的计算达到视觉上的效果。
二、功能增强
1. 添加拖动功能
<template><div class="my-slider" :class="{ disabled: disabled }"><div class="my-slider__runway" @click="handleSliderClick" ref="slider"><div class="my-slider__bar" :style="barStyle"></div><divclass="my-slider__button-wrapper":class="{ disabled: disabled, dragging: dragging }":style="wrapperStyle"@mousedown="onButtonDown"@touchstart="onButtonDown"ref="button"><div class="my-slider__button"></div></div></div></div>
</template><script>export default {name: 'MySlider',///data() {return {currentValue: this.value, // 当前值sliderSize: 1, // 滑块大小dragging: false, // 是否正在拖拽startX: 0, // 开始拖拽时的 x 坐标currentX: 0, // 当前拖拽时的 x 坐标startPosition: 0, // 开始拖拽时的位置newPosition: null, // 新位置oldValue: this.value, // 旧值}},computed: {// 滑块的样式,高亮展示已移动的区域(单个滑块-左侧,多个滑块-中间高亮)barStyle() {return {width: `${this.currentValue}%`,left: `0%`,}},wrapperStyle() {return {left: `${this.currentValue}%`,}},precision() {//确定 min、max 和 step 中最大的小数位数let precisions = [this.min, this.max, this.step].map(item => {let decimal = ('' + item).split('.')[1]return decimal ? decimal.length : 0})return Math.max.apply(null, precisions)},},watch: {value(val) {this.currentValue = val},},mounted() {this.resetSliderSize()},methods: {/*** 点击滑块* @param {Event} event - 事件对象*/handleSliderClick(event) {if (this.disabled) return// 防止点击滑块按钮时触发if (this.$refs.button && this.$refs.button.contains(event.target)) {return}const sliderOffsetLeft = this.$refs.slider.getBoundingClientRect().leftthis.setPosition(((event.clientX - sliderOffsetLeft) / this.sliderSize) * 100)this.emitChange()},onButtonDown(event) {if (this.disabled) returnevent.preventDefault() // 阻止默认行为this.dragging = true // 标记开始拖动// 处理触屏事件if (event.type === 'touchstart') {event.clientX = event.touches[0].clientX}// 记录初始位置this.startX = event.clientXthis.startPosition = parseFloat(this.currentValue)this.newPosition = this.startPosition// 添加全局事件监听window.addEventListener('mousemove', this.onDragging)window.addEventListener('touchmove', this.onDragging)window.addEventListener('mouseup', this.onDragEnd)window.addEventListener('touchend', this.onDragEnd)window.addEventListener('contextmenu', this.onDragEnd)this.resetSliderSize() // 重新计算滑块尺寸},/*** 拖拽中*/onDragging(event) {if (this.dragging) {// 获取当前鼠标位置let clientXif (event.type === 'touchmove') {clientX = event.touches[0].clientX} else {clientX = event.clientX}// 计算移动距离并转换为百分比const diff = ((clientX - this.startX) / this.sliderSize) * 100// 计算新位置this.newPosition = this.startPosition + diff// 更新滑块位置this.setPosition(this.newPosition)}},/*** 拖拽结束*/onDragEnd() {if (this.dragging) {// 使用setTimeout确保在mouseup事件之后执行setTimeout(() => {this.dragging = falsethis.setPosition(this.newPosition)this.emitChange() // 触发change事件}, 0)// 移除所有事件监听window.removeEventListener('mousemove', this.onDragging)window.removeEventListener('touchmove', this.onDragging)window.removeEventListener('mouseup', this.onDragEnd)window.removeEventListener('touchend', this.onDragEnd)window.removeEventListener('contextmenu', this.onDragEnd)}},/*** 设置滑块位置* @param {number} position - 滑块位置 0-100*/setPosition(position) {if (position === null || isNaN(position)) returnif (position < 0) {position = 0} else if (position > 100) {position = 100}//每步的步长 max 50 min 0 ,每步步长 100 / 50 = 2const lengthPerStep = 100 / ((this.max - this.min) / this.step)//根据当前滑块的百分比位置(percentage)和每一步的长度(lengthPerStep),计算出当前所在的步数(steps) 四舍五入const steps = Math.round(position / lengthPerStep)//当前显示值 步长 * 步数* 每步的步长+最小值let value = steps * lengthPerStep * (this.max - this.min) * 0.01 + this.minvalue = parseFloat(value.toFixed(this.precision))this.currentValue = value// 更新 v-model 绑定值,但不触发 change 事件this.$emit('input', this.currentValue)},emitChange() {// 拖动结束时触发 change 事件this.$emit('change', this.currentValue)},resetSliderSize() {this.sliderSize = this.$refs.slider.offsetWidth},},}
</script>
效果:

实现思路:
使用 mousedown/touchstart 开始拖动
使用 mousemove/touchmove 处理拖动过程
使用 mouseup/touchend 结束拖动
2. 支持范围选择
添加range方法,重点是拖动至重合时候的处理,要记住当前拖动的是哪一个滑块
// 判断当前点击的是哪个滑块const target = event.target.closest('.my-slider__button-wrapper')if (target === this.$refs.button) {this.startPosition = this.firstValuethis.currentSlider = 'first'} else if (target === this.$refs.button1) {this.startPosition = this.secondValuethis.currentSlider = 'second'}
3. 添加垂直模式
通过prop属性vertical来判断是否开启垂直模式

三、高级特性
1. 键盘操作支持
@keydown.left,@keydown.right, @keydown.up,@keydown.down,根据键盘方向事件,更新调用setposition方法直接更新滑块位置
2. 禁用状态
.my-slider {&.is-disabled {cursor: not-allowed;opacity: 0.6;.my-slider__button-wrapper {cursor: not-allowed;}}
}
五、使用示例
最后实现的效果:

六、总结
通过这个渐进式的实现过程,我们完成了一个功能完整的Slider组件。主要特点包括:
-
基础功能:
- 单滑块/双滑块支持
- 自定义数值范围
- 平滑的拖动效果
-
增强功能:
- 刻度标记
- 禁用状态
-
高级特性:
- 键盘操作支持
- 垂直模式
- 自定义格式化
-
性能优化:
- 防抖处理
- 计算属性缓存
这个实现不仅满足了基本需求,还考虑到了用户体验、可访问性和性能优化等多个方面。通过这样的渐进式开发,我们可以确保每一步都有坚实的基础,同时逐步增加功能复杂度。
相关文章:
日常学习开发记录-slider组件
日常学习开发记录-slider组件 从零开始实现一个优雅的Slider滑块组件前言一、基础实现1. 组件结构设计2. 基础样式实现3. 基础交互实现 二、功能增强1. 添加拖动功能2. 支持范围选择3. 添加垂直模式 三、高级特性1. 键盘操作支持2. 禁用状态 五、使用示例六、总结 从零开始实现…...
Windows 系统如何使用Redis 服务
前言 在学习过程中,我们长期接触到的是Mysql 关系型数据库,也是够我们平时练习项目用的,但是后面肯定会有大型数据的访问就要借助新的新的工具。 一、什么是Redis Redis(Remote Dictionary Server)是一个基于内存的 键…...
【unity游戏开发入门到精通——UGUI】CanvasScaler画布缩放器组件
注意:考虑到UGUI的内容比较多,我将UGUI的内容分开,并全部整合放在【unity游戏开发——UGUI】专栏里,感兴趣的小伙伴可以前往逐一查看学习。 文章目录 一、CanvasScaler画布缩放器组件是什么二、CanvasScaler的三种适配模式1、Cons…...
Hugging Face 模型:AI 模型的“拥抱”与开源革命!!!
🌐 Hugging Face 模型:AI 模型的“拥抱”与开源革命 用表情符号、图表和代码,探索开源模型生态的底层逻辑与应用场景! 🌟 名字由来:为什么叫 Hugging Face? “Hugging”:象征 开放…...
关于 人工智能(AI)发展简史 的详细梳理,按时间阶段划分,涵盖关键里程碑、技术突破、重要人物及挑战
以下是关于 人工智能(AI)发展简史 的详细梳理,按时间阶段划分,涵盖关键里程碑、技术突破、重要人物及挑战: 字数:约2500字 逻辑结构:时间线清晰,分阶段描述技术突破、关键事件与挑战…...
微服务即时通信系统---(四)框架学习
目录 ElasticSearch 介绍 安装 安装kibana ES客户端安装 头文件包含和编译时链接库 ES核心概念 索引(Index) 类型(Type) 字段(Field) 映射(mapping) 文档(document) ES对比MySQL Kibana访问ES测试 创建索引库 新增数据 查看并搜索数据 删除索引 ES…...
Android查看依赖树的方法,简单有效
一、使用命令打印 在工具栏“Terminal”中输入以下命令,即可打印依赖树信息 gradlew xxxx:dependencies (“xxxx”为module名称)二、工具栏双击打印 右侧“Gradle”工具栏打开按下图顺序依次查找到“dependencies”,双击后依赖树就会在控制台中打印出…...
自动化测试工具playwright中文文档-------14.Chrome 插件
介绍 注意 插件仅在以持久化上下文启动的 Chrome/Chromium 浏览器中工作。请谨慎使用自定义浏览器参数,因为其中一些可能会破坏 Playwright 的功能。 以下是获取位于 ./my-extension 的 Manifest v2 插件背景页面句柄的代码示例。 from playwright.sync_api imp…...
GitHub配置密钥
1.生成SSH密钥 1)检查 SSH 密钥是否存在 首先,确认是否已经在本地系统中生成了 SSH 密钥对。可以通过以下命令检查: ls -al ~/.ssh 在命令输出中,应该能看到类似 id_rsa 和 id_rsa.pub 这样一对文件。如果这些文件不存在&#…...
【2-10】E1与T1
前言 之前我们简单介绍了人类从电话线思维到如今的数据报分组交换思维过渡时期的各种技术产物,今天我们重点介绍 E1/T1技术。 文章目录 前言1. 产生背景2. T13. E14. SONET4.1 OC-14.2 OC-3 及其它 5. SDH5.1. STM-1 6. SONET VS SDH后记修改记录 1. 产生背景 E1/…...
【设计模式】适配器模式:让不兼容的接口和谐共处
引言 在软件开发中,我们经常会遇到这样的情况:两个已经存在的接口无法直接协同工作,但我们又希望它们能够无缝对接。这时,适配器模式就派上用场了。适配器模式(Adapter Pattern)是一种结构型设计模式&…...
Servlet、HTTP与Spring Boot Web全面解析与整合指南
目录 第一部分:HTTP协议与Servlet基础 1. HTTP协议核心知识 2. Servlet核心机制 第二部分:Spring Boot Web深度整合 1. Spring Boot Web架构 2. 创建Spring Boot Web应用 3. 控制器开发实践 4. 请求与响应处理 第三部分:高级特性与最…...
PTA:古风排版
中国的古人写文字,是从右向左竖向排版的。本题就请你编写程序,把一段文字按古风排版。 输入格式: 输入在第一行给出一个正整数N(<100),是每一列的字符数。第二行给出一个长度不超过1000的非空字符串&a…...
Android LiveData学习总结(源码级理解)
LiveData 工作原理 数据持有与观察者管理:LiveData 内部维护着一个数据对象和一个观察者列表。当调用 observe 方法注册观察者时,会将 LifecycleOwner 和 Observer 包装成 LifecycleBoundObserver 对象并添加到观察者列表中。生命周期感知:L…...
Pandas进行数据预处理(标准化数据)③
数据标准化处理代码解析 数据标准化处理代码解析课前预习1. 离差标准化(Min - Max Scaling)结果2. 标准差标准化(Standard Scaling)结果3. 小数定标标准化(Decimal Scaling)结果 代码整体概述代码详细解析1…...
vue里provide作用:将一组全局方法注入到 Vue 应用的所有子组件中
在 Vue.js 中, provide(mainFunc, {...}) 是依赖注入(Dependency Injection)的提供者(provider)部分,它的作用是: 功能说明 : 将一组全局方法注入到 Vue 应用的所有子组件中子组件可以通过 inject 接收这些方法 import { provi…...
基于uniapp 实现画板签字
直接上效果图 代码 <template><view class"container"><!-- 签名画布 --><view class"canvas-container"><canvas canvas-id"signCanvas" class"sign-canvas"touchstart"handleTouchStart"touc…...
JDBC 初认识、速了解
目录 一. JDBC的简介 1. 数据的持久化 2. 什么是JDBC 二. JDBC中常用的类和接口 1. Driver 接口 2. DriverManager 类 3. Connection 接口 4. Statement 接口 5. PreparedStatement接口 6. ResultSet 接口 三. 总结 前言 从现在开始就来讲解JDBC的相关知识了 本文的…...
(2025亲测可用)Chatbox多端一键配置Claude/GPT/DeepSeek-网页端配置
1. 资源准备 API Key:此项配置填写在一步API官网创建API令牌,一键直达API令牌创建页面创建API令牌步骤请参考API Key的获取和使用API Host:此项配置填写https://yibuapi.com/v1查看支持的模型请参考这篇教程模型在线查询 2. ChatBox网页版配…...
4.vtk光照vtkLight
文章目录 VTK中的光照1. vtkLight 的两种类型:位置光照和方向光照2. vtkLight 的常用方法3. 方法命名风格4. vtkProp 的可见性与 vtkLight 的开关 示例 VTK中的光照 vtkLight: 用于定义一个或多个光源。每个光源可以有其颜色、位置、焦点等属性。 vtkActor: 每个vtk…...
【速写】formatting_func与target_modules的细节(peft)
文章目录 SFTTrainer的构造参数版本差异怎么写formatting_func?关于lora_config中的target_modules能否在target_modules中指定特定某个模块? 以下面的示例pipeline为案: # -*- coding: utf8 -*- # author: caoyang # email: caoyangstu.sufe.edu.cnfr…...
YOLOv2学习笔记
YOLOv2 背景 YOLOv2是YOLO的第二个版本,其目标是显著提高准确性,同时使其更快 相关改进: 添加了BN层——Batch Norm采用更高分辨率的网络进行分类主干网络的训练 Hi-res classifier去除了全连接层,采用卷积层进行模型的输出&a…...
第十五届蓝桥杯----数字串个数\Python
目录 问题: 思想: 代码: 问题: Q:小蓝想要构造出一个长度为 10000 的数字字符串,有以下要求: 1) 小蓝不喜欢数字 0 ,所以数字字符串中不可以出现 0 ; 2) 小蓝喜欢数字 3 和 7 ,所以数字字符串中必须…...
【YOLOv8改进 - 卷积Conv】PConv(Pinwheel-shaped Conv): 风车状卷积用于红外小目标检测, 复现!
YOLOv8目标检测创新改进与实战案例专栏 专栏目录: YOLOv8有效改进系列及项目实战目录 包含卷积,主干 注意力,检测头等创新机制 以及 各种目标检测分割项目实战案例 专栏链接: YOLOv8基础解析+创新改进+实战案例 文章目录 YOLOv8目标检测创新改进与实战案例专栏介绍摘要文章链…...
LeetCode:链表
160. 相交链表 /*** 单链表的定义* Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode(int x) {* val x;* next null;* }* }*/ public class Solution {public ListNode getIntersectionN…...
Dockerfile项目实战-单阶段构建Vue2项目
单阶段构建镜像-Vue2项目 1 项目层级目录 以下是项目的基本目录结构: 2 Node版本 博主的Windows电脑安装了v14.18.3的node.js版本,所以直接使用本机电脑生成项目,然后拷到了 Centos 7 里面 # 查看本机node版本 node -v3 创建Vue2项目 …...
音视频小白系统入门笔记-0
本系列笔记为博主学习李超老师课程的课堂笔记,仅供参阅 音视频小白系统入门课 音视频基础ffmpeg原理 绪论 ffmpeg推流 ffplay/vlc拉流 使用rtmp协议 ffmpeg -i <source_path> -f flv rtmp://<rtmp_server_path> 为什么会推流失败? 默认…...
Zabbix 简介+部署+对接Grafana(详细部署!!)
目录 一.Zabbix简介 1.Zabbix是什么 2.Zabbix工作原理(重点) 3.Zabbix 的架构(重点) 1.服务端 2.客户端: 4.Zabbix和Prometheus区别 二.Zabbix 部署 1.前期准备 2.安装zabbix软件源和组件 3.安装数据库…...
C++: Initialization and References to const 初始化和常引用
cpp primer 5e, P97. 理解 这是一段很容易被忽略、 但是又非常重要的内容。 In 2.3.1 (p. 51) we noted that there are two exceptions to the rule that the type of a reference must match the type of the object to which it refers. The first exception is that we …...
Ubuntu2404装机指南
因为原来的2204升级到2404后直接嘎了,于是要重新装一下Ubuntu2404 Ubuntu系统下载 | Ubuntuhttps://cn.ubuntu.com/download我使用的是balenaEtcher将iso文件烧录进U盘后,使用u盘安装,默认选的英文版本, 安装后,安装…...
