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 复制代码
相关推荐
程序员爱技术1 小时前
Vue 2 + JavaScript + vue-count-to 集成案例
前端·javascript·vue.js
并不会2 小时前
常见 CSS 选择器用法
前端·css·学习·html·前端开发·css选择器
悦涵仙子2 小时前
CSS中的变量应用——:root,Sass变量,JavaScript中使用Sass变量
javascript·css·sass
衣乌安、2 小时前
【CSS】居中样式
前端·css·css3
兔老大的胡萝卜2 小时前
ppk谈JavaScript,悟透JavaScript,精通CSS高级Web,JavaScript DOM编程艺术,高性能JavaScript pdf
前端·javascript
低代码布道师2 小时前
CSS的三个重点
前端·css
耶啵奶膘4 小时前
uniapp-是否删除
linux·前端·uni-app
王哈哈^_^5 小时前
【数据集】【YOLO】【目标检测】交通事故识别数据集 8939 张,YOLO道路事故目标检测实战训练教程!
前端·人工智能·深度学习·yolo·目标检测·计算机视觉·pyqt
cs_dn_Jie6 小时前
钉钉 H5 微应用 手机端调试
前端·javascript·vue.js·vue·钉钉
开心工作室_kaic7 小时前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js