標籤:lin settime 效能 解決方案 tip const script 比較 方法
關於點透事件這裡不再贅述,如果不清楚的可以上網搜一搜,或者看小火柴的這篇文章。
這裡是自己在做移動端時,在列表滑動的時候,遇到的點透問題。出現這個問題的來由是因為在轉場的時候,各個手機的轉場效果不一樣,有的比較好,但是在有些低端機上,轉場顯得有點卡,於是就把過渡效果去掉了,因此就是直接的路由切換。【具體事件具體分析,可能我遇到的問題並不適合你,這裡只是貼出來共用】
先看下面兩張圖片:
點擊列表頁的按鈕會切換到下一個頁面,但是在下一個頁面上的每一個條目都是可以點擊的,這時就會觸發了下一個頁面的彈窗,事實上我們並不想直接顯示這個彈窗,而是要等待使用者點擊。
你可能在項目中的列表頁寫了如下的一段代碼:
render() { return ( <ul className="list"> { list.map((l, index) => { return ( <li key={ `list${index}` } onClick={ () => doSomething() }> { `item${index}` } </li> ) }) } </ul> );}
在一個列表中的每個項目上綁定了點擊事件,但是當點擊之後切換到下一頁。當時移動端的點擊事件都會有300ms的延遲,因此在切換了頁面之後,瀏覽器會再次判斷點擊的行為,此時如果下一個頁面都有可以觸發點擊的元素,這時候就觸發了下一個頁面的點擊行為。
於是,你可能會這麼做,將onClick
事件換成onTouchEnd
事件
<li key={ `list${index}` } onTouchStart= { event => event.preventDefault() } onTouchEnd={ () => doSomething() }> { `item${index}` }</li>
但是,每次滑動的時候,其實你也觸發了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像素,並且touchstart
和touchend
的時間差小於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 ( <Page style={ this.props.style }> <MyComponent { ...this.props } container={ this } /> { this.state.modal && <div style={ styles.modal }></div> } </Page> ) } }); 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, (<div style={ styles.modal }></div>), this._modalTarget ); } }, _getContainerDOMNode() { const node = ReactDOM.findDOMNode(this), body = document.body; return node ? node.parentNode || body : body; }};export default clickThroughMixin;
移動端APP列表點透事件處理方法