前言
别人家的效果
自家的效果 用的是 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
}
}
codesandbox查看
后言
后面我朋友反馈,借助我写的这个效果实现了需求并上线,后续未收到问题,对于把这案例分享出来,一是作者我没有搜索到相关类似案例,(或许github应该有只是我没有找到而已),二是我向通过分享这个效果实现过程,来突出我们在项目效果实现中,有必要注重细节,该效果中,边界值的计算参照值得思考,同时也希望大家一起完善该效果,共同进步。(技术分享的初衷),重要的一点提升自己的写作水平(狗头笑?)。
结语
如果你有更好的点子或是疑问,欢迎留言
文中若有不准确或错误的地方,欢迎指出
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!