因为自己的小站首页需要一个可以滑动切换的显示内容的功能,本来是用的swiper,但是坑,BUG实在是太多了,实在是搞不明白了,就这么个小功能还是决定自己写一个组件。
直接贴代码,基本都含有注释
<template>
<div
class="touch_tab_container"
ref="TabContainerEl"
:style="{
height:height+'px',
}"
@touchmove="(e)=>{
mouseMove(e.touches[0]);
}"
@touchstart="(e)=>{
onMouseDown(e.touches[0]);
}"
@touchend="(e)=>{
onMouseUp(e.touches[0]);
}"
@mousemove="mouseMove"
@mouseleave="onMouseLeave"
@mousedown="onMouseDown"
@mouseup="onMouseUp">
<div
:class="{
container:true,
animation:animation,
}"
ref="ContainerEl"
:style="{
transform:`translateX(${translateX}px)`,
}">
<slot></slot>
</div>
</div>
</template>
<script>
//自定义touchTab组件
export default {
name:'TouchTab',
data() {
return {
height:0, //总容器高度
TabContainerEl:undefined, //总容器元素
ContainerEl:undefined,
tabItemEls:[],
mouseDown:false, //鼠标是否按下
translateX:0, //容器的偏移量
animation:false, //是否开启容器动画
activeIndex:0,
speed:0, //移动速度
};
},
computed:{
translateX_:{ //translateX的代理层
set(newValue){
if(!this.ContainerEl) return;
if(newValue > 0) return;
if(newValue < this.TabContainerEl.clientWidth-this.ContainerEl.clientWidth) return;
this.translateX = newValue;
if(this.mouseDown){ //如果鼠标按下的话计算移动速率
let oldTranslateX = this.oldTranslateX || newValue;
let oldDate = this.oldDate || new Date().getTime();
let newTranslateX = newValue;
let newDate =new Date().getTime();
this.speed = Math.abs((newTranslateX - oldTranslateX)/(newDate - oldDate));
this.oldTranslateX = newTranslateX;
this.oldDate = newDate;
}
},
get(){
return this.translateX;
},
},
},
watch:{
activeIndex:{ //当前选项发生变化时向外部暴露当前显示的选项索引
handler(newVlaue){
this.$emit('onChange',newVlaue);
},
immediate:true,
},
},
mounted(){
this.TabContainerEl = this.$refs.TabContainerEl;
this.ContainerEl = this.$refs.ContainerEl;
this.tabItemEls = this.$refs.ContainerEl.children;
this.setActiveEl(this.activeIndex); //设置初始显示item
const that = this;
requestAnimationFrame(function func(){ //每一帧都计算样式
that.computeStyle();
requestAnimationFrame(func);
});
},
methods:{
mouseMove(e){ //鼠标移动事件
if(!this.mouseDown) return; //必须是鼠标按下
this.animation = false;
let clientX = e.clientX - this.lastEnven.clientX;
this.translateX_ = this.translateX_ + clientX;
this.lastEnven = e;
},
onMouseLeave(){ //鼠标移出事件
this.mouseDown = false;
this.lastEnven = undefined;
this.computeIndex();
},
onMouseDown(e){ //鼠标按下事件
this.mouseDown = true;
this.lastEnven = e;
},
onMouseUp(){ //鼠标抬起事件
this.mouseDown = false;
this.lastEnven = undefined;
this.computeIndex();
},
computeStyle(){ //计算样式
if(this.tabItemEls.length>0){
this.tabItemEls.forEach(el => { //设置子元素的宽度与总容器宽度一致
el.style.width = this.TabContainerEl.clientWidth+'px';
el.style.minWidth = this.TabContainerEl.clientWidth+'px';
});
this.height = this.tabItemEls[this.activeIndex].clientHeight;
}
},
computeIndex(){ //计算索引位置
let itemWidth = this.TabContainerEl.clientWidth;
let translateX = Math.abs(this.translateX_);
let width_ = itemWidth*this.activeIndex - translateX;
let direction = width_>0?'left':'right';
width_ = Math.abs(width_);
if(width_>(itemWidth/2)){ //如果滑动超过了一半
this.setActiveEl(
direction=='right'?this.activeIndex+1:this.activeIndex-1,
);
}else if(this.speed>0.5){ //表示没有超过一半,可以通过滑动速率和方向判断
this.setActiveEl(
direction=='right'?this.activeIndex+1:this.activeIndex-1,
);
}else{
this.setActiveEl(this.activeIndex);
}
this.speed = 0;
},
setActiveEl(index){ //设置当前显示的item(可外部调用)
this.animation = true;
if(index<=0){
this.activeIndex = 0;
}else if(index>(this.tabItemEls.length-1)){
this.activeIndex = this.tabItemEls.length-1;
}else{
this.activeIndex = index;
}
this.translateX_ = -this.TabContainerEl.clientWidth * this.activeIndex;
},
},
}
</script>
<style lang="scss" scoped>
.touch_tab_container{
width: 100%;
margin: 0 auto;
background-color: aquamarine;
border-radius: 5px;
height: fit-content;
overflow: hidden;
transition: height 0.2s;
>.container{
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
width:fit-content;
display: flex;
transform: translateX(0px);
>.item{
min-width: 0;
width: 0;
height: fit-content;
}
}
.animation{
transition: all 0.2s;
transition-timing-function: cubic-bezier(0.69, 0.05, 0.24, 1.01);
}
}
</style>
使用方法
<myComponent
ref="myComponent"
@onChange="onChange">
</myComponent>
//直接设置显示那个选项
this.$refs.myComponent.setActiveEl(1);