互联网技术 · 2024年2月11日 0

Vue实现仿微信的滑动单元格效果示例

这篇文章主要介绍了vue swipeCell滑动单元格(仿微信)的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

抽离Vant weapp滑动单元格代码改造而成

带有拉动弹性回弹效果

demo展示:

<template>
 <div
  class=”cell_container”
  @touchstart
  v-click-outside=”handleClickOutside”
  @click=”getClickHandler(cell)”>
  <div
   :style=”{transform:
   translateX(+(offset+(isElastic?elasticX:0))+px),transition-duration:dragging?0s:0.6s}”>
   <div
    @touchend=”onClick()”
    :class=”offset?cell_content:cell_content_active”>SwipeCell</div>
   <div ref=”cellRight”
    class=”cell_right”
    @click=”getClickHandler(right, true)”>
    <div
     :class=”type?divPostion:”
     ref=”remove”
     :style=”{background:#ccc,padding-left:10px,padding-right:10+(isElastic?Math.abs(elasticX/3):0)+px,transition-duration:dragging?0s:0.6s}”>标记</div>
    <div
     :class=”type?divPostion:”
     ref=”tag”
     :style=”{transform: type?translateX(+(-offset*removeWidth/cellRightWidth-(isElastic?elasticX/3:0))+px):,padding-left:10px,padding-right:10+(isElastic?Math.abs(elasticX/3):0)+px,transition-duration:dragging?0s:0.6s,background:#000}”>不再关注</div>
    <div
     :class=”type?divPostion:”
     :style=”{transform: type?translateX(+(-offset*(removeWidth+tagWidth)/cellRightWidth-(isElastic?elasticX/3*2:0))+px):,padding-left:10px,padding-right:10+(isElastic?Math.abs(elasticX/3):0)+px,transition-duration:dragging?0s:0.6s}”>删除</div>
   </div>
  </div>
 </div>
</template>
<script>
import ClickOutside from vue-click-outside;
import { TouchMixin } from @/components/mixins/touch;
export default{
name:”SwipeCell”,
props: {
// @deprecated
// should be removed in next major version, use beforeClose instead
onClose: Function,
disabled: Boolean,
leftWidth: [Number, String],
rightWidth: [Number, String],
beforeClose: Function,
stopPropagation: Boolean,
name: {
type: [Number, String],
default: ,
},
// type:{
// type:[Number,String],
// default:1 //0 常规 1 定位
// },
isElastic:{
type:Boolean,
default:true
}
},
data(){
return {
offset: 0,
dragging: true,
//-位移
elasticX:0,
removeWidth:0,
tagWidth:0,
cellRightWidth:0,
cellLeftWidth:0
}
},
computed: {
computedLeftWidth() {
return +this.leftWidth || this.getWidthByRef(cellLeft);
},
computedRightWidth() {
return +this.rightWidth || this.getWidthByRef(cellRight);
},
},
mounted() {
//防止弹性效果影响宽度
this.cellRightWidth = this.getWidthByRef(cellRight);
this.cellLeftWidth = this.getWidthByRef(cellLeft);
this.removeWidth = this.getWidthByRef(remove);
this.tagWidth = this.getWidthByRef(tag);
this.bindTouchEvent(this.$el);
},
mixins: [
TouchMixin
],
directives: {
ClickOutside
},
methods: {
getWidthByRef(ref) {
if (this.$refs[ref]) {
const rect = this.$refs[ref].getBoundingClientRect();
//type=1定位时获取宽度为0,为此采用获取子元素宽度之和
if(!rect.width){
let childWidth = 0;
for(const item of this.$refs[ref].children){
childWidth += item.getBoundingClientRect().width
}
return childWidth;
}
return rect.width;
}
return 0;
},
handleClickOutside(e){
if(this.opened) this.close()
},
// @exposed-api
open(position) {
const offset =
position === left ? this.computedLeftWidth : -this.computedRightWidth;
this.opened = true;
this.offset = offset;
this.$emit(open, {
position,
name: this.name,
// @deprecated
// should be removed in next major version
detail: this.name,
});
},
// @exposed-api
close(position) {
this.offset = 0;
if (this.opened) {
this.opened = false;
this.$emit(close, {
position,
name: this.name,
});
}
},
onTouchStart(event) {
if (this.disabled) {
return;
}
this.startOffset = this.offset;
this.touchStart(event);
},
range(num, min, max) {
return Math.min(Math.max(num, min), max);
},
preventDefault(event, isStopPropagation) {
/* istanbul ignore else */
if (typeof event.cancelable !== boolean || event.cancelable) {
event.preventDefault();
}
if (this.isStopPropagations) {
stopPropagation(event);
}
},
stopPropagations(event) {
event.stopPropagation();
},
onTouchMove(event) {
if (this.disabled) {
return;
}
this.touchMove(event);
if (this.direction === horizontal) {
this.dragging = true;
this.lockClick = true;
const isPrevent = !this.opened || this.deltaX * this.startOffset < 0; if (isPrevent) { this.preventDefault(event, this.stopPropagation); } this.offset = this.range( this.deltaX + this.startOffset, -this.computedRightWidth, this.computedLeftWidth ); //增加弹性 if(this.computedRightWidth && this.offset === -this.computedRightWidth || this.computedLeftWidth && this.offset === this.computedLeftWidth){ this.preventDefault(event, this.stopPropagation); //弹性系数 this.elasticX = (this.deltaX + this.startOffset - this.offset)/4; } }else{ //上下滑动后取消close this.dragging = true; this.lockClick = true; } }, onTouchEnd() { if (this.disabled) { return; } //回弹 this.elasticX = 0 if (this.dragging) { this.toggle(this.offset > 0 ? left : right);
this.dragging = false;
// compatible with desktop scenario
setTimeout(() => {
this.lockClick = false;
}, 0);
}
},
toggle(direction) {
const offset = Math.abs(this.offset);
const THRESHOLD = 0.15;
const threshold = this.opened ? 1 – THRESHOLD : THRESHOLD;
const { computedLeftWidth, computedRightWidth } = this;
if (
computedRightWidth &&
direction === right &&
offset > computedRightWidth * threshold
) {
this.open(right);
} else if (
computedLeftWidth &&
direction === left &&
offset > computedLeftWidth * threshold
) {
this.open(left);
} else {
this.close();
}
},
onClick(position = outside) {
this.$emit(click, position);
if (this.opened && !this.lockClick) {
if (this.beforeClose) {
this.beforeClose({
position,
name: this.name,
instance: this,
});
} else if (this.onClose) {
this.onClose(position, this, { name: this.name });
} else {
this.close(position);
}
}
},
getClickHandler(position, stop) {
return (event) => {
if (stop) {
event.stopPropagation();
}
this.onClick(position);
};
},
}
}
</script>
<style lang=”stylus” scoped>
.cell_container{
position: relative;
overflow: hidden;
line-height: 68px;
height:68px;
div{
height: 100%;
.cell_content{
height: 100%;
width: 100%;
text-align: center;
}
.cell_content_active{
height: 100%;
width: 100%;
text-align: center;
&:active{
background: #e8e8e8;
}
}
.cell_left,.cell_right{
position: absolute;
top: 0;
height: 100%;
display: flex;
color: #fff;
.divPostion{
position: absolute;
}
div{
white-space:nowrap;
display: flex;
align-items: center;
background: #ccc;
}
}
.cell_left{
left: 0;
transform:translateX(-100%);
}
.cell_right{
right: 0;
transform:translateX(100%);
}
}
}
</style>
touch.js
import Vue from vue;
export const isServer=false;
const MIN_DISTANCE = 10;
const TouchMixinData = {
startX: Number,
startY: Number,
deltaX: Number,
deltaY: Number,
offsetX: Number,
offsetY: Number,
direction: String
};

function getDirection(x,y) {
if (x > y && x > MIN_DISTANCE) {
return horizontal;
}
if (y > x && y > MIN_DISTANCE) {
return vertical;
}
return ;
}

export let supportsPassive = false;

export function on(
target,
event,
handler,
passive = false
) {
if (!isServer) {
target.addEventListener(
event,
handler,
supportsPassive ? { capture: false, passive } : false
);
}
}

export const TouchMixin = Vue.extend({
data() {TouchMixinData
return { direction: } ;
},
methods: {
touchStart() {
this.resetTouchStatus();
this.startX = event.touches[0].clientX;
this.startY = event.touches[0].clientY;
},
touchMove() {
const touch = event.touches[0];
this.deltaX = touch.clientX – this.startX;
this.deltaY = touch.clientY – this.startY;
this.offsetX = Math.abs(this.deltaX);
this.offsetY = Math.abs(this.deltaY);
this.direction =
this.direction || getDirection(this.offsetX, this.offsetY);
},
resetTouchStatus() {
this.direction = ;
this.deltaX = 0;
this.deltaY = 0;
this.offsetX = 0;
this.offsetY = 0;
},
// avoid Vue 2.6 event bubble issues by manually binding events
//https://github.com/youzan/vant/issues/3015
bindTouchEvent( el ) {
const { onTouchStart, onTouchMove, onTouchEnd } = this;
on(el, touchstart, onTouchStart);
on(el, touchmove, onTouchMove);
if (onTouchEnd) {
on(el, touchend, onTouchEnd);
on(el, touchcancel, onTouchEnd);
}
},
},
});
引入即可!!!
来源:脚本之家
链接:https://www.jb51.net/article/195575.htm