网站首页> 前端开发> vue.js> vue swipeCell滑动单元格(仿微信)的实现示例

vue swipeCell滑动单元格(仿微信)的实现示例

时间:2020-11-05 08:16:03 阅读:4853次 来源:互联网

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

带有拉动弹性回弹效果

<img alt="vue swipeCell滑动单元格(仿微信)的实现示例" title="vue swipeCell滑动单元格(仿微信)的实现示例 src="https://img.manongw.com/uploads/images/20201105/1604535199600507.png?x-oss-process=style/shuiyin"/>

demo展示:https://littaotao.github.io/me/index(切换为浏览器调试的手机模式并且再次刷新一次)

<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 ref="cellLeft" @click="getClickHandler('left', true)">
<div>收藏</div>
<div>添加</div>
</div> -->
<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 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);
  }
 },
 },
});

引入即可!!!

到此这篇关于vue swipeCell滑动单元格(仿微信)的实现示例的文章就介绍到这了。

本文地址:https://www.manongw.com/article/154.html

文章来源:转载于CSDN,转载网址为https://blog.csdn.net/qq_43476178/article/details/107083010

版权申明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 ezhongheng@126.com 举报,一经查实,本站将立刻删除。

相关文章
  • 本文主要介绍了详解vue 中 scoped 样式作用域的规则的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...
    2020-11-05 08:41
  • 本文主要介绍了html+vue.js 实现漂亮分页功能可兼容IE的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...
    2020-11-08 09:32
  • 本文主要介绍了解决vue watch数据的方法被调用了两次的问题的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...
    2020-11-08 09:37
  • 本文主要介绍了解决Vue watch里调用方法的坑的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...
    2020-11-08 09:11
  • 本文主要介绍了vue实现动态给id赋值,点击事件获取当前点击的元素的id操作的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...
    2020-11-10 09:19
  • 本文主要介绍了vue中使用腾讯云Im的示例的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...
    2020-10-24 10:26
  • 本文主要介绍了Vue 列表页带参数进详情页的操作(router-link)的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...
    2020-11-13 16:21
  • 本文主要介绍了vue.js页面加载执行created,mounted的先后顺序说明的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...
    2020-11-09 09:57
  • 本文主要介绍了vant 中van-list的用法说明的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...
    2020-11-12 11:04
  • 本文主要介绍了vue 如何从单页应用改造成多页应用的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...
    2020-10-23 11:11