Uniapp中实现可拖拽元素并抽离公共方法

实现目的

日常前端开发经常会遇到悬浮按钮操作,悬浮按钮偶尔会遇到遮挡底部页面内容的情况,而使用scroll滚动控制按钮呈现会因频繁触发滚动导致性能损耗,因此自定义拖动方法,旨在用户需要时使用。

效果呈现

公共方法

首先定义事件类

javascript 复制代码
/**
 * 事件类
 */
class EventDispatcher {
  constructor() {
    this._listeners = {};
    this.ds = this.dispatchEvent;
    this.trigger = this.dispatchEvent;
    this.emit = this.dispatchEvent;
    this.off = this.removeEventListener;
    this.hs = this.hasEventListener;
    this.on = this.addEventListener;
  }
  /**
   * 添加监听事件
   * @param {string} type 事件标识
   * @param {function} listener 事件执行函数
   * @param {*} [context=this]  监听方法内的this上下文关系
   */
  addEventListener(type, listener, context) {
    if (this._listeners === undefined) this._listeners = {};

    var _listeners = this._listeners;

    if (_listeners[type] === undefined) {
      _listeners[type] = [];
    }

    if (_listeners[type].indexOf(listener) === -1) {
      if (context !== undefined) listener._ds_context = context;

      _listeners[type].push(listener);
    }
  }
  /**
   * 添加监听事件,执行一次就销毁
   * @param {string} type 事件标识
   * @param {function} listener 事件执行函数
   * @param {*} [context=this]  监听方法内的this上下文关系
   */
  once(type, listener, context) {
    listener.once = true;
    this.addEventListener(type, listener, context);
  }
  /**
   * 判断是否监听指定事件

   * @param {string } type  事件标识
   * @param {function} listener 事件执行函数
   * @returns {boolean}
   */
  hasEventListener(type, listener) {
    if (this._listeners === undefined) return false;

    var _listeners = this._listeners;

    return (
      _listeners[type] !== undefined &&
      _listeners[type].indexOf(listener) !== -1
    );
  }
  /**
   * 删除监听事件
   * @param type 事件标识
   * @param listener  时间执行函数
   */
  removeEventListener(type, listener) {
    if (this._listeners === undefined) return;

    var _listeners = this._listeners;
    var _listenerArray = _listeners[type];

    if (_listenerArray !== undefined) {
      var _index = _listenerArray.indexOf(listener);

      if (_index !== -1) {
        _listenerArray.splice(_index, 1);
      }
    }
  }
  /**
   * 派发事件
   * @param {*} event
   */
  dispatchEvent(event) {
    if (this._listeners === undefined) return;

    if (typeof event === "string") event = { type: event };

    var _listeners = this._listeners;
    var _listenerArray = _listeners[event.type];

    if (_listenerArray !== undefined) {
      event.target = this;
      var _array = _listenerArray.slice(0);
      for (var i = 0, l = _array.length; i < l; i++) {
        // console.log('--->_array[i]:',_array[i],i,_array[i].call);
        // console.log(_array[i]._ds_context,this);
        if (_array[i]._ds_context) _array[i].call(_array[i]._ds_context, event);
        else _array[i].call(this, event);

        if (_array[i].once) {
          this.removeEventListener(event.type, _array[i]);
        }
      }
    }
  }
  /**
   * 删除所有的事件监听
   */
  removeAllEventListeners() {
    this._listeners = {};
  }
}

export default EventDispatcher;

用事件类定义touch操作类

javascript 复制代码
import EventDispatcher from "./EventDispatcher"; // 引入上面定义的事件类

/**
 * 手势事件处理
 */
class TouchGestures extends EventDispatcher {
	constructor() {
		super();

		this.preV = { x: null, y: null };
		this.pinchStartLen = null;
		this.scale = 1;
		this.isDoubleTap = false;

		this.delta = null;
		this.last = null;
		this.now = null;
		this.tapTimeout = null;
		this.singleTapTimeout = null;
		this.longTapTimeout = null;
		this.swipeTimeout = null;
		this.x1 = this.x2 = this.y1 = this.y2 = null;
		this.preTapPosition = { x: null, y: null };
	}

