Gesture 手势兼容
前言
做终端自适应除了通过css让布局自动布局,还有一些必须使用js才能解决的兼容性问题,比如在pc端可以使用hover伪类显示菜单,在移动端则不可以;再比如pc端可使用dblclick事件,移动端则不行,等等。
在终端自适应方案中,兼容手势是一个很重要的课题,我们通过axGesture插件做了如下兼容:
- 通过touchstart/move/end和mousedown/move/up的对应关系,实现click兼容事件
- 通过监测click事件延时内的操作,判断是否触发dblclick事件,实现在pc和移动端均可使用dblclick事件
- 监测移动距离和按下时间(手指或鼠标左键),判断是否触发hold事件(长按事件)
- 允许PC端以右键代替hold事件,效果表现为监听hold事件可使移动端长按弹出菜单,PC端右键弹出菜单
- 监测滑动事件和滑动距离,判断是否触发drift惯性漂移事件
- 移动端可通过双指使用scale和rotate事件,如果是多指也将按双指处理
- pc端的滚轮默认不会触发目标元素的相关事件,但是可以开启滚轮对应的某一个事件,可选择开启的功能有scale、rotate和translate(三选一)
- 如果需要监听所有事件,插件提供对应的start、move和end事件
- 对于translate、drift、scale和rotate相关事件提供全过程的配套事件
使用方法
插件运行方式有两种:
- ax*属性:对列表父层使用
axGesture
属性,即可按默认参数运行。 - js实例:通过
new axGesture('#ID',{})
方式创建实例运行。
插件默认开启click、dblclick、hold(左键长按或右键)、translate和drift监听,默认未开启scale、rotate和滚轮scroll监听。
-
<div class="gesture"> <div class="parent"> <div class="child" id="comm">AXUI</div> </div> <i></i> <button class="ax-btn ax-primary clear">清空打印</button> <div class="print"></div> </div>
-
let gestures = [...document.querySelectorAll('.gesture')]; gestures.forEach(k => { k.querySelector('.clear').onclick = () => { k.querySelector('.print').innerHTML = ''; } if (k.querySelector('.reset')) { k.querySelector('.reset').onclick = () => { k.querySelector('.child').style.transform = ''; } } }); let commIns = new axGesture('#comm', { onInit: function () { this.parentDom = this.targetDom.closest('.gesture'); this.printDom = this.parentDom.querySelector('.print'); this.dotDom = this.parentDom.querySelector('i'); }, onClick: function (e) { this.printDom.insertAdjacentHTML('afterbegin', `
click事件,当前值坐标:${JSON.stringify(e.coord)}`); this.dotDom.style.left = e.coord.x + 'px'; this.dotDom.style.top = e.coord.y + 'px'; console.log('单击了', e); }, onDblclick: function (e) { console.log('双击了', e); this.printDom.insertAdjacentHTML('afterbegin', `dblclick事件,当前坐标:${JSON.stringify(e.coord)}`); this.dotDom.style.left = e.coord.x + 'px'; this.dotDom.style.top = e.coord.y + 'px'; }, onHold: function (e) { console.log('长按了', e); this.printDom.insertAdjacentHTML('afterbegin', `hold事件,当前坐标:${JSON.stringify(e.coord)}`); this.dotDom.style.left = e.coord.x + 'px'; this.dotDom.style.top = e.coord.y + 'px'; }, }); -
.gesture .parent { height: 40rem; background-color: #fafafa; overflow: hidden; display: flex; align-items: center; justify-content: center; position: relative; } .gesture .child { width: 20rem; height: 20rem; display: flex; align-items: center; justify-content: center; background-color: darksalmon; position: relative; transition-timing-function: ease; } .gesture i { width: 4px; height: 4px; background-color: red; pointer-events: none; position: fixed; left: 0; top: 0; } .gesture .print { font-size: 12px; line-height: 18px; }
拖拽监听
拖拽监听包含全生命周期的拖拽事件,包含translate
、translating
和translated
,其与touchstart、touchmove和touchend(或mousedown、mousemove和mouseup)一一对应。
-
<div class="gesture"> <div class="parent"> <div class="child" id="translate">AXUI</div> </div> <i></i> <button class="ax-btn ax-primary clear">清空打印</button> <button class="ax-btn ax-primary reset">复原</button> <div class="print"></div> </div>
-
let gestures = [...document.querySelectorAll('.gesture')]; gestures.forEach(k => { k.querySelector('.clear').onclick = () => { k.querySelector('.print').innerHTML = ''; } if (k.querySelector('.reset')) { k.querySelector('.reset').onclick = () => { k.querySelector('.child').style.transform = ''; } } }); let translateParent = document.querySelector('#translate'), translateChild = translateParent.querySelector('.child'), translateIns = new axGesture(translateChild, { //拖拽容易拖出边界导致不能正确执行end事件,使用parent参数可以让鼠标离开父层也执行end事件,以确保拖拽事件生命周期完整 parent: translateParent, wheel: { //允许滚轮代替拖拽 action: 'translate', }, onInit: function () { this.parentDom = this.targetDom.closest('.gesture'); this.printDom = this.parentDom.querySelector('.print'); this.dotDom = this.parentDom.querySelector('i'); }, onTranslate: function (e) { console.log('开始拖拽了', e); this.printDom.insertAdjacentHTML('afterbegin', `
拖拽开始,当前位移值:${JSON.stringify(e.translate.value)}`); }, onTranslating: function (e) { //打印输出会导致拖拽迟滞,故注释掉 //console.log('正在拖拽', e); //this.targetDom.style.transform = `translate(${e.translate.value.x}px,${e.translate.value.y}px)`; //可以使用axSetStyleTransform工具函数修改style.transform,效果同以上代码 axSetStyleTransform(this.targetDom, { translate: e.translate.value }); this.dotDom.style.left = e.coord.x + 'px'; this.dotDom.style.top = e.coord.y + 'px'; //写入节点会导致拖拽迟滞,故注释掉 //this.printDom.insertAdjacentHTML('afterbegin', `正在拖拽,当前位移值:${JSON.stringify(e.translate.value)}`); }, onTranslated: function (e) { console.log('拖拽完成了', e); this.printDom.insertAdjacentHTML('afterbegin', `拖拽完成,当前位移值:${JSON.stringify(e.translate.value)}`); } }); -
<同上>
漂移监听
除了拖拽生命周期事件,还有一组在拖拽完成(translate)时判断的drift关联事件,包含drift
、drifting
和drifted
。快速甩动可发生该事件,视觉上的表现是惯性漂移效果。
触摸完成后,产生drift事件的条件共有两个:
- 参数1、drift.timeTHR:从start到end的最长拖拽时间,默认300,单位ms,小于该时间是达成drift事件的条件之一。
- 参数2、drift.distTHR:从start到end的最短拖拽距离,默认20,单位px,大于该距离是达成drift事件的条件之二。
另外drift.duration
参数为漂移时间,如果设置为0是不会有漂移效果的。
需要注意的是,产生漂移后,如果再次拖拽元素会取消当前的drifting事件。
-
<div class="gesture"> <div class="parent"> <div class="child" id="drift">AXUI</div> </div> <i></i> <button class="ax-btn ax-primary clear">清空打印</button> <button class="ax-btn ax-primary reset">复原</button> <div class="print"></div> </div>
-
let gestures = [...document.querySelectorAll('.gesture')]; gestures.forEach(k => { k.querySelector('.clear').onclick = () => { k.querySelector('.print').innerHTML = ''; } if (k.querySelector('.reset')) { k.querySelector('.reset').onclick = () => { k.querySelector('.child').style.transform = ''; } } }); let driftParent = document.querySelector('#drift'), driftChild = driftParent.querySelector('.child'), driftIns = new axGesture(driftChild, { parent: driftParent, drift: { coef: 0.5//设置漂移距离系数,系数越大漂移越远 }, onInit: function () { this.parentDom = this.targetDom.closest('.gesture'); this.printDom = this.parentDom.querySelector('.print'); this.dotDom = this.parentDom.querySelector('i'); }, onTranslate: function (e) { console.log('开始拖拽了', e); this.printDom.insertAdjacentHTML('afterbegin', `
拖拽开始,当前位移坐标:${JSON.stringify(e.translate.value)}`); }, onTranslating: function (e) { //由于start时可能已经是有偏移的状态,所以可以使用差值+原偏移值来计算元素移动的总值 //e.translate.value.x=e.translate.diff.x + this.startValues.translate.x //e.translate.value.y=e.translate.diff.y + this.startValues.translate.y //this.targetDom.style.transform = `translate(${e.translate.value.x}px,${e.translate.value.y}px)`; //this.startValues变量是start或使用滚轮是时最早获取的元素transform值,例如{scale:{x:1,y:1},translate:{x:0,y:0},rotate:30},下一行代码等同于上一行 axSetStyleTransform(this.targetDom, { translate: { x: e.translate.diff.x + this.startValues.translate.x, y: e.translate.diff.y + this.startValues.translate.y } }); this.dotDom.style.left = e.coord.x + 'px'; this.dotDom.style.top = e.coord.y + 'px'; }, onDrift: function (e) { console.log('快速的拖拽产生了漂移', e); //this.targetDom.style.transform = `translate(${e.drift.value.x}px,${e.drift.value.y}px)`;//等同于axSetStyleTransform设定 axSetStyleTransform(this.targetDom, { translate: e.drift.value }); this.printDom.insertAdjacentHTML('afterbegin', `drift事件,当前值:${JSON.stringify(e.translate.value)},差值:${JSON.stringify(e.translate.diff)},坐标:${JSON.stringify(e.coord)}`); }, onDrifting: function (e) { console.log('漂移中'); }, onDrifted: function (e) { console.log('漂移结束'); } }); -
<同上>
漂移是拖拽行为(translate
事件)的副产品,我们也可以在拖拽完成时判断是否继续惯性漂移。
-
<div class="gesture"> <div class="parent"> <div class="child" id="drift2">AXUI</div> </div> <i></i> <button class="ax-btn ax-primary clear">清空打印</button> <button class="ax-btn ax-primary reset">复原</button> <div class="print"></div> </div>
-
let gestures = [...document.querySelectorAll('.gesture')]; gestures.forEach(k => { k.querySelector('.clear').onclick = () => { k.querySelector('.print').innerHTML = ''; } if (k.querySelector('.reset')) { k.querySelector('.reset').onclick = () => { k.querySelector('.child').style.transform = ''; } } }); let drift2Parent = document.querySelector('#drift2'), drift2Child = drift2Parent.querySelector('.child'), drift2Ins = new axGesture(drift2Child, { parent: drift2Parent, drift: { coef: 0.5//设置漂移距离系数,系数越大漂移越远 }, onInit: function () { this.parentDom = this.targetDom.closest('.gesture'); this.printDom = this.parentDom.querySelector('.print'); this.dotDom = this.parentDom.querySelector('i'); }, onTranslate: function (e) { console.log('开始拖拽了', e); this.printDom.insertAdjacentHTML('afterbegin', `
拖拽开始,当前位移坐标:${JSON.stringify(e.translate.value)}`); }, onTranslating: function (e) { //this.targetDom.style.transform = `translate(${e.translate.value.x}px,${e.translate.value.y}px)`;//等同于axSetStyleTransform设定 axSetStyleTransform(this.targetDom, { translate: { x: e.translate.value.x, y: e.translate.value.y } }); this.dotDom.style.left = e.coord.x + 'px'; this.dotDom.style.top = e.coord.y + 'px'; }, onTranslated: function (e) { if (e.translate.isDrift) { console.log('快速的拖拽产生了漂移', e); this.targetDom.style.transform = `translate(${e.drift.value.x}px,${e.drift.value.y}px)`; } } }); -
<同上>
缩放监听
在移动端实现双指放大和缩小需要设置参数scale.enable:true
以开启该功能;如果需要pc端使用鼠标对元素进行缩放,需要设置wheel.action:'scale'
。
-
<div class="gesture"> <div class="parent"> <div class="child" id="scale">AXUI</div> </div> <i></i> <button class="ax-btn ax-primary clear">清空打印</button> <button class="ax-btn ax-primary reset">复原</button> <div class="print"></div> </div>
-
let gestures = [...document.querySelectorAll('.gesture')]; gestures.forEach(k => { k.querySelector('.clear').onclick = () => { k.querySelector('.print').innerHTML = ''; } if (k.querySelector('.reset')) { k.querySelector('.reset').onclick = () => { k.querySelector('.child').style.transform = ''; } } }); let scaleParent = document.querySelector('#scale'), scaleIns = new axGesture(scaleParent.querySelector('.child'), { scale: { enable: true, //设置缩放限制 max: 4, min: 0.5, }, wheel: { action: 'scale', //改变滚轮方向 //reverse:true, }, onInit: function () { this.parentDom = this.targetDom.closest('.gesture'); this.printDom = this.parentDom.querySelector('.print'); this.dotDom = this.parentDom.querySelector('i'); }, onScale: function (e) { console.log('开始缩放了', e); this.printDom.insertAdjacentHTML('afterbegin', `
缩放开始,当前缩放值:${JSON.stringify(e.scale.value)}`); }, onScaling: function (e) { //this.targetDom.style.transform = `scale(${e.scale.value.x})`;//等同于axSetStyleTransform设定 axSetStyleTransform(this.targetDom, { scale: e.scale.value.x }); this.printDom.insertAdjacentHTML('afterbegin', `缩放开始,当前缩放值:${JSON.stringify(e.scale.value)}`); //落点定位 this.dotDom.style.left = e.coord.x + 'px'; this.dotDom.style.top = e.coord.y + 'px'; }, onScaled: function (e) { console.log('缩放结束', e); this.printDom.insertAdjacentHTML('afterbegin', `缩放结束,当前缩放值:${JSON.stringify(e.scale.value)}`); }, }); -
<同上>
默认为居中缩放,如果需要以落点为中心点缩放,则需要设置scale.centered:false
。以落点为中心缩放会产生translate偏移值。
-
<div class="gesture"> <div class="parent"> <div class="child" id="scale2">AXUI</div> </div> <i></i> <button class="ax-btn ax-primary clear">清空打印</button> <button class="ax-btn ax-primary reset">复原</button> <div class="print"></div> </div>
-
let gestures = [...document.querySelectorAll('.gesture')]; gestures.forEach(k => { k.querySelector('.clear').onclick = () => { k.querySelector('.print').innerHTML = ''; } if (k.querySelector('.reset')) { k.querySelector('.reset').onclick = () => { k.querySelector('.child').style.transform = ''; } } }); let scaleParent2 = document.querySelector('#scale2'), scaleIns2 = new axGesture(scaleParent2.querySelector('.child'), { scale: { enable: true, max: 4, min: 0.5, centered: false, }, wheel: { action: 'scale', //改变滚轮方向 //reverse:true, }, onInit: function () { this.parentDom = this.targetDom.closest('.gesture'); this.printDom = this.parentDom.querySelector('.print'); this.dotDom = this.parentDom.querySelector('i'); }, onScale: function (e) { console.log('开始缩放了', e); this.printDom.insertAdjacentHTML('afterbegin', `
缩放开始,当前缩放值:${JSON.stringify(e.scale.value)}`); }, onScaling: function (e) { //以落点为中心缩放会产生translate偏移值,所以除了需要应用transform.scale还需要设置transform.translate //this.targetDom.style.transform = `translate(${e.scale.translate.x}px,${e.scale.translate.y}px) scale(${e.scale.value.x})`;//等同于axSetStyleTransform设定 axSetStyleTransform(this.targetDom, { translate: { x: e.scale.translate.x, y: e.scale.translate.y }, scale: e.scale.value.x }); this.printDom.insertAdjacentHTML('afterbegin', `缩放开始,当前缩放值:${JSON.stringify(e.scale.value)}`); //落点定位 this.dotDom.style.left = e.coord.x + 'px'; this.dotDom.style.top = e.coord.y + 'px'; }, onScaled: function (e) { console.log('缩放结束', e); this.printDom.insertAdjacentHTML('afterbegin', `缩放结束,当前缩放值:${JSON.stringify(e.scale.value)}`); }, }); -
<同上>
旋转监听
在移动端实现双指放大和缩小需要设置参数rotate.enable:true
以开启该功能;如果需要pc端使用鼠标对元素进行缩放,需要设置wheel.action:'rotate'
。
-
<div class="gesture"> <div class="parent" id="rotate"> <div class="child">AXUI</div> </div> <i></i> <button class="ax-btn ax-primary clear">清空打印</button> <button class="ax-btn ax-primary reset">复原</button> <div class="print"></div> </div>
-
let gestures = [...document.querySelectorAll('.gesture')]; gestures.forEach(k => { k.querySelector('.clear').onclick = () => { k.querySelector('.print').innerHTML = ''; } if (k.querySelector('.reset')) { k.querySelector('.reset').onclick = () => { k.querySelector('.child').style.transform = ''; } } }); let rotateParent = document.querySelector('#rotate'), rotateIns = new axGesture(rotateParent.querySelector('.child'), { rotate: { enable: true, }, wheel: { action: 'rotate', //改变滚轮方向 //reverse:true, }, onInit: function () { this.parentDom = this.targetDom.closest('.gesture'); this.printDom = this.parentDom.querySelector('.print'); this.dotDom = this.parentDom.querySelector('i'); }, onRotate: function (e) { console.log('开始旋转了', e); this.printDom.insertAdjacentHTML('afterbegin', `
旋转开始,当前旋转值:${JSON.stringify(e.rotate.value)}`); }, onRotating: function (e) { //this.targetDom.style.transform = `rotate(${e.rotate.value}deg)`;//等同于axSetStyleTransform设定 axSetStyleTransform(this.targetDom, { rotate: e.rotate.value }); this.printDom.insertAdjacentHTML('afterbegin', `旋转开始,当前旋转值:${JSON.stringify(e.rotate.value)}`); //落点定位 this.dotDom.style.left = e.coord.x + 'px'; this.dotDom.style.top = e.coord.y + 'px'; }, onRotated: function (e) { console.log('旋转结束', e); this.printDom.insertAdjacentHTML('afterbegin', `旋转结束,当前旋转值:${JSON.stringify(e.rotate.value)}`); }, }); -
<同上>
多事件监听
start、move、end和cancel四个事件对应着touchstart/touchmove/touchend/touchcancel和mousedown/mousemove/mouseup/mouseleave,所以可以使用该四个事件做多事件监听。
translate/translating/translated,rotate/rotating/rotated,scale/scaling/scaled,三组事件都对应着start/move/end,所以可以直接在start/move/end事件中进行监听;
另外,hold事件可在start中监听;click,dblclick,以及drift事件可在end中监听。
-
<div class="gesture"> <div class="parent" id="multi"> <div class="child">AXUI</div> </div> <i></i> <button class="ax-btn ax-primary clear">清空打印</button> <button class="ax-btn ax-primary reset">复原</button> <div class="print"></div> </div>
-
let gestures = [...document.querySelectorAll('.gesture')]; gestures.forEach(k => { k.querySelector('.clear').onclick = () => { k.querySelector('.print').innerHTML = ''; } if (k.querySelector('.reset')) { k.querySelector('.reset').onclick = () => { k.querySelector('.child').style.transform = ''; } } }); let multiParent = document.querySelector('#multi'), multiIns = new axGesture(multiParent.querySelector('.child'), { scale: { enable: true, max: 4, min: 0.5, centered: false, }, rotate: { enable: true, }, wheel: { action: 'rotate', }, onInit: function () { this.parentDom = this.targetDom.closest('.gesture'); this.printDom = this.parentDom.querySelector('.print'); this.dotDom = this.parentDom.querySelector('i'); }, onStart: function (e) { console.log('操作开始', e); this.printDom.insertAdjacentHTML('afterbegin', `
操作开始`); }, onMove: function (e) { axSetStyleTransform(this.targetDom, { translate: e.translate.value, scale: e.scale.value, rotate: e.rotate.value }); this.dotDom.style.left = e.coord.x + 'px'; this.dotDom.style.top = e.coord.y + 'px'; //移动过程计算较多,尽量不打印和不输出,以避免卡顿 //this.printDom.insertAdjacentHTML('afterbegin', `操作开始,当前值:${transformTxt}`); }, onEnd: function (e) { console.log('操作结束'); if (e.translate.isDrift) { axSetStyleTransform(this.targetDom, { translate: e.drift.value, }); } this.printDom.insertAdjacentHTML('afterbegin', `操作结束`); }, }); -
<同上>
变量和操作方法
本插件有系列内部成员变量和内部操作方法,用户根据这些变量和方法可以自由操作实例,据此可方便与其他组件进行交互。请按下F12按键打开浏览器控制台来观摩以下实例。
内部成员变量如下:
this.targetDom
:事件目标元素节点this.started
:开始触发状态this.destroyed
:销毁状态this.jitterValue
:抖动阈值this.clickCount
:点击次数this.moveCount
:移动次数(touchmove),包含单指或多指this.touchesMoveCount
:多指移动次数(touchmove)this.paramsFormat
:click、dblclick、hold、translate、rotate、scale和drift事件中的参数格式this.startCoord
:开始触摸时候相对浏览器左上角的坐标,该值在每次touchstart时均更新,格式为:{x:0,y:0}this.driftListen
:drift事件中监听的动画循环函数,可通过cancelAnimationFrame方法取消监听drifting和drifted事件this.moveValues
:移动中产生的值,格式:{x:0,y:0,h:0,c:{x:0,y:0},d:{x:1,y:1}}- x是指x轴移动距离,为正值表示向右移动,为负值表示向左移动
- y是指y轴移动距离,为正值表示向下移动,为负值表示向上移动
- h是指斜边长度(hypotenuse),其为x和y构成的三角中的斜边,是绝对值
- c是指相对元素左上角的坐标(coordinate),其x和y均为正值
- d是指移动方向(direction),对于translate行为,x:1表示向右移动,-1表示向左移动,y:1表示向下移动,-1表示向上移动;对于rotate行为,1表示顺时针,-1表示逆时针;对于scale行为,1表示放大,-1表示缩小
this.diffValues
:移动中本次和上次的差值,格式:{ scale: 0, rotate: 0, translate: { x: 0, y: 0, h: 0 } }- scale是指触发scale行为产生的缩放差值
- rotate是指触发rotate行为产生的角度差值
- translate是指触发translate行为产生的x轴和y轴差值
this.triangleValues
:移动中并且有多个触摸点时,第一和第二个触摸点构成的直角三角形的值,格式:{ start: { x: 0, y: 0, h: 0 }, last: { x: 0, y: 0, h: 0 }, now: { x: 0, y: 0, h: 0 } }- start为touchstart时的三角值
- now为touchmove时当前的三角值
- last为touchmove时上回的三角值,当touchend时,last将等于now
内部操作方法如下:
this.init(callback)
:初始化实例,支持一个参数即回调函数,回调无参数,在destroy后可使用this.getStartValues()
:获得startValues值,无参数this.setCompleted()
:强制事件完成,无参数,如果移动中发生意外情况可使用该方法this.on(type,callback)
:绑定监听事件,支持两个参数,type事件名例如'click',和关联函数this.off(type,callback)
:解除监听事件,支持两个参数,type事件名例如'click',和关联函数this.reset(callback)
:重置目标节点的style.transform值,支持一个参数即回调函数,回调无参数this.destroy(callback)
:销毁多点触摸实例,支持一个参数即回调函数,回调无参数
-
<div class="gesture"> <div class="parent" id="method"> <div class="child" style="transform: translate(100px,0px);">AXUI</div> </div> <i></i> <button class="ax-btn ax-primary clear">清空打印</button> <button class="ax-btn ax-primary zero">重置</button> <button class="ax-btn ax-primary destroy">销毁</button> <button class="ax-btn ax-primary init">初始化</button> <div class="print"></div> </div>
-
let gestures = [...document.querySelectorAll('.gesture')]; gestures.forEach(k => { k.querySelector('.clear').onclick = () => { k.querySelector('.print').innerHTML = ''; } if (k.querySelector('.reset')) { k.querySelector('.reset').onclick = () => { k.querySelector('.child').style.transform = ''; } } }); let methodParent = document.querySelector('#method'), methodZero = methodParent.closest('.gesture').querySelector('.zero'), methodInit = methodParent.closest('.gesture').querySelector('.init'), methodDestroy = methodParent.closest('.gesture').querySelector('.destroy'), methodIns = new axGesture(methodParent.querySelector('.child'), { scale: { enable: true, max: 4, min: 0.5, }, rotate: { enable: true, }, wheel: { action: 'rotate', }, onInit: function () { this.parentDom = this.targetDom.closest('.gesture'); this.printDom = this.parentDom.querySelector('.print'); this.dotDom = this.parentDom.querySelector('i'); this.printDom.insertAdjacentHTML('afterbegin', `
初始化完成`); }, onStart: function (e) { console.log('操作开始', e); this.printDom.insertAdjacentHTML('afterbegin', `操作开始`); }, onMove: function (e) { axSetStyleTransform(this.targetDom, { translate: e.translate.value, scale: e.scale.value, rotate: e.rotate.value }); this.dotDom.style.left = e.coord.x + 'px'; this.dotDom.style.top = e.coord.y + 'px'; }, onEnd: function (e) { console.log('操作结束'); if (e.translate.isDrift) { axSetStyleTransform(this.targetDom, { translate: e.drift.value, }); } this.printDom.insertAdjacentHTML('afterbegin', `操作结束`); }, }); methodZero.onclick = () => { methodIns.reset(); } methodDestroy.onclick = () => { methodIns.destroy(); } methodInit.onclick = () => { methodIns.init(); } -
<同上>
监听事件
本插件有若干监听方法,以on为关键字,参数中监听格式为:new instance({onShow:function(){}})
;实例后监听格式是:instance.on('show',function(){})
。在参数中监听和实例后监听效果相同。具体事件说明如下:
onInit/init
初始化后执行,无参数onStart/start
touchstart或mousedown后执行,支持一个参数即当前事件对象onMove/move
touchmove或mousemove后执行,支持一个参数即当前事件对象onEnd/end
touchend或mouseup后执行,支持一个参数即当前事件对象onClick/click
单击后执行,支持一个参数即当前事件对象onDblclick/dblclick
双击后执行,支持一个参数即当前事件对象onHold/hold
长按或鼠标右键后执行,支持一个参数即当前事件对象onScale/scale
开始缩放事件执行,支持一个参数即当前事件对象onScaling/scaling
正在缩放执行,支持一个参数即当前事件对象onScaled/scaled
缩放结束后执行,支持一个参数即当前事件对象onRotate/rotate
开始旋转事件执行,支持一个参数即当前事件对象onRotating/rotating
正在旋转执行,支持一个参数即当前事件对象onRotated/rotated
旋转结束后执行,支持一个参数即当前事件对象onTranslate/translate
开始位移事件执行,支持一个参数即当前事件对象onTranslating/translating
正在位移执行,支持一个参数即当前事件对象onTranslated/translated
位移结束后执行,支持一个参数即当前事件对象onDrift/drift
开始漂移事件执行,支持一个参数即当前事件对象onDrifting/drifting
正在漂移执行,支持一个参数即当前事件对象onDrifted/drifted
漂移结束后执行,支持一个参数即当前事件对象onCancel/cancel
失去目标节点后执行,支持一个参数即当前事件对象onReset/reset
重置transform值后执行,无参数onDestroy/destroy
销毁后执行,无参数
演示实例显示结果使用了console.log方法,请按下F12按键打开开发者工具中的“控制台”查看监听效果。
-
<div class="gesture"> <div class="parent" id="on"> <div class="child">AXUI</div> </div> <i></i> <button class="ax-btn ax-primary clear">清空打印</button> <button class="ax-btn ax-primary reset">复原</button> <div class="print"></div> </div>
-
let gestures = [...document.querySelectorAll('.gesture')]; gestures.forEach(k => { k.querySelector('.clear').onclick = () => { k.querySelector('.print').innerHTML = ''; } if (k.querySelector('.reset')) { k.querySelector('.reset').onclick = () => { k.querySelector('.child').style.transform = ''; } } }); let onParent = document.querySelector('#on'), onIns = new axGesture(onParent.querySelector('.child'), { scale: { enable: true, max: 4, min: 0.5, centered: false, }, rotate: { enable: true, }, wheel: { action: 'rotate', }, onInit: function () { this.parentDom = this.targetDom.closest('.gesture'); this.printDom = this.parentDom.querySelector('.print'); this.dotDom = this.parentDom.querySelector('i'); }, }); onIns.on('start', function (e) { console.log('操作开始', e); this.printDom.insertAdjacentHTML('afterbegin', `
操作开始`); }).on('move', function (e) { axSetStyleTransform(this.targetDom, { translate: e.translate.value, scale: e.scale.value, rotate: e.rotate.value }); this.dotDom.style.left = e.coord.x + 'px'; this.dotDom.style.top = e.coord.y + 'px'; }).on('end', function (e) { console.log('操作结束'); if (e.translate.isDrift) { axSetStyleTransform(this.targetDom, { translate: e.drift.value, }); } this.printDom.insertAdjacentHTML('afterbegin', `操作结束`); });
解绑事件
on和off是一组相对的事件操作方法,如果绑定和解绑同一个事件,那么他们的关联函数应该是同一个。
-
<div class="gesture"> <div class="parent" id="on"> <div class="child">AXUI</div> </div> <i></i> <button class="ax-btn ax-primary clear">清空打印</button> <button class="ax-btn ax-primary reset">复原</button> <div class="print"></div> </div>
-
let gestures = [...document.querySelectorAll('.gesture')]; gestures.forEach(k => { k.querySelector('.clear').onclick = () => { k.querySelector('.print').innerHTML = ''; } if (k.querySelector('.reset')) { k.querySelector('.reset').onclick = () => { k.querySelector('.child').style.transform = ''; } } }); let offParent = document.querySelector('#off'), onT = offParent.closest('.gesture').querySelector('.onT'), offT = offParent.closest('.gesture').querySelector('.offT'), onS = offParent.closest('.gesture').querySelector('.onS'), offS = offParent.closest('.gesture').querySelector('.offS'), offIns = new axGesture(offParent.querySelector('.child'), { scale: { enable: true, max: 4, min: 0.5, }, wheel: { action: 'scale', }, onInit: function () { this.parentDom = this.targetDom.closest('.gesture'); this.printDom = this.parentDom.querySelector('.print'); this.dotDom = this.parentDom.querySelector('i'); }, }), translateFun = function (e) { console.log('开始拖拽了', e); this.printDom.insertAdjacentHTML('afterbegin', `
拖拽开始,当前位移值:${JSON.stringify(e.translate.value)}`); }, translatingFun = function (e) { axSetStyleTransform(this.targetDom, { translate: e.translate.value }); }, translatedFun = function (e) { console.log('拖拽完成了', e); this.printDom.insertAdjacentHTML('afterbegin', `拖拽完成,当前位移值:${JSON.stringify(e.translate.value)}`); }, scaleFun = function (e) { console.log('开始缩放了', e); this.printDom.insertAdjacentHTML('afterbegin', `缩放开始,当前缩放值:${JSON.stringify(e.scale.value)}`); }, scalingFun = function (e) { axSetStyleTransform(this.targetDom, { scale: e.scale.value.x }); this.printDom.insertAdjacentHTML('afterbegin', `缩放开始,当前缩放值:${JSON.stringify(e.scale.value)}`); }, scaledFun = function (e) { console.log('缩放结束', e); this.printDom.insertAdjacentHTML('afterbegin', `缩放结束,当前缩放值:${JSON.stringify(e.scale.value)}`); }; onT.onclick = () => { offIns.on('translate', translateFun).on('translating', translatingFun).on('translated', translatedFun); } onS.onclick = () => { offIns.on('scale', scaleFun).on('scaling', scalingFun).on('scaled', scaledFun); } offT.onclick = () => { offIns.off('translate', translateFun).off('translating', translatingFun).off('translated', translatedFun); } offS.onclick = () => { offIns.off('scale', scaleFun).off('scaling', scalingFun).off('scaled', scaledFun); }
事件对象
所有监听事件均支持一个相同格式的参数,即事件对象,详细说明如下:
originEvent: null,//touchstart/move/end和mousedown/move/up的原始事件对象 scale: {//scale、scaling和scaled监听可使用该值 direction: 0,//缩放方向,放大为1,缩小为-1 diff: 0,//距离上次缩放的差值 value: { x: 1, y: 1 },//当前的缩放值 translate: { x: 0, y: 0 }//动态中心点会产生transform.translate偏移值 }, rotate: {//rotate、rotating和rotated监听可使用该值 direction: 0,//旋转方向,顺时针为1,逆时针为-1 diff: 0,//距离上次旋转的差值 value: 0//当前的旋转值 }, translate: {//translate、translating和translated监听可使用该值 direction: { x: 0, y: 0 },//位移方向,x:1表示水平向右,x:-1表示水平向左,y:1表示垂直向下,y:-1表示垂直向上 diff: { x: 0, y: 0, h: 0 },//距离上次位移的差值,h为上次坐标与本次坐标的直线距离,为绝对值 value: { x: 0, y: 0 },//当前的位移值 isDrift: false,//translated事件之后是否产生漂移,如果为true则可使用以下drift参数 }, drift: {//drift、drifting和drifted监听可使用该值 duration: 0,//漂移持续时间,由options.drift.duration设定 direction: { x: 0, y: 0 },//同translate.direction diff: { x: 0, y: 0, h: 0 }, //产生drift监听的额外移动距离 value: { x: 0, y: 0 } //drift之后最终停留的tanslate值 }, wheel: { direction: { x: 0, y: 0 },//滚动方向,x:1或y:1表示向下滚,x:-1或y:-1表示向上滚 }, coord: { x: 0, y: 0 },//当前相对浏览器的中心坐标,如果是多指触摸则为前两个手指的中心点坐标 target: null, //目标对象 name: '',//事件名称 moveStart: false, //是否是首次move touchesStart: false,//多指触摸是否开始 moveEnd: false, //是否是最后一次move moveTime: 0, //从start到end的持续时间
参数选项
document.addEventListener("DOMContentLoaded", function() { var demo1 = new axGesture('#demo',{ insName: '',//实例名称,字符串格式;如果填写了实例名称,那么该实例会被添加到实例合集当中,通过axInstance方法可获取本实例,详细请查看:ax-utils-instance.php parent: '',//父节点,离开父节点将终止事件,可填#id、.className、NODENAME、节点,默认为空即 jitterValue: 10,//抖动值,小于该值不触发滑动事件,只触发单击或双击事件 origin: null,//元素变换原点,不填则默认为中间,如果需要改变元素原点可以填,例如:{x:50,y:50}(单位px) click: { timeTHR: 200,//触发单击或双击事件的阈值时间,默认200即从start到end的时间需要小于200才会触发click或dblclick事件,单位ms delay: 200,//执行双击的等待时间,默认200,单位ms,即一次单击后在200ms内再次单击会取消单击事件并触发双击事件 dblclickable: true,//是否可双击,默认true可双击,如果希望单击无延时可禁用双击 holdDelay: 1000,//长按延时,默认是1000,单位ms,即1秒,按下不放将触发hold事件,等同于pc端鼠标右键事件 holdToMenu: true,//在pc端上触发右键事件是否等效长按事件,默认false,可选择true,true将禁用默认右键事件 }, wheel: { action: '',//是否允许鼠标滚动等效事件,可填scale、translate或rotate,默认为空即不改变原鼠标滚轮事件 axis: 'y',//如果action填了translate则需要指明方向(滚轮只有上下),默认为y轴,可填x轴 distance: 60,//滚轮触发一次的滚动距离,默认是60,单位px,与常规浏览器滚轮距离基本一致 scale: 0.5,//滚轮一次缩放比例的累加值,默认0.5即+0.5或-0.5,可自定义该值,请填写一个正数 degree: 10,//如果action=rotate时的滚轮操作旋转角度,默认10,单位deg duration: 500,//PC端滚轮操作是否强制加上动画效果,默认500即动画执行500ms,如果不需要PC端滚轮动画请设为0 reverse: false,//是否让滚轮逆向操作,默认false即滚轮向下拨动表示scale放大/rotate顺时针,若设为true则滚轮向下拨动表示scale缩小/rotate逆时针 }, translate: { max: 10000,//位移最大极限,默认为10000px min: -10000,//位移最小极限,默认为-10000px positionInstead: false,//发生位移的属性默认是transform(改变translateX和translateY),如果需要改变top和left属性来实现位移设设为true }, drift: { enable: true,//是否开启漂移功能,默认true开启,可设为false关闭 timeTHR: 300,//拖拽完成后触发drift的最大时间,小于该时间能达成触发条件 distTHR: 20,//拖拽完成后触发drift的最少移动量,大于该值能达成触发条件 coef: 1,//漂移系数,系数越大手指或鼠标释放后获得额外距离就越远,默认为1,请填写一个正数 duration: 500,//漂移动画持续时间,默认500,单位ms }, scale: { enable: false,//是否开启缩放功能,默认false关闭,可设为true开启 centered: true,//是否居中缩放,默认无论落点在哪里都居中缩放,可选择false可将落点作为中心点进行缩放 max: 100,//缩放最大极限,默认为100倍 min: 0,//缩放最小极限,默认为0倍即可缩小到无限小 }, rotate: { enable: false,//是否开启旋转功能,默认false关闭,可设为true开启 max: 3600,//旋转最大极限,默认为3600即顺时针最大可旋转10圈 min: -3600,//旋转最小极限,默认为-3600即逆时针最大可旋转10圈 }, initial: null,//设置初始值,如果在css中设置了transform则需要在此设置初始值,以保持一致 /* initial:{ translate: { x: 0, y: 0 },//位移初始值,单位px scale: { x: 1, y: 1 },//缩放初始值,无单位 rotate: 0,//旋转初始值,单位deg }, */ breakpoints: {},//使用断点以适配终端,详情查看ax-utils-other.php页面 onInit: '',//回调函数,加载完毕执行,无参数 onStart: '',//回调函数,手指/鼠标按下执行,支持一个参数即当前数据 onMove: '',//回调函数,手指/鼠标拖动过程执行,支持一个参数即当前数据 onEnd: '',//回调函数,松开手指/鼠标后执行,支持一个参数即当前数据 onClick: '',//回调函数,短时间内点击后执行,支持一个参数即当前数据 onDblclick: '',//回调函数,双击后执行,支持一个参数即当前数据 onHold: '',//回调函数,长按后执行,支持一个参数即当前数据 onScale: '',//回调函数,开始缩放执行,支持一个参数即当前数据 onScaling: '',//回调函数,缩放中执行,支持一个参数即当前数据 onScaled: '',//回调函数,缩放结束后执行,支持一个参数即当前数据 onRotate: '',//回调函数,开始旋转执行,支持一个参数即当前数据 onRotating: '',//回调函数,旋转中执行,支持一个参数即当前数据 onRotated: '',//回调函数,旋转结束后执行,支持一个参数即当前数据 onTranslate: '',//回调函数,开始位移执行,支持一个参数即当前数据 onTranslating: '',//回调函数,位移中执行,支持一个参数即当前数据 onTranslated: '',//回调函数,位移结束后执行,支持一个参数即当前数据 onDrift: '',//回调函数,位移结束后产生漂移执行,支持一个参数即当前数据 onDrifting: '',//回调函数,漂移中执行,支持一个参数即当前数据 onDrifted: '',//回调函数,漂移结束后执行,支持一个参数即当前数据 onCancel: '',//回调函数,失去目标节点后执行,支持一个参数即当前数据 onReset: '',//回调函数,重置style.transform后执行,无参数 onDestroy: '',//回调函数,销毁后执行,无参数 }); });