1. 程式人生 > 程式設計 >vue swipeCell滑動單元格(仿微信)的實現示例

vue swipeCell滑動單元格(仿微信)的實現示例

抽離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)':'','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)':'','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,beforeClose: Function,stopPropagation: Boolean,name: {
			type: [Number,default: '',},//
		type:{
			type:[Number,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,});
			}
		},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,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,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滑動單元格內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!