	touchstart(evt) {
		if (!evt.touches) return;
		this.now = Date.now();
		this.x1 = evt.touches[0].x ?? evt.touches[0].pageX;
		this.y1 = evt.touches[0].y ?? evt.touches[0].pageY;
		this.delta = this.now - (this.last || this.now);
		// console.log("touchStart", evt.touches, this.x1, this.y1);
		this.ds({ type: "touchStart", evt });
		if (this.preTapPosition.x !== null) {
			this.isDoubleTap =
				this.delta > 0 && this.delta <= 250 && Math.abs(this.preTapPosition.x - this.x1) < 30 && Math.abs(this.preTapPosition.y - this.y1) < 30;
		}
		this.preTapPosition.x = this.x1;
		this.preTapPosition.y = this.y1;
		this.last = this.now;
		let preV = this.preV,
			len = evt.touches.length;
		if (len > 1) {
			this._cancelLongTap();
			this._cancelSingleTap();
			let otx = evt.touches[1].x ?? evt.touches[1].pageX;
			let oty = evt.touches[1].y ?? evt.touches[1].pageY;
			let v = { x: otx - this.x1, y: oty - this.y1 };
			preV.x = v.x;
			preV.y = v.y;
			this.pinchStartLen = getLen(preV);
			// console.log('multipointStart');
			this.ds({ type: "multipointStart", evt });
		}
		this.longTapTimeout = setTimeout(
			function() {
				// console.log('longTap');
				this.ds({ type: "longTap", evt });
			}.bind(this),
			750
		);
	}

	touchmove(evt) {
		if (!evt.touches) return;
		let preV = this.preV,
			len = evt.touches.length,
			currentX = evt.touches[0].pageX ?? evt.touches[0].pageX,
			currentY = evt.touches[0].pageY ?? evt.touches[0].pageY;
		this.isDoubleTap = false;
		if (len > 1) {
			let otx = evt.touches[1].pageX ?? evt.touches[1].pageX;
			let oty = evt.touches[1].pageY ?? evt.touches[1].pageY;
			let v = { x: otx - currentX, y: oty - currentY };

			if (preV.x !== null) {
				if (this.pinchStartLen > 0) {
					evt.scale = getLen(v) / this.pinchStartLen;
					// console.log('pinch', evt.scale);
					this.ds({ type: "pinch", evt, scale: evt.scale });
				}

				evt.angle = getRotateAngle(v, preV);
				// console.log('rotate', evt.angle);
				this.ds({ type: "rotate", evt, angle: evt.angle });
			}
			preV.x = v.x;
			preV.y = v.y;
		} else {
			if (this.x2 !== null) {
				evt.deltaX = currentX - this.x2;
				evt.deltaY = currentY - this.y2;
			} else {
				evt.deltaX = 0;
				evt.deltaY = 0;
			}
			// console.log("pressMove", currentY,this.y2,evt.deltaY);
			this.ds({ type: "pressMove", evt });
		}

		// console.log('touchMove');
		this.ds({ type: "touchMove", evt });

		this._cancelLongTap();
		this.x2 = currentX;
		this.y2 = currentY;
		if (len > 1) {
			// evt.preventDefault();
		}
	}

	touchend(evt) {
		if (!evt.touches) return;
		this._cancelLongTap();
		let self = this;
		if (evt.touches.length < 2) {
			// console.log('multipointEnd');
			this.ds({ type: "multipointEnd", evt });
		}
		// console.log('touchEnd');
		this.ds({ type: "touchEnd", evt });
		//swipe
		
		if ((this.x2 && Math.abs(this.x1 - this.x2) > 30) || (this.y2 && Math.abs(this.y1 - this.y2) > 30)) {
			evt.direction = this._swipeDirection(this.x1, this.x2, this.y1, this.y2);
			this.swipeTimeout = setTimeout(function() {
				// console.log('swipe');
				self.ds({ type: "swipe", evt, direction: evt.direction });
			}, 0);
		} else {
			this.tapTimeout = setTimeout(function() {
				// console.info("tap");
				self.ds({ type: "tap", evt });
				// trigger double tap immediately
				if (self.isDoubleTap) {
					// console.log('doubleTap');
					self.ds({ type: "doubleTap", evt });
					clearTimeout(self.singleTapTimeout);
					self.isDoubleTap = false;
				}
			}, 0);

			if (!self.isDoubleTap) {
				self.singleTapTimeout = setTimeout(function() {
					// console.log('singleTap');
					self.ds({ type: "singleTap", evt });
				}, 250);
			}
		}

		this.preV.x = 0;
		this.preV.y = 0;
		this.scale = 1;
		this.pinchStartLen = null;
		this.x1 = this.x2 = this.y1 = this.y2 = null;
	}

	touchcancel(evt) {
		clearTimeout(this.singleTapTimeout);
		clearTimeout(this.tapTimeout);
		clearTimeout(this.longTapTimeout);
		clearTimeout(this.swipeTimeout);
		// console.log('touchCancel');
		this.ds({ type: "touchCancel", evt });
	}

	_cancelLongTap() {
		clearTimeout(this.longTapTimeout);
	}

	_cancelSingleTap() {
		clearTimeout(this.singleTapTimeout);
	}

