如何利用JS实现一个可左右拉伸的滑块效果
目录
- 前言
- 需求
- 方案
- 源码
- 后言
- 总结
前言
上月我一朋友,让我帮他实现一个效果,话说是他公司产品觉得自家项目的制作gif功能用户体验差,看到了别人的效果,于是就“挥刀霍霍”向了我那朋友,具体我们看下效果。
别人家的效果
自家的效果
用的是 element ui的 滑块
果然还是别人家的效果,话说这个忙还是得帮,下面开始正题
需求
动手之前我们先捋一捋需求
滑块不能超出临界值(不能滑出轨道)
手动进行向左、向右、滑块整体左右滑动
鼠标对滑块左按钮、右按钮、中间按钮 分别可以向左拉伸、向右拉伸、滑块整体左右滑动
方案
需求貌似不多,选什么样的方案来实现呢? C3的 transform的 translateX 还是 定位position呢?由于之前我对定位的拖拽比较熟悉,于是我选择使用 定位position来实现。
具体思路:
- 创建一个容器container、容器下面创建一个滑轨track、滑轨羡慕创建一个滑块 slider、滑块下面分别创建 左边按钮、中间按钮、右边按钮
- 创建一个Slider类进行具体实现
源码
代码配有详尽的注释,以助于理解
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>slider</title> </head> <style> * { margin: 0; padding: 0; } .container { width: 400px; margin: 50px auto; } .track { box-sizing: content-box; position: relative; background: #d1d1d1; height: 30px; line-height: 30px; font-size: 12px; } .track::before, .track::after { content: ''; position: absolute; top: 0; height: 100%; width: 12px; background: #d1d1d1; } .track::before { left: -12px; border-radius: 2px 0 0 2px; } .track::after { right: -12px; border-radius: 0 2px 2px 0; } .slider { position: absolute; top: 0; height: 100%; background: rgba(66, 133, 244, 0.25); text-align: center; color: #fff; font-size: 12px; border-radius: 2px; z-index: 1; cursor: pointer; } .middle { width: inherit; position: absolute; top: 0; height: 100%; background: rgba(66, 133, 244, 0.25); text-align: center; color: #fff; font-size: 12px; border-radius: 2px; z-index: 1; cursor: pointer; } .left { position: absolute; top: 0; height: 100%; width: 12px; background: #4285f4; border: 0; padding: 0; cursor: ew-resize; left: -12px; border-radius: 2px 0 0 2px; } .right { position: absolute; top: 0; height: 100%; width: 12px; background: #4285f4; border: 0; padding: 0; cursor: ew-resize; outline: none; right: -12px; border-radius: 0 2px 2px 0; z-index: 10; } .right:not(:hover):not(:active) > span, .left:not(:hover):not(:active) > span { visibility: hidden; color: #000; } .right > span, .left > span { position: absolute; bottom: 33px; transform: translate(-50%); left: 50%; visibility: visible; } .right > span > span, .left > span > span { display: block; background: #4285f4; height: 20px; padding: 0 4px; border-radius: 2px; line-height: 20px; } .left::before, .left::after, .right::before, .right::after { content: ''; position: absolute; width: 1px; height: 8px; background: #fff; margin: 1px; top: 10px; right: 3px; } .left::before, .right::before { left: 3px; } </style> <body> <!-- 容器 --> <div class="container"> <!-- 轨道 --> <div class="track"> <!-- 滑块 --> <div class="slider" style="left: 0px"> <!-- 中间按钮 --> <div class="middle">0</div> <!-- 左边按钮 --> <div class="left"> <span> <span style="margin-left: 30px" class="left-text">0</span> </span> </div> <!-- 右边按钮 --> <div class="right"> <span> <span style="margin-left: 0px" class="right-text">0</span> </span> </div> </div> </div> </div> </body> </html>
效果如图
下来我们来加上灵魂 js
想怎么用比较合适?
const slider = new Slider({ slider_width: 50,// 滑块初始宽度 track: "track", // 轨道class类名 slider: "slider", // 滑块类名 text: { left: "left-text", // 左边按钮文案class类名 right: "right-text" // 右边按钮文案class类名 }, btn: { left: "left", // 左边按钮class类名 right: "right",// 右边按钮class类名 middle: "middle" //中间按钮class类名 }, range: { min: 50, // 滑块区间范围最小值 max: 200 // 滑块区间范围最大值 } }); // 手动向右拉伸 滑块宽度至100 //slider.rightStretch(100) // 手动向左拉伸 滑块宽度至100 //slider.leftStretch(100) // 手动移动滑块距离左边200 //slider.move(200)
想好了怎么用了,那么就是按怎么用进行逐步实现
第一步,我们要把传入的参数转化为构造函数 Slider 内部可以用的值
class Slider { constructor(options) { this._init(options) } _init(options){ this.options = options // 传入的参数对象 // this.init_width = options.slider_width // 初始滑块宽度 options.range && options.range.min && (this.range_min = options.range.min) // 滑块宽度最小值 options.range && options.range.max && (this.range_max = options.range.max) // 滑块宽度最大值 // this.btn_left_slider = this._$(options.btn.left) // 滑块左边按钮类 - 用于向左边进行拉伸 this.text_middle = this.btn_middle_slider = this._$(options.btn.middle) // 初始滑块宽度类 - 用于滑块整体左右移动 // 滑块中间按钮上面文案提示类 this.btn_right_slider = this._$(options.btn.right) // 初始滑块宽度类 - 用于向右边边进行拉伸 // this.track = this._$(options.track) // 滑块轨道类 this.slider = this._$(options.slider) // 滑块轨道类 // this.text_left = this._$(options.text.left) // 滑块左边按钮上面文案提示类 this.text_right = this._$(options.text.right) // 滑块右边按钮上面文案提示类 // this._initAttr() // 初始化属性值 this._initevent() // 初始化事件 } }
第一步做了:同步用户传入参数于示例中、以及初始化滑块宽度、配置滑块的最大最小值、同步必须的dom、初始化属性值、初始化事件
为什么需要一开始就执行初始化属性(this._initAttr())呢?是dom里面都是默认的 0,用户传入的值需要同步初始化到dom中。
/** * @description: 初始化文案 */ _initAttr() { this.slider.style.width = this.options.slider_width + 'px' this.text_middle.innerText = this.options.slider_width }
然后会初始化事件,主要用到的事件是 鼠标在 滑块元素 mousedown 事件,然后借助事件代理,来区分我们作用的事件源对象的class类名,最后根据作用类名不同来执行不同的逻辑,这个是整个实现的重点
/** * @description: 初始化事件 */ _initevent() { this.slider.onmousedown = ({ target: { className }, clientX }) => { // 滑块宽度 动态值 const sliderWidth = this.slider.offsetWidth // 滑块 距离左边 动态值 const sliderLeft = this.slider.offsetLeft switch (className) { case this.options.btn.middle: // 滑块 X 坐标计算 this.disX = clientX - this.slider.offsetLeft // 滑块整体向右边移动 最大能移动的临界值 const valid_right = this.track.offsetWidth - sliderWidth // document.onmousemove = ({ clientX }) => { this.slider.style.left = clientX - this.disX < 0 ? 0 // 如果是滑块整体向左移动 超出临界值 就是临界值 : clientX - this.disX > valid_right ? valid_right + 'px' // 如果是滑块整体向右移动 超出临界值 就是临界值 : clientX - this.disX + 'px' this._syncText() } break case this.options.btn.left: // 滑块 X 坐标计算 this.disX = clientX // 滑块右边按钮 至 轨道 最左边变距离 const rightWidth = this.slider.getBoundingClientRect().right - this.track.getBoundingClientRect().left // document.onmousemove = ({ clientX }) => { // 两点间的 x 差值 // 向左 一直移动,临界值是 0 超出边界 就会变成 负数 ,如果一直向右移动,超出右边临界值,右边临界值在移动到右边按钮时产生 const diff = this.disX - clientX // 如果 一直向左移动 对应的 临界值 let valid_left = sliderWidth + diff // 如果 一直向右移动 对应的 临界值 let valid_right = sliderLeft - diff if (this.range_min && valid_left <= this.range_min) { // 如果有设置最小范围值 并且 此时 向左拉伸超出最小值,那么此时的 值 就是最小值 range_min valid_left = this.range_min valid_right = rightWidth - valid_left } else if (this.range_max && valid_left >= this.range_max) { // 如果右设置最大范围值 并且此时移动 valid_left 超出 设置的最大范围值 valid_left = this.range_max >= rightWidth ? rightWidth : this.range_max // 如果设置的最大范围值 超出有效值 rightWidth,那么就设置 valid_left 为 有效值 rightWidth ,否则就是设置为最大范围值 valid_right = rightWidth - valid_left } else if (valid_left >= rightWidth) { // 如果一直向左边移动 , 超出 有效值 rightWidth,(抵死在左边情形) valid_left = rightWidth valid_right = 0 } else if (valid_right >= rightWidth) { // 如果一直向右边移动 , 超出 有效值 rightWidth valid_left = 0 valid_right = rightWidth } this.slider.style.left = valid_right + 'px' this.slider.style.width = valid_left + 'px' this.btn_right_slider.style.left = valid_left + 'px' this._syncText() } break case this.options.btn.right: // 滑块 X 坐标计算 this.disX = clientX - this.btn_right_slider.offsetLeft // document.onmousemove = ({ clientX }) => { // track 轨道宽度 const track_width = this.track.offsetWidth // 滑块左边可滑动距离 const valid_left = this.slider.offsetLeft // 滑块整体向右边移动 最大能移动的临界值 const valid_right = this.range_max ? (this.range_max >= track_width - valid_left ? track_width - valid_left : this.range_max) : track_width - valid_left // 两点x坐标差值 const diff = clientX - this.disX // 滑块右边按钮的 left 值 设置 this.btn_right_slider.style.left = (diff <= (this.range_min || 0) ? this.range_min || 0 : diff >= valid_right ? valid_right : diff) + 'px' // 滑块的宽度 width 设置 this.slider.style.width = this.btn_right_slider.offsetLeft + 'px' // 文案同步 this._syncText() } break } document.onmouseup = () => { document.onmousemove = null document.onmouseup = null } return false } }
其余部分方法实现没有_initevent事件复杂,分别是以下方法:
- 获取dom _$
- 同步文案 _syncText
- 手动控制滑块右边按钮向右拉伸 rightStretch
- 手动控制滑块左边按钮向左拉伸 leftStretch
- 手动控制滑块整体左右滑动 move
/** * @description: 获取dom * @param { string } className */ _$(className) { return document.querySelector(`.${className}`) } /** * @description: 同步文案信息 */ _syncText() { // 开始按钮距离 左边的距离 const start = this.slider.offsetLeft // 结束按钮距离 左边的距离 const end = this.slider.getBoundingClientRect().right - this.track.getBoundingClientRect().left // 容器的总宽度 const total = this.track.offsetWidth // 刷新 左边按钮 tip 文案 this.text_left.innerText = start // 刷新 右边按钮 tip 文案 this.text_right.innerText = end // 刷新 内容按钮 tip 文案 this.btn_middle_slider.innerText = end - start } /** * @description: 手动设置右边按钮拉伸到右边, * @param { Number } n:设置的值 范围 为: {0, rightMax:右边最大距离值} */ rightStretch(n) { // 边界处理 const rightMax = this.track.offsetWidth - this.slider.offsetLeft this.slider.style.width = this.btn_right_slider.style.left = (n >= rightMax ? rightMax : n) + 'px' this._syncText() return this } /** * @description: 手动设置左边按钮拉伸到左边, * @param { Number } n:设置的值 范围 为: { leftMax:左边最大距离值,0} */ leftStretch(n) { // <左边最大距离值> const leftMax = this.slider.getBoundingClientRect().right - this.track.getBoundingClientRect().left // 如果左边按钮超出 最大距离左边边界值,则 就都设置成 <左边最大距离值>leftMax this.slider.style.width = this.btn_right_slider.style.left = (n >= leftMax ? leftMax : n) + 'px' // 边界处理 this.slider.style.left = (n <= leftMax ? leftMax - n : 0) + 'px' this._syncText() return this } /** * @description: 手动设置滑块整体 向右 / 向右 移动 * @param { Number } n :移动距离 {左边:0,右边:max } */ move(n) { const max = this.track.offsetWidth - this.slider.offsetWidth this.slider.style.left = (n >= max ? max : n) + 'px' this._syncText() return this }
最后附上完整代码,和效果
class Slider { constructor(options) { this._init(options) } /** * @description: 初始化 * @param {Object} options */ _init(options) { this.options = options // this.init_width = options.slider_width || 0 // 初始滑块宽度 options.range && options.range.min && (this.range_min = options.range.min) // 滑块宽度最小值 options.range && options.range.max && (this.range_max = options.range.max) // 滑块宽度最大值 // this.btn_left_slider = this._$(options.btn.left) // 滑块左边按钮类 - 用于向左边进行拉伸 this.text_middle = this.btn_middle_slider = this._$(options.btn.middle) // 初始滑块宽度类 - 用于滑块整体左右移动 // 滑块中间按钮上面文案提示类 this.btn_right_slider = this._$(options.btn.right) // 初始滑块宽度类 - 用于向右边边进行拉伸 // this.track = this._$(options.track) // 滑块轨道类 this.slider = this._$(options.slider) // 滑块轨道类 // this.text_left = this._$(options.text.left) // 滑块左边按钮上面文案提示类 this.text_right = this._$(options.text.right) // 滑块右边按钮上面文案提示类 // this._initAttr() // 初始化属性值 this._initEvent() // 初始化事件 } /** * @description: 获取dom * @param { string } className */ _$(className) { return document.querySelector(`.${className}`) } /** * @description: 初始化文案 */ _initAttr() { this.slider.style.width = this.options.slider_width || 0 + 'px' this.text_middle.innerText = this.options.slider_width || 0 } /** * @description: 初始化事件 */ _initevent() { this.slider.onmousedown = ({ target: { className }, clientX }) => { // 滑块宽度 动态值 const sliderWidth = this.slider.offsetWidth // 滑块 距离左边 动态值 const sliderLeft = this.slider.offsetLeft switch (className) { case this.options.btn.middle: // 滑块 X 坐标计算 this.disX = clientX - this.slider.offsetLeft // 滑块整体向右边移动 最大能移动的临界值 const valid_right = this.track.offsetWidth - sliderWidth // document.onmousemove = ({ clientX }) => { this.slider.style.left = clientX - this.disX < 0 ? 0 // 如果是滑块整体向左移动 超出临界值 就是临界值 : clientX - this.disX > valid_right ? valid_right + 'px' // 如果是滑块整体向右移动 超出临界值 就是临界值 : clientX - this.disX + 'px' this._syncText() } break case this.options.btn.left: // 滑块 X 坐标计算 this.disX = clientX // 滑块右边按钮 至 轨道 最左边变距离 const rightWidth = this.slider.getBoundingClientRect().right - this.track.getBoundingClientRect().left // document.onmousemove = ({ clientX }) => { // 两点间的 x 差值 // 向左 一直移动,临界值是 0 超出边界 就会变成 负数 ,如果一直向右移动,超出右边临界值,右边临界值在移动到右边按钮时产生 const diff = this.disX - clientX // 如果 一直向左移动 对应的 临界值 let valid_left = sliderWidth + diff // 如果 一直向右移动 对应的 临界值 let valid_right = sliderLeft - diff if (this.range_min && valid_left <= this.range_min) { // 如果有设置最小范围值 并且 此时 向左拉伸超出最小值,那么此时的 值 就是最小值 range_min valid_left = this.range_min valid_right = rightWidth - valid_left } else if (this.range_max && valid_left >= this.range_max) { // 如果右设置最大范围值 并且此时移动 valid_left 超出 设置的最大范围值 valid_left = this.range_max >= rightWidth ? rightWidth : this.range_max // 如果设置的最大范围值 超出有效值 rightWidth,那么就设置 valid_left 为 有效值 rightWidth ,否则就是设置为最大范围值 valid_right = rightWidth - valid_left } else if (valid_left >= rightWidth) { // 如果一直向左边移动 , 超出 有效值 rightWidth,(抵死在左边情形) valid_left = rightWidth valid_right = 0 } else if (valid_right >= rightWidth) { // 如果一直向右边移动 , 超出 有效值 rightWidth valid_left = 0 valid_right = rightWidth } this.slider.style.left = valid_right + 'px' this.slider.style.width = valid_left + 'px' this.btn_right_slider.style.left = valid_left + 'px' this._syncText() } break case this.options.btn.right: // 滑块 X 坐标计算 this.disX = clientX - this.btn_right_slider.offsetLeft // document.onmousemove = ({ clientX }) => { // track 轨道宽度 const track_width = this.track.offsetWidth // 滑块左边可滑动距离 const valid_left = this.slider.offsetLeft // 滑块整体向右边移动 最大能移动的临界值 const valid_right = this.range_max ? (this.range_max >= track_width - valid_left ? track_width - valid_left : this.range_max) : track_width - valid_left // 两点x坐标差值 const diff = clientX - this.disX // 滑块右边按钮的 left 值 设置 this.btn_right_slider.style.left = (diff <= (this.range_min || 0) ? this.range_min || 0 : diff >= valid_right ? valid_right : diff) + 'px' // 滑块的宽度 width 设置 this.slider.style.width = this.btn_right_slider.offsetLeft + 'px' // 文案同步 this._syncText() } break } document.onmouseup = () => { document.onmousemove = null document.onmouseup = null } return false } } /** * @description: 同步文案信息 */ _syncText() { // 开始按钮距离 左边的距离 const start = this.slider.offsetLeft // 结束按钮距离 左边的距离 const end = this.slider.getBoundingClientRect().right - this.track.getBoundingClientRect().left // 容器的总宽度 const total = this.track.offsetWidth // 刷新 左边按钮 tip 文案 this.text_left.innerText = start // 刷新 右边按钮 tip 文案 this.text_right.innerText = end // 刷新 内容按钮 tip 文案 this.btn_middle_slider.innerText = end - start } /** * @description: 手动设置右边按钮拉伸到右边, * @param { Number } n:设置的值 范围 为: {0, rightMax:右边最大距离值} */ rightStretch(n) { // 边界处理 const rightMax = this.track.offsetWidth - this.slider.offsetLeft this.slider.style.width = this.btn_right_slider.style.left = (n >= rightMax ? rightMax : n) + 'px' this._syncText() return this } /** * @description: 手动设置左边按钮拉伸到左边, * @param { Number } n:设置的值 范围 为: <leftMax> <左边最大距离值,0> */ leftStretch(n) { // <左边最大距离值> const leftMax = this.slider.getBoundingClientRect().right - this.track.getBoundingClientRect().left // 如果左边按钮超出 最大距离左边边界值,则 就都设置成 <左边最大距离值>leftMax this.slider.style.width = this.btn_right_slider.style.left = (n >= leftMax ? leftMax : n) + 'px' // 边界处理 this.slider.style.left = (n <= leftMax ? leftMax - n : 0) + 'px' this._syncText() return this } /** * @description: 手动设置滑块整体 向右 / 向右 移动 * @param { Number } n :移动距离 <左边:0> <右边:max > */ move(n) { const max = this.track.offsetWidth - this.slider.offsetWidth this.slider.style.left = (n >= max ? max : n) + 'px' this._syncText() return this } }
后言
后面我朋友反馈,借助我写的这个效果实现了需求并上线,后续未收到问题,对于把这案例分享出来,一是作者我没有搜索到相关类似案例,(或许github应该有只是我没有找到而已),二是我向通过分享这个效果实现过程,来突出我们在项目效果实现中,有必要注重细节,该效果中,边界值的计算参照值得思考,同时也希望大家一起完善该效果,共同进步。(技术分享的初衷),重要的一点提升自己的写作水平(狗头笑😄)。
总结
到此这篇关于如何利用JS实现一个可左右拉伸的滑块效果的文章就介绍到这了,更多相关JS实现可左右拉伸的滑块内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!