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登录弹出层特效
Mar 07 Javascript
javascript中的this详解
Dec 08 Javascript
使用AngularJS实现表单向导的方法
Jun 19 Javascript
js中对函数设置默认参数值的3种方法
Oct 23 Javascript
需灵活掌握的Bootstrap预定义排版类 你精通吗?
Jun 20 Javascript
js多个物体运动功能实例分析
Dec 20 Javascript
vue-cli项目中怎么使用mock数据
Sep 27 Javascript
基于dataset的使用和图片延时加载的实现方法
Dec 11 Javascript
JS计算两个时间相差分钟数的方法示例
Jan 10 Javascript
微信小程序实现星级评分和展示
Jul 05 Javascript
对vue2.0中.vue文件页面跳转之.$router.push的用法详解
Aug 24 Javascript
vue请求本地自己编写的json文件的方法
Apr 25 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
谈一谈收音机的高放电路
2021/03/02 无线电
PHP 工厂模式使用方法
2010/05/18 PHP
PHP开发负载均衡指南
2010/07/17 PHP
FCK调用方法..
2006/12/21 Javascript
jquery插件制作 图片走廊 gallery
2012/08/17 Javascript
JavaScript对象创建及继承原理实例解剖
2013/02/28 Javascript
基于jQuery的判断iPad、iPhone、Android是横屏还是竖屏的代码
2014/05/11 Javascript
使用javascript实现雪花飘落的效果
2015/01/13 Javascript
javascript实现点击商品列表checkbox实时统计金额的方法
2015/05/15 Javascript
基于JavaScript实现网页倒计时自动跳转代码
2015/12/28 Javascript
JS简单设置下拉选择框默认值的方法
2016/08/20 Javascript
针对后台列表table拖拽比较实用的jquery拖动排序
2016/10/10 Javascript
JS实现图片高斯模糊切换效果的焦点图实例
2017/01/21 Javascript
vue实现前端分页完整代码
2020/06/17 Javascript
[01:03:51]2018DOTA2亚洲邀请赛 4.7 淘汰赛 VP vs LGD 第三场
2018/04/09 DOTA
python基础教程之元组操作使用详解
2014/03/25 Python
python中引用与复制用法实例分析
2015/06/04 Python
Python的string模块中的Template类字符串模板用法
2016/06/27 Python
python 删除字符串中连续多个空格并保留一个的方法
2018/12/22 Python
Flask项目中实现短信验证码和邮箱验证码功能
2019/12/05 Python
python中编写函数并调用的知识点总结
2021/01/13 Python
ProBikeKit美国官网:自行车套件,跑步和铁人三项套件
2016/10/13 全球购物
Europcar葡萄牙:葡萄牙汽车和货车租赁
2017/10/13 全球购物
东南亚旅游平台:The Trip Guru
2018/01/01 全球购物
大学生的自我鉴定范文
2014/01/21 职场文书
大四学生找工作的自荐信
2014/03/27 职场文书
学习退步检讨书
2014/09/28 职场文书
作弊检讨书
2015/01/27 职场文书
《我和小伙伴》教学反思
2016/02/20 职场文书
怎样写好工作计划
2019/04/10 职场文书
创业计划书之面包店
2019/09/17 职场文书
pytorch通过训练结果的复现设置随机种子
2021/06/01 Python
Redis入门教程详解
2021/08/30 Redis
深入理解go缓存库freecache的使用
2022/02/15 Golang
Python+tkinter实现高清图片保存
2022/03/13 Python
python数字图像处理之图像的批量处理
2022/06/28 Python