zoukankan      html  css  js  c++  java
  • React/anu实现Touchable

    在RN中有一个叫Touchable 的组件,这里我们重演如何实现它。

    Touchable存在的意义是屏蔽click的问题。移动端与手机的click 在一些浏览器是有差异,比如说著名的300ms延迟。

    Touchable的实现要点是将事件通过包装,然后绑定在它的下一级元素节点上。

    而一级元素节点可以通过this.props.children[0]取到。为了解决兼容问题,我们通常用React.Children.only(this.props.children)来取这个节点。

    而事件的传递则通过React.cloneElement(child, newPropsWithEvents)实现。

    最后是事件包装,在移动端下,点击事件是通过4个事件实现的:ontouchstart, ontouchmove, ontouchend, ontouchcancel

    为了模块化的需要,我们将事件包装这块拆出来,叫gesture.js

    /**
     * touchable手势处理,解决Scroller内部的手势冲突
     * 在滚动时不会触发active
     * 在active之后发生滚动会取消active状态
     */
    import ReactDOM from 'react-dom';
    const TAP_SLOP = 5;
    export const TAP_DELAY = 50;
    /**
     * @param endPoint
     * @param startPoint
     * @returns {number}
     * 求两个点之间的距离
     */
    function getDistance(endPoint, startPoint) {
        return Math.sqrt(Math.pow(endPoint.pageX - startPoint.pageX, 2) + Math.pow(endPoint.pageY - startPoint.pageY, 2));
    }
    
    /**
     * @param endPoint
     * @param startPoint
     * @returns {boolean}
     *如果移动的距离太远了,应该是认为其他事件,而不是Tap事件
     */
    function onTouchMoveShouldCancelTap(endPoint, startPoint) {
        return getDistance(endPoint, startPoint) > TAP_SLOP;
    }
    
    /**
     * @param evt
     * @returns {touch/null}
     * 获取触点
     */
    function getTouchPoint(evt) {
        return evt.touches.length ? { pageX: evt.touches[0].pageX, pageY: evt.touches[0].pageY } : null;
    }
    
    /**
     * @param domNode
     * @param activeClass
     * 移除item的activeClass
     */
    function removeActiveClass(domNode, activeClass) {
        if (domNode && activeClass) {
            domNode.className = domNode.className.replace(` ${activeClass}`, '');
        }
    }
    
    /**
     * @param scroller
     * @returns {boolean}
     * 判断组件是否在滚动
     */
    function isScrolling(scroller) {
        return scroller ? scroller.isScrolling : false;
    }
    
    function isAnySwipeMenuOpen(swipeMenuList) {
        return swipeMenuList ? swipeMenuList.openIndex !== -1 : false;
    }
    
    // touchStart的位置,是否需要放弃Tap触发,Tap周期(start,move,end)是否已经结束
    let startPoint,
        shouldAbortTap;
    let captured = null;
    
    export default function ({
        component,
        scroller,
        swipeMenuList,
        activeClass,
        onTap,
        onTouchStart,
        disabled
    }) {
        const gestureObj = {
            onTouchStart(evt) {
                const domNode = ReactDOM.findDOMNode(component);
                removeActiveClass(domNode, activeClass);
                // 如果组件正在滚动,直接放弃Tap触发
                shouldAbortTap = isScrolling(scroller) || isAnySwipeMenuOpen(swipeMenuList);
                startPoint = getTouchPoint(evt);
                onTouchStart(evt);
                if (!captured) {
                    captured = domNode;
                }
                // TAP_DELAY之后再次判断是否要触发Tap,如果这段时间内出现了大的位移,if后面的逻辑就不会执行
                setTimeout(() => {
                    const className = activeClass;
                    if (!shouldAbortTap && className && captured === domNode && !disabled) {
                        domNode.className += ` ${className}`;
                    }
                }, TAP_DELAY);
            },
            onTouchMove(evt) {
                const domNode = ReactDOM.findDOMNode(component);
                const currentPoint = getTouchPoint(evt);
                // 根据touchmove的距离判断是否要放弃tap
                if (onTouchMoveShouldCancelTap(currentPoint, startPoint)) {
                    shouldAbortTap = true;
                    captured = null;
                    removeActiveClass(domNode, activeClass);
                }
            },
            onTouchEnd(evt) {
                const target = evt.target;
                const domNode = ReactDOM.findDOMNode(component);
                // 如果需要触发tap,在TAP_DELAY之后触发onTap回调
                if (!shouldAbortTap && captured === domNode) {
                    setTimeout(() => {
                        if (!disabled) {
                            onTap(target);
                        }
                        removeActiveClass(domNode, activeClass);
                        captured = null;
                    }, TAP_DELAY + 10);
                } else if (shouldAbortTap) {
                    captured = null;
                }
            },
            onTouchCancel() {
                const domNode = ReactDOM.findDOMNode(component);
                removeActiveClass(domNode, activeClass);
            }
        };
    
        return gestureObj;
    }
    

    Touchable.js的源码如下

    import { Component, PropTypes,cloneElement } from 'react';
    import gesture from './gesture';
    
    export class Touchable extends Component {
    
        static propTypes = {
            /**
             * @property touchClass
             * @type String
             * @default null
             * @description 触摸Touchable时附加的className,可以用来实现Native常见的触摸反馈功能(例如给触摸区域添加深色背景或者改变透明度等等)。
             */
            touchClass: PropTypes.string,
            /**
             * @property onTap
             * @type Function
             * @default null
             * @param {DOMElement} target tap事件的target
             * @description 给Touchable绑定的onTap事件。
             */
            onTap: PropTypes.func,
            /**
             * @property disabled
             * @type Bool
             * @default false
             * @description Touchable是否处于可点击状态,如果设为true,那么onTap事件回调和触摸反馈效果都不可用。
             * @version 3.0.7
             */
            disabled: PropTypes.bool,
            /**
             * @skip 给List定制的属性
             */
            onTouchStart: PropTypes.func,
            /**
             * @skip 内部使用标志
             */
            internalUse: PropTypes.bool,
            children: PropTypes.object
        };
    
        static defaultProps = {
            onTouchStart: () => {
            },
            touchClass: null,
            onTap: () => {
            },
            internalUse: false,
            disabled: false
        };
    
        static contextTypes = {
            scroller: PropTypes.object,
            swipeMenuList: PropTypes.object
        };
    
        render() {
            if (process.env.NODE_ENV !== 'production') {
                if (this.props.touchClass == null && !this.props.internalUse) {
                    console.error('yo-touchable: Touchable组件没有设置touchClass, 出于用户体验考虑, 应该尽量给触摸区域添加触摸反馈。');
                }
            }
    
            const onlyChild = React.Children.only(this.props.children);
            const gestureObj = gesture({
                component: this,
                scroller: this.context.scroller,
                swipeMenuList: this.context.swipeMenuList,
                activeClass: this.props.touchClass,
                onTap: this.props.onTap,
                onTouchStart: this.props.onTouchStart,
                disabled: this.props.disabled
            });
            const { onTouchStart, onTouchMove, onTouchEnd, onTouchCancel } = gestureObj;
    
            return cloneElement(onlyChild, { onTouchStart, onTouchMove, onTouchEnd, onTouchCancel });
        }
    }
    

    Touchable就是将用户传人它的属性提出来,复制到第一个子节点的props上。这个过程我们用cloneElement实现。

    使用

    <Touchable onTap={(e)=>{ console.log(e)}}
    
    
  • 相关阅读:
    搭建strom 的开发环境
    maven 的plugin 的使用
    Maven 的dependency 的 classifier的作用
    Maven中的dependency的scope作用域详解
    Supervisor-进程监控自动重启
    websocket 实战
    vue 监听路由变化
    vux-uploader 图片上传组件
    vue 定义全局函数
    判断对象属性的值是否空,如为空,删除该属性
  • 原文地址:https://www.cnblogs.com/rubylouvre/p/7028923.html
Copyright © 2011-2022 走看看