	_swipeDirection(x1, x2, y1, y2) {
		return Math.abs(x1 - x2) >= Math.abs(y1 - y2) ? (x1 - x2 > 0 ? "left" : "right") : y1 - y2 > 0 ? "up" : "down";
	}

	destroy() {
		if (this.singleTapTimeout) clearTimeout(this.singleTapTimeout);
		if (this.tapTimeout) clearTimeout(this.tapTimeout);
		if (this.longTapTimeout) clearTimeout(this.longTapTimeout);
		if (this.swipeTimeout) clearTimeout(this.swipeTimeout);

		this.element.removeEventListener("touchstart", this.start);
		this.element.removeEventListener("touchmove", this.move);
		this.element.removeEventListener("touchend", this.end);
		this.element.removeEventListener("touchcancel", this.cancel);

		this.preV = this.pinchStartLen = this.scale = this.isDoubleTap = this.delta = this.last = this.now = this.tapTimeout = this.singleTapTimeout = this.longTapTimeout = this.swipeTimeout = this.x1 = this.x2 = this.y1 = this.y2 = this.preTapPosition = this.rotate = this.touchStart = this.multipointStart = this.multipointEnd = this.pinch = this.swipe = this.tap = this.doubleTap = this.longTap = this.singleTap = this.pressMove = this.touchMove = this.touchEnd = this.touchCancel = null;

		return null;
	}
}

function getLen(v) {
	return Math.sqrt(v.x * v.x + v.y * v.y);
}

function dot(v1, v2) {
	return v1.x * v2.x + v1.y * v2.y;
}

function getAngle(v1, v2) {
	let mr = getLen(v1) * getLen(v2);
	if (mr === 0) return 0;
	let r = dot(v1, v2) / mr;
	if (r > 1) r = 1;
	return Math.acos(r);
}

function cross(v1, v2) {
	return v1.x * v2.y - v2.x * v1.y;
}

function getRotateAngle(v1, v2) {
	let angle = getAngle(v1, v2);
	if (cross(v1, v2) > 0) {
		angle *= -1;
	}

	return (angle * 180) / Math.PI;
}

export default TouchGestures;

业务代码中使用

html 复制代码
<template>
     <!-- 使用transform做偏移量呈现 -->
     <view class="default-class" :style="{transform:`translateY(${OffsetY}px)`}" @click="xxx" @touchstart="touchstart" @touchmove.stop.prevent="touchmove" @touchend="touchend" @touchcancel="touchcancel">批量继承</view>
</template>

<script>
import TouchGestures from "@/common/TouchGestures";

const touchGestures = new TouchGestures();

export default {
     data() {
        return {
            OffsetY: 0, // 偏移量
        }
     },
     methods:{
        // touch操作
        touchstart(event) {
            touchGestures.touchstart(event);
        },
        touchmove(event) {
            touchGestures.touchmove(event);
        },
        touchend(event) {
            touchGestures.touchend(event);
        },
        touchcancel(event) {
            touchGestures.touchcancel(event);
        },
     },
     onReady() {
        touchGestures.on("pressMove", ({ evt: { deltaY } }) => {
            //evt.deltaX和evt.deltaY代表在屏幕上移动的距离,evt.target可以用来判断点击的对象
            // console.log("deltaY :>> ", deltaY);
            this.OffsetY += deltaY;
        });
    },
}
</script>
<style lang="less" scoped>
    .default-class{
        position: fixed;
        right: 40rpx;
        bottom: 160rpx;
        font-size: 28rpx;
        border-radius: 50%;
        width: 80rpx;
        height: 80rpx;
        display: flex;
        justify-content: center;
        align-items: center;
        padding: 10rpx;
        text-align: center;
    }
</style>
javascript 复制代码
相关推荐
WeiXiao_Hyy1 分钟前
成为 Top 1% 的工程师
java·开发语言·javascript·经验分享·后端
吃杠碰小鸡18 分钟前
高中数学-数列-导数证明
前端·数学·算法
kingwebo'sZone23 分钟前
C#使用Aspose.Words把 word转成图片
前端·c#·word
xjt_090143 分钟前
基于 Vue 3 构建企业级 Web Components 组件库
前端·javascript·vue.js
我是伪码农1 小时前
Vue 2.3
前端·javascript·vue.js
夜郎king1 小时前
HTML5 SVG 实现日出日落动画与实时天气可视化
前端·html5·svg 日出日落
辰风沐阳1 小时前
JavaScript 的宏任务和微任务
javascript
夏幻灵2 小时前
HTML5里最常用的十大标签
前端·html·html5
冰暮流星2 小时前
javascript之二重循环练习
开发语言·javascript·数据库
Mr Xu_3 小时前
Vue 3 中 watch 的使用详解:监听响应式数据变化的利器
前端·javascript·vue.js