内容简介: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)}}
以上所述就是小编给大家介绍的《React/anu实现Touchable》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- php如何实现session,自己实现session,laravel如何实现session
- AOP如何实现及实现原理
- webpack 实现 HMR 及其实现原理
- Docker实现原理之 - OverlayFS实现原理
- 为什么实现 .NET 的 ICollection 集合时需要实现 SyncRoot 属性?如何正确实现这个属性?
- 自己实现集合框架(十):顺序栈的实现
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Head First Design Patterns—深入淺出設計模式
天瓏
寫應用程式時需要依照需求預先規劃、設計,而設計模式累積了前人的經歷,經由四人幫彙整出一系列的設計模式,以利後人可以套用。本書集合四人幫的23個模式(十幾年前的事)外加這十幾年來新增的一些模式,作者群以詼諧、幽默、圖文並茂、打破傳統著書的方式,由淺入深地詳解了設計模式的精神及重點。全書全部以當紅的 Java 程式語言為範例。 本書特點: * 全世界第二本書......一起来看看 《Head First Design Patterns—深入淺出設計模式》 这本书的介绍吧!