移动端APP列表点透事件处理方法


关于点透事件这里不再赘述,如果不清楚的可以上网搜一搜,或者看小火柴的这篇文章。

这里是自己在做移动端时,在列表滑动的时候,遇到的点透问题。出现这个问题的来由是因为在转场的时候,各个手机的转场效果不一样,有的比较好,但是在有些低端机上,转场显得有点卡,于是就把过渡效果去掉了,因此就是直接的路由切换。【具体事件具体分析,可能我遇到的问题并不适合你,这里只是贴出来共享】

先看下面两张图片:


点击列表页的按钮会切换到下一个页面,但是在下一个页面上的每一个条目都是可以点击的,这时就会触发了下一个页面的弹窗,事实上我们并不想直接显示这个弹窗,而是要等待用户点击。

你可能在项目中的列表页写了如下的一段代码:

render() {
  	return (
    	
    { list.map((l, index) => { return (
  • doSomething() }> { `item${index}` }
  • ) }) }
); }

在一个列表中的每个项目上绑定了点击事件,但是当点击之后切换到下一页。当时移动端的点击事件都会有300ms的延迟,因此在切换了页面之后,浏览器会再次判断点击的行为,此时如果下一个页面都有可以触发点击的元素,这时候就触发了下一个页面的点击行为。

于是,你可能会这么做,将onClick事件换成onTouchEnd事件

  • event.preventDefault() } onTouchEnd={ () => doSomething() }> { `item${index}` }
  • 但是,每次滑动的时候,其实你也触发了onTouchEnd事件,于是每次滑动你都会点击进入到下一页。于是你又想,加上一个onTouchStart事件,然后阻止掉默认事件,尼玛发现滑都滑不动了。

    因此针对常用的几种解决点透事件的方法,我想了几种解决方案:

    方案一:自己模拟Tap事件

    大致的代码如下:

    var list = document.querySelector('#list');
    var dragState = {};
    
    list.addEventListener('touchstart', function(event) {
        var touch = event.touches[0];
        dragState.startTime = new Date();
        dragState.startLeft = touch.pageX;
        dragState.startTop = touch.pageY;
        dragState.startTopAbsolute = touch.clientY;
    });
    
    list.addEventListener('touchmove', function(event) {
        var touch = event.touches[0];
        dragState.currentLeft = touch.pageX;
        dragState.currentTop = touch.pageY;
        dragState.currentTopAbsolute = touch.clientY;
    });
    
    list.addEventListener('touchend', function() {
        var dragDuration = new Date() - dragState.startTime;
        var offsetLeft = dragState.currentLeft - dragState.startLeft;
        var offsetTop = dragState.currentTop - dragState.startTop;
    
        if (dragDuration < 300) {
            var fireTap = Math.abs(offsetLeft) < 10 && Math.abs(offsetTop < 10);
            if (isNaN(offsetLeft) || isNaN(offsetTop)) {
              	fireTap = true;
            }
            if (fireTap) {
              	alert('tap');
            }
        }
        dragState = {};
    });
    

    判断水平位移差和垂直位移差都小于10像素,并且touchstarttouchend的时间差小于300ms时,即认为触发了Tap事件。

    方案二:加入转场动画

    既然是因为转场动画在某些机型上比较卡的原因造成的,那么如果不是太考虑性能的话,可以加上转场动画,关于react中的转场动画,时间大概在300ms就好,可以看我之前对于转场代码的研究:react-css3-transition-group

    方案三:在目标页面加入遮罩层

    在目标页面加上一层透明的弹层,使上一个页面的点击在此弹层上失效,具体做法为使用一个高阶组件,在高阶组件中添加一个定时器,在每个页面加载的时候生成一个弹层,400ms之后消失弹层即可。

    import React from 'react';
    
    const styles = {
        modal: {
            width: '100%',
            height: '100%',
            position: 'absolute',
            top: 0,
            left: 0,
            right: 0,
            bottom: 0,
            zIndex: 20,
            backgroundColor: 'transparent'
        }
    };
    
    const ComponentWrapper = MyComponent => {
    
        const ComponentTemplate = React.createClass({
    		getInitialState() {
                return {
                    modal: true
                };
            },
    
            componentDidMount() {
                this.modalInter = setTimeout(() => {
                    this.setState({ modal: false });
                }, 400);
            },
    
            componentWillUnmount() {
                this.hideTips();
                this.modalInter && clearTimeout(this.modalInter);
            },
    
            render() {
                return (
                    
                        
                        { this.state.modal && 
    }
    ) } }); return ComponentTemplate; }; export default ComponentWrapper;

    另外在0.13.3版本的react还支持mixins的时候,可以添加如下代码:

    import React from 'react';
    import ReactDOM from 'react-dom';
    
    const styles = {
        modal: {
            width: '100%',
            height: '100%',
            position: 'absolute',
            top: 0,
            left: 0,
            right: 0,
            bottom: 0,
            zIndex: 20,
            backgroundColor: 'transparent'
        }
    };
    
    const clickThroughMixin = {
        getInitialState() {
            return {
                clickThroughModal: true
            }
        },
    
        componentDidMount() {
            this._renderModal();
            this.modalInter = setTimeout(() => {
                this._modalTarget && ReactDOM.unmountComponentAtNode(this._modalTarget);
                this._modalTarget.remove();
            }, 400);
        },
    
        componentWillUnmount() {
            this.modalInter && clearTimeout(this.modalInter);
        },
    
        _renderModal() {
            if (!this._modalTarget) {
                this._modalTarget = document.createElement('div');
                this._container = this._getContainerDOMNode().appendChild(this._modalTarget);
                ReactDOM.unstable_renderSubtreeIntoContainer(
                    this, (
    ), this._modalTarget ); } }, _getContainerDOMNode() { const node = ReactDOM.findDOMNode(this), body = document.body; return node ? node.parentNode || body : body; } }; export default clickThroughMixin;