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


Posted in Javascript onSeptember 14, 2020

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

带有拉动弹性回弹效果

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

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" class="cell_left" @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 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);
  }
 },
 },
});

引入即可!!!

到此这篇关于vue swipeCell滑动单元格(仿微信)的实现示例的文章就介绍到这了,更多相关vue swipeCell滑动单元格内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
js模拟实现Array的sort方法
Dec 11 Javascript
清除网页历史记录,屏蔽后退按钮!
Dec 22 Javascript
jquery 学习之二 属性相关
Nov 23 Javascript
使用jQuery清空file文件域的解决方案
Apr 12 Javascript
jCallout 轻松实现气泡提示功能
Sep 22 Javascript
Javascript定义类(class)的三种方法详解
Mar 13 Javascript
javascript关于继承解析
May 10 Javascript
vue router路由嵌套不显示问题的解决方法
Jun 17 Javascript
webstrom Debug 调试vue项目的方法步骤
Jul 17 Javascript
微信小程序实现图片上传
May 23 Javascript
JavaScript异步操作的几种常见处理方法实例总结
May 11 Javascript
node脚手架搭建服务器实现token验证的方法
Jan 20 Javascript
JavaScript 如何计算文本的行数的实现
Sep 14 #Javascript
JavaScript实现串行请求的示例代码
Sep 14 #Javascript
浅谈JavaScript 声明提升
Sep 14 #Javascript
详解vue 中 scoped 样式作用域的规则
Sep 14 #Javascript
详解JavaScript 高阶函数
Sep 14 #Javascript
vue实现简单计算商品价格
Sep 14 #Javascript
Vue中添加滚动事件设置的方法详解
Sep 14 #Javascript
You might like
php header Content-Type类型小结
2011/07/03 PHP
PHP 字符串长度判断效率更高的方法
2014/03/02 PHP
PHP include任意文件或URL介绍
2014/04/29 PHP
解密ThinkPHP3.1.2版本之模板继承
2014/06/19 PHP
PHP简单判断字符串是否包含另一个字符串的方法
2016/03/25 PHP
thinkPHP分组后模板无法加载问题解决方法
2016/07/12 PHP
PHP递归获取目录内所有文件的实现方法
2016/11/01 PHP
PHP设计模式之工厂模式(Factory Pattern)的讲解
2019/03/21 PHP
js 获取服务器控件值的代码
2010/03/05 Javascript
浅析javascript 定时器
2014/12/23 Javascript
JQuery控制div外点击隐藏而div内点击不会隐藏的方法
2015/01/13 Javascript
JS清除选择内容的方法
2015/01/29 Javascript
jQuery及JS实现循环中暂停的方法
2015/02/02 Javascript
浅谈js中对象的使用
2016/08/11 Javascript
JavaScript实现输入框与清空按钮联动效果
2016/09/09 Javascript
js html css实现复选框全选与反选
2016/10/09 Javascript
AngularJS入门教程之MVC架构实例分析
2016/11/01 Javascript
JS填写银行卡号每隔4位数字加一个空格
2016/12/19 Javascript
深入浅析vue中cross-env的使用
2019/09/12 Javascript
微信小程序实现watch监听
2020/06/04 Javascript
微信小程序实现多行文字滚动
2020/11/18 Javascript
利用Python和OpenCV库将URL转换为OpenCV格式的方法
2015/03/27 Python
Python的mysql数据库的更新如何实现
2017/07/31 Python
Python批量发送post请求的实现代码
2018/05/05 Python
Python面向对象总结及类与正则表达式详解
2019/04/18 Python
解决pycharm最左侧Tool Buttons显示不全的问题
2019/12/17 Python
全球最大的中文旅行网站:去哪儿网
2017/11/16 全球购物
阿迪达斯中国官网:Adidas中国
2020/12/14 全球购物
如何用SQL语句进行模糊查找
2015/09/25 面试题
美发店5.1活动方案
2014/01/24 职场文书
医生爱岗敬业演讲稿
2014/08/26 职场文书
基层党组织整改方案
2014/10/25 职场文书
2014年餐厅服务员工作总结
2014/11/18 职场文书
2014年社区矫正工作总结
2014/11/18 职场文书
解决mysql模糊查询索引失效问题的几种方法
2021/06/18 MySQL
python脚本框架webpy的url映射详解
2021/11/20 Python