使用vue实现左滑删除的功能,效果图如下:
上代码:SlideToDel组件(子组件)代码:
<template>
<div class="delete">
<div class="slider">
<div
class="content"
@touchstart="touchStart"
@touchmove="touchMove"
@touchend="touchEnd"
:style="deleteSlider"
>
<!-- 插槽中放具体项目中需要内容 -->
<slot></slot>
</div>
<!-- 左滑之后右侧显示的内容按钮 -->
<div class="remove" ref="remove">
<p v-for="item in handles" :key="item.id" @click="onClick(item.id)">{{item.title}}</p>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
// 当前组件的id
sliderId: {
},
// 父组件当前移动的组件id
curSlideID: {
},
sensitivity: { // 灵敏度(1 ~ 10)1 => 最细腻(跟手) 10 => 最灵敏
type: Number,
default: 5
},
handles: {
type: Array,
default: [{title: '删除', id: 0}]
}
},
data() {
return {
startX: 0, //触摸开始位置
endX: 0, //结束位置
moveX: 0, //滑动时的位置
disX: 0, //移动距离
deleteSlider: "", //滑动时的效果
wd: 0 // 左滑后显示出来的remove元素的宽度,设定为最大左滑距离
};
},
mounted() {
this.ready();
// 左滑后显示出来的remove元素的宽度,设定为最大左滑距离
this.wd = this.$refs.remove.offsetWidth
if (this.handles.length > 3) {
throw new Error('handles more than 3')
}
this.sensitivity = this.sensitivity > 10 ? 10 : (this.sensitivity < 1 ? 5 : this.sensitivity)
},
methods: {
touchStart(ev) {
ev = ev || event;
//tounches类数组,等于1时表示此时有只有一只手指在触摸屏幕(在touchend时,此属性为空)
if (ev.touches.length == 1) {
// 记录开始位置
this.startX = ev.touches[0].clientX;
}
},
touchMove(ev) {
ev = ev || event;
if (ev.touches.length == 1) {
// 滑动时距离浏览器左侧实时距离
this.moveX = ev.touches[0].clientX;
//起始位置减去 实时的滑动的距离,得到手指实时偏移距离
this.disX = this.startX - this.moveX;
// 如果是向右滑动或者不滑动,不改变滑块的位置
if (this.disX < 0 || this.disX == 0) {
this.deleteSlider = "transform:translateX(0px)";
// 大于0,表示左滑了,此时滑块开始滑动
} else if (this.disX > 0) {
// 告知父组件当前向左滑动的组件id
this.$emit("onSlide", this.sliderId);
//具体滑动距离取的是 手指偏移距离 * sensitivity。
this.deleteSlider = "transform:translateX(-" + this.disX * this.sensitivity + "px)";
// 最大也只能等于删除按钮宽度
if (this.disX * this.sensitivity >= this.wd) {
this.deleteSlider = "transform:translateX(-" + this.wd + "px)";
}
}
}
},
touchEnd(ev) {
ev = ev || event;
// touchEnd获取值为changedTouches,因为当touchend时,touches的值会被清空,详情可参考https://www.cnblogs.com/mengff/p/6005516.html
if (ev.changedTouches.length == 1) {
let endX = ev.changedTouches[0].clientX;
this.disX = this.startX - endX;
//如果距离小于删除按钮一半,强行回到起点
if (this.disX * this.sensitivity < this.wd / 2) {
this.deleteSlider = "transform:translateX(0px)";
} else {
//大于一半 滑动到最大值
this.deleteSlider = "transform:translateX(-" + this.wd + "px)";
}
}
},
ready() {
document.addEventListener("click", (e) => {
// this.$el.contains(e.target) === 除了自己意外的其他元素,此处的作用是当用户点击其他组件,隐藏当前左滑的组件
if (!this.$el.contains(e.target) && this.disX !== 0) {
this.deleteSlider = "transform:translateX(-" + "0px)";
}
});
},
onClick(clickId) {
// 将用户点击的操作id返回出去
this.$emit("onClick", clickId);
}
},
watch: {
// 监听父组件的curSlideID值,和自己的sliderId比较,用以隐藏左滑的组件
curSlideID(newVal, oldVal) {
if (this.curSlideID !== this.sliderId && this.disX !== 0) {
this.deleteSlider = "transform:translateX(-" + "0px)";
}
},
},
};
</script>
<style>
.delete {
height: 100%;
}
.slider {
width: 100%;
height: 100%;
position: relative;
user-select: none;
}
.content {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: #ffffff;
z-index: 100;
transition: 0.3s;
}
.remove {
position: absolute;
min-width: 60px;
max-width: 150px;
height: 100%;
background: red;
right: 0;
top: 0;
color: #fff;
text-align: center;
font-size: 16px;
}
.remove p {
min-width: 50px;
max-width: 60px;
height: 100%;
float: left;
}
.remove > p:first-child {
background: #5a6bfc
}
.remove > p:nth-child(2) {
background: rgb(58, 235, 82);
}
.remove > p:last-child {
background: none;
}
</style>
父组件代码:
<template>
<div id="app">
<div class="liDemo" v-for="item in list" :key="item.imgUrl">
<SlideToDel
@onClick="onClick"
@onSlide="onSlide"
:sliderId="item.id"
:curSlideID="curSlideID"
:handles="handles"
:sensitivity="sensitivity"
>
<div class="df fg1 f-ac">
<img :src="item.imgUrl" alt="" class="df f-js img" />
<p class="df fg1">{{ item.title }}</p>
</div>
</SlideToDel>
</div>
</div>
</template>
<script>
import SlideToDel from "@/components/SlideToDel.vue";
export default {
name: "App",
components: {
SlideToDel,
},
data: function () {
return {
list: [
{
imgUrl:
"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1608116221510&di=694852c91e552e68a670f82815d5ad97&imgtype=0&src=http%3A%2F%2Fattach.bbs.miui.com%2Fforum%2F201305%2F30%2F220025pxfkhykvkgkvuktq.jpg",
title: 0,
id: 0,
},
{
imgUrl:
"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1608122319754&di=baef1cc964e443464c02ea2ac9588b63&imgtype=0&src=http%3A%2F%2Fattach.bbs.miui.com%2Fforum%2F201408%2F07%2F213601f2xz7usscm2z1mjh.jpg",
title: 1,
id: 1,
},
{
imgUrl:
"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1608122326450&di=65f10f53b82d30037ce3c4bf17b4068c&imgtype=0&src=http%3A%2F%2Ff.hiphotos.baidu.com%2Fzhidao%2Fpic%2Fitem%2F241f95cad1c8a7863cb5bacd6709c93d71cf5052.jpg",
title: 2,
id: 2,
},
{
imgUrl:
"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1608122346127&di=b2e344cb29f3cd4b725b3b4bd18deb76&imgtype=0&src=http%3A%2F%2Fbenyouhuifile.it168.com%2Fforum%2F201304%2F06%2F11435052yrezzae1bua8ee.jpg",
title: 3,
id: 3,
},
{
imgUrl:
"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1608122467284&di=3d3684c0fcd46a4936a375fb841929dc&imgtype=0&src=http%3A%2F%2Fattach.bbs.miui.com%2Fforum%2F201403%2F01%2F134104shkenreehh21nol1.jpg",
title: 4,
id: 4,
},
{
imgUrl:
"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1608122469971&di=c54f426772a69fa37150fce4cc1f5e68&imgtype=0&src=http%3A%2F%2Fattachments.gfan.com%2Fforum%2Fattachments2%2Fday_100430%2F10043015437962673bf6152eff.jpg",
title: 5,
id: 5,
},
{
imgUrl:
"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1608122484081&di=215184f79d0ecc8597ea5c14f4bbb641&imgtype=0&src=http%3A%2F%2Fimg4.imgtn.bdimg.com%2Fit%2Fu%3D3579831024%2C633721272%26fm%3D214%26gp%3D0.jpg",
title: 6,
id: 6,
}
],
nihao: "bushiba",
curSlideID: null,
handles: [
{ title: "回复", id: 0 },
// { title: "置顶", id: 1 },
{ title: "删除", id: 2 },
],
sensitivity: 3
};
},
methods: {
onClick(val) {// 监听用户操作的,返回的val和handles对应
console.log("csx click -> " + val);
},
onSlide(val) {// 监听当前滑动的哪个slider组件
console.log("csx onSlide val:", val);
this.curSlideID = val;
},
},
};
</script>
<style>
* {
margin: 0;
padding: 0;
}
html,
body,
#app {
height: 100%;
}
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
background: rgba(220, 220, 220);
}
ul,
li {
list-style-type: none;
margin: 0;
padding: 0;
}
.liDemo {
color: #000;
margin-bottom: 3px;
height: 60px;
line-height: 60px;
}
.img {
width: 20%;
height: auto;
margin: 5px;
}
.df {
display: flex;
display: -webkit-flex;
}
.fg1 {
flex-grow: 1;
}
.f-ac {
align-content: center;
-webkit-align-content: center;
}
.f-js {
justify-content: flex-start;
-webkit-justify-content: flex-start;
}
.f-jc {
justify-content: center;
-webkit-justify-content: center;
}
</style>
优化:
1.独立出来当作组件,使用者可以自定义滑动的灵敏度,滑动出现的操作数目等等;
2.使用父子组件传值的方式实现隐藏当前滑块的功能,避免引入vuex,使其模块化更好
完整代码请见Vue-SlideToDel
==================================================================================================================================================
此贴为转载贴,感谢@a57521大牛的文章
文章链接:https://blog.csdn.net/weixin_42618523/article/details/102719197?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control