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监听。

AXUI
  •                                         <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;
                                                }
                                            

拖拽监听

拖拽监听包含全生命周期的拖拽事件,包含translatetranslatingtranslated,其与touchstart、touchmove和touchend(或mousedown、mousemove和mouseup)一一对应。

AXUI
  •                                         <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关联事件,包含driftdriftingdrifted。快速甩动可发生该事件,视觉上的表现是惯性漂移效果。

触摸完成后,产生drift事件的条件共有两个:

  • 参数1、drift.timeTHR:从start到end的最长拖拽时间,默认300,单位ms,小于该时间是达成drift事件的条件之一。
  • 参数2、drift.distTHR:从start到end的最短拖拽距离,默认20,单位px,大于该距离是达成drift事件的条件之二。

另外drift.duration参数为漂移时间,如果设置为0是不会有漂移效果的。

需要注意的是,产生漂移后,如果再次拖拽元素会取消当前的drifting事件。

AXUI
  •                                         <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事件)的副产品,我们也可以在拖拽完成时判断是否继续惯性漂移。

AXUI
  •                                         <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'

AXUI
  •                                         <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偏移值。

AXUI
  •                                         <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'

AXUI
  •                                         <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中监听。

AXUI
  •                                         <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):销毁多点触摸实例,支持一个参数即回调函数,回调无参数
AXUI
  •                                         <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按键打开开发者工具中的“控制台”查看监听效果。

AXUI
  •                                         <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是一组相对的事件操作方法,如果绑定和解绑同一个事件,那么他们的关联函数应该是同一个。

AXUI
  •                                         <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: '',//回调函数,销毁后执行,无参数
      });
});
                        
以axMenu插件为例,如果在元素上通过ax*使用属性,需要使用类似json的写法,属性不需要使用引号,如果值是字符串需要使用引号包裹,如果值是数字、布尔值、null或undefined不需要引号;比如要设置active,那么在元素的ax*属性中应该这样书写:axMenu="active:2"