zoukankan      html  css  js  c++  java
  • 移动端Tap与滑屏实战技巧总结以及Vue混合开发自定义指令

    最近在忙混合开发,因交互相对复杂,所以也踩了很多坑。在此做一下总结。

    1.tap事件的实际应用

    在使用tap事件时,老生常谈的肯定是点透问题,大多情况下,在有滑屏交互的页面时,我们会在根节点阻止默认行为以解决事件点透的bug。

    阻止默认行为有优点,但也会相对带来一些问题。

    优点:

    (1)解决事件点透

    (2)解决IOS10+ safari 以及部分安卓浏览器 不在支持 viewport的最大缩放值和禁止缩放的问题

    (3)解决IOS10+ safari下给body加overflow:hidden无效的问题 

    给元素加了 一个绝对定位,但是元素本身没有定位父级,元素如果超出了body的宽度,body 上的overflow对这个元素,不起效果
    解决办法:增加一个div作为根节点,并且添加相对定位和overflow:hidden

    缺点:

    (1)禁止mouse事件执行(也可以说成是优点,具体情况具体分析)

    (2)阻止浏览器的自带效果(左右滑动切换页面,滚动回弹等)

    (3)阻止触发浏览器的滚动条

    (4)阻止触发浏览器系统菜单

    (5)阻止图片文字被选中

    (6)阻止input的输入(在新开页面进行输入 eg.淘宝)

    2.touchEvent相关变量

    (1)touches 当前屏幕上的手指列表

    (2)targetTouches 当前元素上的手指列表

    (3)changedTouches 触发当前事件的手指列表

    (4)clientX 和 clientY 手指相对于可视区的坐标

    (5)pageX 和 pageY 手指相对于页面的坐标

    3.tap事件封装要点

    (1)touchend时确定最大距离,大于此距离无效

    (2)确定从touchstart到touchend的时间间隔,大于此事件间隔无效

    (3)严格意义上讲,touchmove时也应该有最大距离限制(手指在某元素上点击后绕了一大圈再回到当前元素不应触发tap事件)

    (4)需要考虑tap事件对click的影响

    (5)如有需要的话,还要限制为只有单指操作才能触发

    4. 遵循上述要求的完整的tap封装如下:

    当然你可以扩展为面向对象,或者试用与Vue的各种形式,但原理都是如此。最后会附上Vue版的常用指令

    function tap(el, fn) {
        var start = {};
        var moveOverLimit;
        el.addEventListener('touchstart', function(e) {
            start = {
                x: e.changedTouches[0].pageX,
                y: e.changedTouches[0].pageY,
                startTime: new Date().getTime()
            }
            moveOverLimit = false;
        });
        el.addEventListener('touchmove', function (e) {
            if (Math.abs(e.changedTouches[0].pageX - start.x) > 5 || Math.abs(e.changedTouches[0].pageY - start.y) > 5) {
                moveOverLimit = true
            }
        })
        el.addEventListener('touchend', function(e) {
            var end = {
                x: e.changedTouches[0].pageX,
                y: e.changedTouches[0].pageY,
                endTime: new Date().getTime()
            }
            // 此处的限制如第三点说明的一样
            if (Math.abs(end.x - start.x) < 5 && Math.abs(end.y - start.y) < 5 && end.endTime - start.startTime < 300 && !moveOverLimit) {
                fn && fn.call(el, e);
            }
        });
    }

     5.  Vue版的常用指令如下:

    import Vue from 'vue';
    // 限制为单手操作,可根据需要自行修改
    function commonSingleSlideCheck (ev) {
        if (ev.touches.length > 1) {
            return false;
        }
        return true;
    }
    class VueSlide {
        constructor (el, binding, vnode) {
            this.el = el;
            this.binding = binding;
            this.vnode = vnode;
            this.evType = '';
            this.startPos = {x: 0, y: 0};
            this.startTime = null;
            this.movePos = {};
            this.canPullToLeft = true;
            this.canPullToRight = true;
            this.canPullToDown = true;
            this.canPullToUp = true;
            this.typeCheck = {
                'toLeft': commonSingleSlideCheck,
                'toRight': commonSingleSlideCheck,
                'toUp': commonSingleSlideCheck,
                'toDown': commonSingleSlideCheck,
                'drag': commonSingleSlideCheck,
            };
        }
        start(ev, el, binding, vnode) {
            this.startPos.x = ev.changedTouches[0].clientX;
            this.startPos.y = ev.changedTouches[0].clientY;
            switch (this.evType) {
                default:
                    if (ev.targetTouches.length > 1) {
                        return false;
                    }
                    break;
            }
            if (this.evType === 'tap') {
                this.startTime = new Date().getTime();
                if (binding.value.stop === true) {
                    ev.stopPropagation();
                }
                if (binding.value.prevent === true) {
                    ev.preventDefault();
                }
            }
        }
        move(ev, el, binding, vnode) {
            this.movePos.disX = Math.abs(ev.changedTouches[0].clientX - this.startPos.x);
            this.movePos.disY = Math.abs(ev.changedTouches[0].clientY - this.startPos.y);
            this.movePos.changeX = ev.changedTouches[0].clientX - this.startPos.x;
            this.movePos.changeY = ev.changedTouches[0].clientY - this.startPos.y;
            this.movePos.dir = '';
            if (!this.typeCheck[this.evType](ev)) {
                return false;
            }
            if ((Math.atan(this.movePos.disX / this.movePos.disY) > Math.PI / 3)) {
                ev.preventDefault();
                if (this.movePos.changeX > 0 && this.movePos.disX > 30) {
                    this.movePos.dir = 'right';
                }
                if (this.movePos.changeX < 0) {
                    this.movePos.dir = 'left';
                }
            }
            if ((Math.atan(this.movePos.disX / this.movePos.disY) < Math.PI / 6)) {
                if (this.movePos.changeY > 0 && this.movePos.disY > 30) {
                    this.movePos.dir = 'down';
                }
                if (this.movePos.changeY < 0) {
                    this.movePos.dir = 'up';
                }
            }
            if (this.evType === 'drag') {
                binding.value({
                    ev: ev,
                    startX: this.startPos.x,
                    startY: this.startPos.y,
                    changeX: this.movePos.changeX,
                    changeY: this.movePos.changeY,
                    dir: this.movePos.dir,
                });
            }
        }
        end(ev, el, binding, vnode) {
            let disX = Math.abs(ev.changedTouches[0].clientX - this.startPos.x);
            let disY = Math.abs(ev.changedTouches[0].clientY - this.startPos.y);
            let changeX = ev.changedTouches[0].clientX - this.startPos.x;
            let changeY = ev.changedTouches[0].clientY - this.startPos.y;
            if (this.evType !== 'drag' && this.evType !== 'tap') {
                if ((Math.atan(disX / disY) > Math.PI / 3) && disX > 30) {
                    if (changeX > 0 && this.evType === 'toRight' && this.canPullToRight) {
                        // console.log('向右滑动');
                        this.canPullToRight = false;
                        binding.value(el);
                    }
                    if (changeX < 0 && this.evType === 'toLeft' && this.canPullToLeft) {
                        // console.log('向左滑动');
                        this.canPullToLeft = false;
                        binding.value(el);
                    }
                }
                if ((Math.atan(disX / disY) < Math.PI / 6) && disY > 30) {
                    if (changeY > 0 && this.evType === 'toDown' && this.canPullToDown) {
                        // console.log('向下滑动');
                        this.canPullToDown = false;
                        binding.value(el);
                    }
                    if (changeY < 0 && this.evType === 'toUp' && this.canPullToUp) {
                        // console.log('向上滑动');
                        this.canPullToUp = false;
                        binding.value(el);
                    }
                }
            }
            if (this.evType === 'tap') {
                let endTime = new Date().getTime();
                if (Math.abs(changeX) < 5 && endTime - this.startTime < 300) {
                    binding.value.handler(binding.value.param);
                } else {
                    return;
                }
            }
            this.canPullToLeft = true;
            this.canPullToRight = true;
            this.canPullToUp = true;
            this.canPullToDown = true;
            this.el.addEventListener('touchstart', null);
            this.el.addEventListener('touchmove', null);
            this.el.addEventListener('touchend', null);
        }
        init(type) {
            let _this = this;
            this.evType = type;
            this.el.addEventListener('touchstart', function(ev) {
                _this.start(ev, _this.el, _this.binding, _this.vnode);
            });
            this.el.addEventListener('touchmove', function(ev) {
                _this.move(ev, _this.el, _this.binding, _this.vnode);
            });
            this.el.addEventListener('touchend', function(ev) {
                _this.end(ev, _this.el, _this.binding, _this.vnode);
            });
        }
    }
    /*
        v-tap: vue移动端tap时间
        eg: <div v-tap="{handler: fn, param: testParam}"></div>
        参数:   handler: 监听函数(function)
                param: 监听函数的参数 (object)
                stop: 是否阻止冒泡 (boolean)
                prevent: 是否阻止默认行为 (booleam)
    */
    Vue.directive('tap', {
        bind: function(el, binding, vnode) {
            new VueSlide(el, binding, vnode).init('tap');
        },
    });
    Vue.directive('to-left', {
        bind: function(el, binding, vnode) {
            new VueSlide(el, binding, vnode).init('toLeft');
        },
    });
    Vue.directive('to-right', {
        bind: function(el, binding, vnode) {
            new VueSlide(el, binding, vnode).init('toRight');
        },
    });
    Vue.directive('to-up', {
        bind: function(el, binding, vnode) {
            new VueSlide(el, binding, vnode).init('toUp');
        },
    });
    Vue.directive('to-down', {
        bind: function(el, binding, vnode) {
            new VueSlide(el, binding, vnode).init('toDown');
        },
    });
    // 拖拽指令:执行函数中注入了三个参数
    // changeX(Number) : 横轴的变化量
    // changeY(Number) : 纵轴的变化量
    // dir(String): 用户想要滑动的方向,只有在横纵轴的左右偏移方向在30deg内才会存在方向,其他角度范围内为空字符。
    Vue.directive('drag', {
        bind: function(el, binding, vnode) {
            new VueSlide(el, binding, vnode).init('drag');
        },
    });
  • 相关阅读:
    开发笔记
    PHP的重载及魔术方法
    网站飘窗效果
    asp.net 导入Excel记录到数据库中(转载)
    safari打不开该网页 因为网址无效(解决办法)
    使用join()方法 分隔拆分后的数组
    面向新手的Web服务器搭建——IIS的搭建(转载)
    ueditor上传大容量视频报http请求错误的解决方法(转载)
    表格中针对大段说明文字加一个弹出层
    JS禁止右键及禁止选中文本
  • 原文地址:https://www.cnblogs.com/pomelott/p/9906316.html
Copyright © 2011-2022 走看看