实现滑动杠杆:
需求:
需要实现一个可以拖动,可以点击到某个百分比的滑动bar
具体效果图如下:
思路:
- 我们需要一个装载slider所有的容器
- 容器中需要有一根常驻线,也就是常见的slider灰色总长度
- 需要一根被选中显示不同颜色的绿线,并且这根线的width是可以动态变化的。
- 需要添加一个被触发滑动的视图,这个视图也就是我们通常看到的拖动的原点。
- 拖动原点时刻需要记录拖动的距离,这个距离也就是作为我们第3步,颜色线的长度。
实现:
- 首先写一个容器来装载这个slider功能
<div class="percent-slider_container">
</div>
.percent-slider_container {
position: absolute;
left: 10px;
width: 192px;
}
- 在这个容器中,添加一跟背景图线,也就是slider长度的灰度线
<div class="percent-slider_container">
<div class="percent-slider__track">
</div>
</div>
.percent-slider__track-slected {
position: absolute;
height: 4px;
width: 50%;
background: #24B37A;
}
- 在这个灰度线的基础上,添加一个被选中的绿色线,也就是拖动后显示选中的颜色(绿色).具体这个绿色多长,取决于拖动的距离
<div class="percent-slider_container">
<div class="percent-slider__track">
<div :style="selectedStyle" class="percent-slider__track-slected"></div>
</div>
</div>
get selectedStyle() {
return {width:(parseFloat(this.currentPosition))+'%'}
}
.percent-slider__track-slected {
position: absolute;
height: 4px;
width: 50%;
background: #24B37A;
}
- 滑动条添加点击事件,长度要覆盖整个slider长度。然后需要设置5个小点平均部署在这个slider长度上。
<div class="percent-slider_container">
<div class="percent-slider__track">
<div :style="selectedStyle" class="percent-slider__track-slected"></div>
</div>
<div class="percent-slider__dot-wrap" @click.self="clickTrack">
<div class="percent-slider__dot" v-for="item in percentDots" :key="item" :style="backgroundStyle(item)" @click="selectedDot(item)"></div>
</div>
</div>
private percentDots:number[] = [0,25,50,75,100];
clickTrack(event:any) {
let value = parseFloat((event.offsetX/this.sliderSize * 100).toFixed(this.precision));
this.$emit("input", value);
}
.percent-slider__dot-wrap {
top: 0;
position: absolute;
width: 192px;
display: flex;
align-items: center;
justify-content: space-between;
}
.percent-slider__dot {
width: 12px;
height: 12px;
background: #425359;
border-radius: 6px;
}
- 还差一点就是设置一个拖动点了,然后对这个拖动点添加拖动手势监听,监视拖动后记录拖动距离,然后就能计算出百分比距离了。
private vertical = false;
<div class="percent-slider_container">
<div class="percent-slider__track">
<div :style="selectedStyle" class="percent-slider__track-slected"></div>
</div>
<div class="percent-slider__dot-wrap" @click.self="clickTrack">
<div class="percent-slider__dot" v-for="item in percentDots" :key="item" :style="backgroundStyle(item)" @click="selectedDot(item)"></div>
</div>
<div class="percent-slider__dot--selected"
:style="wrapperStyle"
@touchstart="onButtonDown">
</div>
</div>
get wrapperStyle() {
return this.vertical
? { bottom: this.currentPosition }
: {left:(parseFloat(this.currentPosition) * this.sliderSize) / 100 + "px",};
}
onButtonDown(event: Event) {
event.preventDefault();
this.onDragStart(event);
window.addEventListener("touchmove", this.onDragging);
window.addEventListener("touchend", this.onDragEnd);
}
onDragStart(event: any) {
this.dragging = true;
this.isClick = true;
if (event.type === "touchstart") {
event.clientY = event.touches[0].clientY;
event.clientX = event.touches[0].clientX;
}
if (this.vertical) {
this.startY = event.clientY;
} else {
this.startX = event.clientX;
}
this.startPosition = parseFloat(this.currentPosition);
this.newPosition = this.startPosition;
}
onDragging(event: any) {
if (this.dragging) {
this.isClick = false;
let diff = 0;
if (event.type === "touchmove") {
event.clientY = event.touches[0].clientY;
event.clientX = event.touches[0].clientX;
}
if (this.vertical) {
this.currentY = event.clientY;
diff = ((this.startY - this.currentY) / this.sliderSize) * 100;
} else {
this.currentX = event.clientX;
diff = ((this.currentX - this.startX) / this.sliderSize) * 100;
}
this.newPosition = this.startPosition + diff;
this.setPosition(this.newPosition);
}
}
onDragEnd() {
if (this.dragging) {
/*
* 防止在 mouseup 后立即触发 click,导致滑块有几率产生一小段位移
* 不使用 preventDefault 是因为 mouseup 和 click 没有注册在同一个 DOM 上
*/
setTimeout(() => {
this.dragging = false;
// this.hideTooltip();
if (!this.isClick) {
this.setPosition(this.newPosition);
// this.$parent.emitChange();
}
}, 0);
window.removeEventListener("touchmove", this.onDragging);
window.removeEventListener("touchend", this.onDragEnd);
}
}
setPosition(newPosition:any) {
if (newPosition === null || isNaN(newPosition)) return;
if (newPosition < 0) {
newPosition = 0;
} else if (newPosition > 100){
newPosition = 100;
}
const lengthPerStep = 100 / ((this.max - this.min) / this.step);
const steps = Math.round(newPosition / lengthPerStep);
let value = steps * lengthPerStep * (this.max - this.min) * 0.01 + this.min;
value = parseFloat(value.toFixed(this.precision));
this.$emit("input", value);
if (!this.dragging && this.value !== this.oldValue) {
this.oldValue = this.value;
}
}
.percent-slider__dot--selected {
position: absolute;
top: -3px;
left: -3px;
width: 18px;
height: 18px;
border-radius: 9px;
background: #24B37A;
}
.percent-slider__dot--selected::after {
content: '';
position: absolute;
width: 14px;
height: 14px;
border-radius: 7px;
border: 2px solid #222E33;
top: 2px;
left: 2px;
box-sizing: border-box;
}
主要是注意 touch相关事件是怎么玩的:
- 首先给相关控件绑定 touch事件,当touch这个div的时候,我们触发onButtonDown方法,这个方法中我们首先把其它杂七杂八默认事件全给取消掉(
event.preventDefault();
),然后识别一下touch事件touchstart
这时候是识别到准备开始拖动了,所以我们需要进行一些状态的改变(this.dragging = true;this.isClick = true;
)并且记录一下开始滑动时候的起始坐标(this.startX = event.clientX;
),绑定识别touchmove、touchend这些事件到对应的方法上面。用户滑动的时候,记录滑动的currentX并计算出滑动的距离,然后计算出所占slider长度的比例。滑动结束onDragEnd 的时候对相关添加绑定监听事件机型移除。
手机端 vue识别手势的时候:event.touches[0].clientY
PC端识别收拾:event.clientY
网友评论