更新
支持 :index 为数值或字符串
演示效果
实现原理
- 需要记录初始位置,滑动方向,是否是滑动(点击不触发touchmove事件)
oldLeft: 0, // 记录初始位置 > 0 显示左边控件 < 0 显示右边控件 0 原始状态
left: 0, // 控制滑动位置
startX: 0, // 记录滑动的初始位置
isTouch: false, //判断是否是滑动
touchStart(e: TouchEvent) {
this.oldLeft = this.left;
this.isTouch = false;
this.startX = e.targetTouches[0].clientX;
},
- 滑动中不能超过可显示的隐藏区域宽度
touchMove(e: TouchEvent) {
this.isTouch = true;
// 获取滑动距离
const diff = e.targetTouches[0].clientX - this.startX;
if (this.oldLeft == 0) {
if (diff < 0) {
// 左滑显示右边控件
this.left = Math.max(diff, -this.getRightWidth());
return;
}
// 右滑显示左边控件
this.left = Math.min(diff, this.getLeftWidth());
return;
}
if (this.oldLeft > 0) {
if (diff > 0) {
// 已显示左边控件,不能继续右滑
return;
}
// 左滑隐藏左边控件
this.left = Math.max(this.oldLeft + diff, 0);
return;
}
if (diff < 0) {
// 已显示右边控件,不能继续左滑
return;
}
// 右滑隐藏右边控件
this.left = Math.min(this.oldLeft + diff, 0);
}
- 滑动结束,判断总滑动距离,不超过1/3自动复原
touchEnd(e: TouchEvent) {
if (!this.isTouch) {
// 点击,并复原
this.animation(this.left, 0);
this.$emit('click');
return;
}
if (this.left == 0) {
return;
}
if (this.left > 0) {
const width = this.getLeftWidth();
this.animation(this.left, this.left * 3 > width ? width : 0);
return;
}
const width = - this.getRightWidth();
this.animation(this.left, this.left * 3 < width ? width : 0);
}
- 点击事件需要复原
关于操作一个隐藏其他实现设想
-
通过父控件记录所有子控件引用,并标记子空间顺序,在父控件调用子元素复原方法或子控件内部调用
-
子控件在初始化的时刻,生成一个自增唯一标识,然后根据这个挂载到父控件或全局属性上
完整代码
<template>
<div class="swipe-row" :style="{left: left + 'px'}">
<div class="actions-left" ref="left">
<slot name="left"></slot>
</div>
<div :class="['swipe-content', name]"
@touchstart='touchStart'
@touchmove='touchMove'
@touchend='touchEnd'>
<slot></slot>
</div>
<div class="actions-right" ref="right">
<slot name="right">
<i class="fa fa-trash" @click="tapRemove"></i>
</slot>
</div>
</div>
</template>
<script lang="ts">
import { Vue, Component, Prop, Emit } from 'vue-property-decorator';
@Component
export default class SwipeRow extends Vue {
@Prop([String, Array]) readonly name!: string| string[];
@Prop([Number, String]) readonly index!: number|string;
oldLeft: number = 0;
left = 0;
startX = 0;
isTouch = false;
leftBox: HTMLDivElement | null = null;
rightBox: HTMLDivElement | null = null;
mounted() {
this.leftBox = this.$refs.left as HTMLDivElement;
this.rightBox = this.$refs.right as HTMLDivElement;
}
getLeftWidth(): number {
if (!this.leftBox) {
return 0;
}
return this.leftBox.clientWidth || this.leftBox.offsetWidth;
}
getRightWidth(): number {
if (!this.rightBox) {
return 0;
}
return this.rightBox.clientWidth || this.rightBox.offsetWidth;
}
tapRemove(item: any) {
this.$emit('remove', item);
}
touchStart(e: TouchEvent) {
this.resetOther();
this.oldLeft = this.left;
this.isTouch = false;
this.startX = e.targetTouches[0].clientX;
}
touchMove(e: TouchEvent) {
this.isTouch = true;
const diff = e.targetTouches[0].clientX - this.startX;
if (this.oldLeft == 0) {
if (diff < 0) {
this.left = Math.max(diff, -this.getRightWidth());
return;
}
this.left = Math.min(diff, this.getLeftWidth());
return;
}
if (this.oldLeft > 0) {
if (diff > 0) {
return;
}
this.left = Math.max(this.oldLeft + diff, 0);
return;
}
if (diff < 0) {
return;
}
this.left = Math.min(this.oldLeft + diff, 0);
}
touchEnd(e: TouchEvent) {
if (!this.isTouch) {
this.animation(this.left, 0);
this.$emit('click');
return;
}
//const diff = e.changedTouches[0].clientX - this.startX;
if (this.left == 0) {
return;
}
if (this.left > 0) {
const width = this.getLeftWidth();
this.animation(this.left, this.left * 3 > width ? width : 0);
return;
}
const width = - this.getRightWidth();
this.animation(this.left, this.left * 3 < width ? width : 0);
}
// 补间动画
animation(
start: number, end: number, endHandle?: Function) {
const diff = start > end ? -1 : 1;
let step = 1;
let handle = setInterval(() => {
start += (step ++) * diff;
if ((diff > 0 && start >= end) || (diff < 0 && start <= end)) {
clearInterval(handle);
this.left = end;
endHandle && endHandle();
return;
}
this.left = start;
}, 16);
}
// 暴露出来给外部调用
public reset() {
if (this.left === 0) {
return;
}
this.animation(this.left, 0);
}
// 复原其他
resetOther() {
if (typeof this.index == 'undefined') {
return;
}
const items: SwipeRow[] = this.$parent.$refs.swiperow as SwipeRow[];
if (!items || items.length < 1) {
return;
}
for (let i = 0; i < items.length; i++) {
if (items[i].index == this.index) {
continue;
}
items[i].reset();
}
}
}
</script>
使用
<div class="swipe-box">
<SwipeRow v-for="(item, index) in items" :key="index" @remove="tapRemove(item)" :index="index" ref="swiperow">
<div>
这是内容
</div>
</SwipeRow>
</div>
样式
.swipe-box {
overflow: hidden;
.swipe-row {
width: 100%;
position: relative;
height: 5rem;
padding: 0;
margin: 0;
transition: left .5s;
.swipe-content {
width: 100%;
display: block;
height: 5rem;
}
.actions-left,
.actions-right {
position: absolute;
height: 5rem;
min-height: 5rem;
max-height: 5rem;
text-align: center;
font-size: 1.375rem;
top: 0;
white-space: nowrap;
.fa {
font-size: 1.875rem;
padding: 1.5625rem 0.9375rem;
}
a {
color: #fff;
text-decoration: none;
}
}
.actions-left {
color: #fff;
background-color: rgb(0, 187, 72);
right: 100%;
}
.actions-right {
color: #fff;
background-color: #BB0000;
left: 100%;
}
}
}
转载请保留原文链接: https://zodream.cn/blog/id/45.html