个人项目地址: SubTopH前端开发个人站
(自己开发的前端功能和UI组件,一些有趣的小功能,感兴趣的伙伴可以访问,欢迎提出更好的想法,私信沟通,网站属于静态页面)
SubTopH前端开发个人站https://subtop.gitee.io/subtoph.github.io/#/home
以上 👆 是个人前端项目,欢迎提出您的建议😊
以下是正文内容...............
实现效果
直接上代码
组件文件
<template>
<div class="swh-range-page" :id="onlyId" ref="rangeRef">
<div class="swh-range-selection" :id="onlyId + '_selection'">
<!-- 滑动槽 -->
<div class="swh-trough" @click="handleTroughClick"></div>
<!-- 选中范围高亮条 -->
<p
class="swh-drag-trough"
:id="onlyId + '_drag-trough'"
@click="handleTroughClick"
></p>
<!-- 拖拽按钮 -->
<p class="swh-drag-btn" :id="onlyId + '_drag-btn'"></p>
<p class="drag-value" :id="onlyId + '_drag-value'">{{ rangValue }}</p>
</div>
</div>
</template>
<script>
import { reactive, toRefs, onBeforeMount, onMounted, ref, nextTick } from 'vue';
import { findCloseNum, handleStepNumber } from '@/utils/common.js';
export default {
name: '',
props: {
minValue: {
type: Number,
default: 0,
explain: '范围最小值',
otherVal: '---'
},
maxValue: {
type: Number,
default: 100,
explain: '范围最大值',
},
// 初始值
initValue: {
type: Number,
default: 0,
explain: '设置初始值',
},
// 是否设置初始值
setInitValue: {
type: Boolean,
default: true,
explain: '是否使用初始值(true时initValue有效)',
},
getRangChange: {
type: Function,
explain: '数值发生变化'
}
},
setup(props, ctx) {
const data = reactive({
dragEle: null, //推拽槽元素
rangeEle: null, //推拽按钮元素
dragTrough: null, //推拽的条元素
clickPos: 0, //点击移动按钮时鼠标距离父级的位置
moveLeft: 0, //移动的left
rangeWidth: 0, //范围盒子宽度
btnWidth: 0, //拖拽按钮尺寸
rangValue: 0, //选中值
optionalRange: 0, //实际范围
rangArr: [], //范围段数组
onlyId: ''
});
const rangeRef = ref(null); // 获取当前组件中外层元素
onBeforeMount(() => {});
onMounted(() => {
nextTick(() => {
// 获取组件数量
const ele = document.getElementsByClassName('swh-range-page');
if (ele.length) {
for (let i = 0; i < ele.length; i++) {
// 组件和当前ref获取的组件本身相等就设置id
if (rangeRef.value === ele[i]) {
data.onlyId = `swhRangeRef_${i}`; //设置显示框id
}
}
}
nextTick(() => {
init();
});
});
});
const init = () => {
const { onlyId } = data;
data.rangeCom = document.querySelector(`#${onlyId}`);
data.dragEle = document.querySelector(`#${onlyId}_drag-btn`);
data.dragValueEle = document.querySelector(`#${onlyId}_drag-value`);
data.rangeEle = document.querySelector(`#${onlyId}_selection`);
data.dragTrough = document.querySelector(`#${onlyId}_drag-trough`);
data.btnWidth = data.dragEle.offsetWidth;
data.rangeWidth = data.rangeEle.offsetWidth;
data.dragEle.style.left = -data.btnWidth / 2 + 'px';
// 设置默认值,没有就是最小值
const { setInitValue, initValue, minValue, maxValue } = props;
if (setInitValue) {
// 设置默认值
if (initValue >= minValue && initValue <= maxValue) {
// 初始值在范围内
data.rangValue = initValue;
} else {
console.error('未设置初始值或者初始值超出范围');
data.rangValue = minValue;
}
} else {
// 未设置默认值,默认最小值
data.rangValue = props.minValue;
}
// 最大值减区最小值是 实际范围
data.optionalRange = props.maxValue - props.minValue;
// 获取分割范围数组
data.rangArr = handleStepNumber(data.rangeWidth, data.optionalRange);
initMovePosition();
bindEvent();
};
// 初始化值所在的位置
const initMovePosition = () => {
const index = rangNumArr().indexOf(data.rangValue);
if (index !== -1) {
const proportion = index / data.optionalRange;
const initLeft = data.rangeWidth * proportion;
data.moveLeft = initLeft;
moveLeft();
}
};
// 范围数值数组
const rangNumArr = () => {
let rNumArr = [];
for (let i = 0; i < data.optionalRange + 1; i++) {
rNumArr.push(props.minValue + i);
}
return rNumArr;
};
// 事件监听
const bindEvent = () => {
data.dragEle.addEventListener('mousedown', handleMouseDown, false);
};
// 按下事件
const handleMouseDown = (e) => {
// 点击时鼠标距离父级left 减去已经实际移动的距离
data.clickPos = e.clientX - data.rangeEle.offsetLeft - data.moveLeft;
document.addEventListener('mousemove', handleMouseMove, false);
document.addEventListener('mouseup', handleMouseUp, false);
};
// 移动处理
const handleMouseMove = (e) => {
// 获取实际移动的位置,移动后left减去点击时clickPos(left)是实际移动的left
const inMoveleft = e.clientX - data.rangeEle.offsetLeft;
// 移动的距离 - 开始点击的位置 = 实际移动距离
data.moveLeft = inMoveleft - data.clickPos;
moveLeft();
};
// 直接点击范围条,改变拖拽按钮选中位置
const handleTroughClick = (e) => {
// 鼠标点击位置减去元素距离body的left,获取点击在跳上的left距离
const inMoveleft = e.clientX - data.rangeCom.getBoundingClientRect().left;
// 距离减去按钮宽度未实际移动left
data.moveLeft = inMoveleft - data.btnWidth;
moveLeft();
};
// 移动位置
const moveLeft = () => {
// 调整实际移动的距离
if (data.moveLeft > data.rangeWidth) {
// 最大限制
data.moveLeft = data.rangeWidth;
} else if (data.moveLeft < 0) {
// 最小限制
data.moveLeft = 0;
} else {
// 移动至鼠标最接近的范围点上
data.moveLeft = findCloseNum(data.rangArr, data.moveLeft);
}
//按键 移动的距离减去按键一半宽度
data.dragEle.style.left = data.moveLeft - data.btnWidth / 2 + 'px';
//设置选中范围条宽度
data.dragTrough.style.width = data.moveLeft + 'px';
// 移动的占比
const proportion = data.moveLeft / data.rangeWidth;
// 计算移动的值
data.rangValue =
parseInt(data.optionalRange * proportion) + props.minValue;
// 计算提示数值的偏移位置
const wc = (data.dragValueEle.offsetWidth - data.btnWidth) / 2;
// 设置显示范围值的提示位置,设置按钮的位置即可
data.dragValueEle.style.left =
data.dragEle.offsetLeft - Math.abs(wc) + 'px';
ctx.emit('getRangChange', data.rangValue);
};
// 移除事件监听
const handleMouseUp = () => {
document.removeEventListener('mousemove', handleMouseMove, false);
document.removeEventListener('mouseup', handleMouseUp, false);
};
return {
rangeRef,
handleTroughClick,
...toRefs(data)
};
}
};
</script>
<style scoped lang="less">
.swh-range-page {
position: relative;
min-width: 160px;
width: 100%;
display: flex;
padding: 0 20px;
justify-content: center;
border-radius: 10px;
.swh-range-selection {
position: relative;
width: 100%;
height: 30px;
z-index: 9;
.swh-trough {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100%;
height: 5px;
background: rgb(243, 243, 243);
border-radius: 3px;
cursor: pointer;
}
.swh-drag-btn {
position: absolute;
top: 50%;
left: 0;
width: 20px;
height: 20px;
background: #fff;
border: 2px solid @TSB;
transform: translateY(-50%);
border-radius: 50%;
z-index: 1;
cursor: pointer;
box-sizing: border-box;
&:hover {
transform: translateY(-50%) scale(1.1);
transition: 0.3s;
}
}
.swh-drag-trough {
cursor: pointer;
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
background: @TSB;
height: 5px;
border-radius: 3px;
}
.drag-value {
position: absolute;
top: -20px;
left: 0px;
border-radius: 5px;
width: 30px;
height: 20px;
background: rgba(0, 0, 0, 0.8);
font-size: 12px;
line-height: 20px;
color: #fff;
text-align: center;
}
}
}
</style>
组件使用到的findCloseNum方法
// 判断当前数字 最靠近数组中那个数字
export function findCloseNum(arr, num) {
var index = 0; // 保存最接近数值在数组中的索引
var old_value = Number.MAX_VALUE; // 保存差值绝对值,默认为最大数值
for (var i = 0; i < arr.length; i++) {
var new_value = Math.abs(arr[i] - num); // 新差值
if (new_value <= old_value) { // 如果新差值绝对值小于等于旧差值绝对值,保存新差值绝对值和索引
if (new_value === old_value && arr[i] < arr[index]) { // 如果数组中两个数值跟目标数值差值一样,取大
continue;
}
index = i;
old_value = new_value;
}
}
return arr[index] // 返回最接近的数值
}
组件使用到的handleStepNumber方法
export function handleStepNumber(w, r) {
const itemPx = w / r;
let rangArr = [];
for (let i = 0; i < r+1; i++) {
rangArr.push(Math.ceil(itemPx * i));
}
return rangArr;
};
1.组件可以实现最小值和最大值的设置
2.可初始化值
3.组件长度根据父组件自定义
4.滑动和点击会改变范围值
根据自己的需求可进行更多扩展