react實(shí)現(xiàn)滑動(dòng)的方法:1、在onTouchStart事件找到touches,根據(jù)identifier中記錄新的touch出現(xiàn);2、在onTouchMove事件中根據(jù)identifier來(lái)記錄每個(gè)touch經(jīng)過(guò)的點(diǎn)的坐標(biāo);3、在onTouchEnd事件中,找到結(jié)束的touch事件,然后通過(guò)結(jié)束的touch事件劃過(guò)的點(diǎn)來(lái)計(jì)算要執(zhí)行的手勢(shì)即可。
本教程操作環(huán)境:Windows10系統(tǒng)、react18.0.0版、Dell G3電腦。
react怎么實(shí)現(xiàn)滑動(dòng)?
react 實(shí)現(xiàn)左右滑動(dòng)效果
React 中滑動(dòng)手勢(shì)的實(shí)現(xiàn)

最近做了一點(diǎn)關(guān)于react在移動(dòng)端滑動(dòng)翻頁(yè)的功能。
開始搜索了一下,發(fā)現(xiàn)居然沒找到合適的庫(kù),唯一找到了名字叫react-touch的庫(kù),一看,前端世界四五百star===自己擼,而且似乎也不是想要的功能,算了自己寫點(diǎn)吧。
看了下原理,基本就是配合onTouchStart,onTouchMove和onTouchEnd這三個(gè)事件,來(lái)記錄滑動(dòng)過(guò)的點(diǎn),然后來(lái)計(jì)算手勢(shì)。
顯然對(duì)于多點(diǎn)觸摸,需要找到每個(gè)點(diǎn)觸摸的路徑,所以有如下幾步:
-
在onTouchStart事件找到touches,根據(jù)identifier中記錄新的touch出現(xiàn)。
-
在onTouchMove事件中根據(jù)identifier來(lái)記錄每個(gè)touch經(jīng)過(guò)的點(diǎn)的坐標(biāo)。
-
在onTouchEnd事件中,找到結(jié)束的touch事件,然后通過(guò)結(jié)束的touch事件劃過(guò)的點(diǎn)來(lái)計(jì)算要執(zhí)行的手勢(shì)。
對(duì)于我來(lái)說(shuō)我只是想要上下滑動(dòng)的功能那么我就只關(guān)注單點(diǎn)觸摸的情況。
接下來(lái)準(zhǔn)備上代碼。哦,不對(duì),首先要想想要怎么封裝。開始自問(wèn)自答:
我想用一個(gè)單例模式。
是不是使用有點(diǎn)太麻煩了,還要先實(shí)例化一下?
那用靜態(tài)類?
都js了還要啥靜態(tài)類,輸出個(gè)字典完事。
那好吧,開始擼吧。
數(shù)據(jù)部分
const touchData = { touching: false, trace: [] }; // 單點(diǎn)觸摸,所以只要當(dāng)前在觸摸中,就可以把劃過(guò)的點(diǎn)記錄到trace中了 function* idGenerator() { let start = 0; while (true) { yield start; start += 1; } } //這個(gè)生成器用來(lái)生成不同事件回調(diào)的id,這樣我們可以注冊(cè)不同的回調(diào),然后在不需要的時(shí)候刪掉。 const callbacks = { onSlideUpPage: { generator: idGenerator(), callbacks: {} }, onSlideDownPage: { generator: idGenerator(), callbacks: {} } }; //存儲(chǔ)向上、下?lián)Q頁(yè)的回調(diào)函數(shù)
登錄后復(fù)制
記錄觸摸部分
這里的事件處理的是react的合成事件,并非原生事件。
function onTouchStart(evt) { if (evt.touches.length !== 1) { touchData.touching = false; touchData.trace = []; return; } touchData.touching = true; touchData.trace = [{ x: evt.touches[0].screenX, y: evt.touches[0].screenY }]; } //在onTouchStart事件,如果是多點(diǎn)觸摸直接清空所有數(shù)據(jù)。如果是單點(diǎn)觸摸,記錄第一個(gè)點(diǎn),并設(shè)置狀態(tài) function onTouchMove(evt) { if (!touchData.touching) return; touchData.trace.push({ x: evt.touches[0].screenX, y: evt.touches[0].screenY }); } //如果在單點(diǎn)觸摸過(guò)程中,持續(xù)記錄觸摸的位置。 function onTouchEnd() { if (!touchData.touching) return; let trace = touchData.trace; touchData.touching = false; touchData.trace = []; handleTouch(trace); //判斷touch類型并調(diào)用適當(dāng)回調(diào) } //在觸摸結(jié)束事件,中調(diào)用handleTouch函數(shù)來(lái)處理手勢(shì)判斷邏輯并執(zhí)行回調(diào)
登錄后復(fù)制
handleTouch函數(shù)
function handleTouch(trace) { let start = trace[0]; let end = trace[trace.length - 1]; if (end.y - start.y > 200) { Object.keys(callbacks.onSlideUpPage.callbacks).map(key => callbacks.onSlideUpPage.callbacks[key]() ); // 向上翻頁(yè) } else if (start.y - end.y > 200) { Object.keys(callbacks.onSlideDownPage.callbacks).map(key => callbacks.onSlideDownPage.callbacks[key]() ); // 向下翻頁(yè) } }
登錄后復(fù)制
在這里我只判斷了向上向下翻頁(yè)兩個(gè)事件,如果事件達(dá)成,則會(huì)調(diào)用所有注冊(cè)到該事件的回調(diào)。如果有多個(gè)回調(diào)可按照需求對(duì)回調(diào)的執(zhí)行順序進(jìn)行調(diào)整。這里應(yīng)該是無(wú)序的。
接口部分
function addSlideUpPage(f) { let key = callbacks.onSlideUpPage.generator.next().value; callbacks.onSlideUpPage.callbacks[key] = f; return key; } //注冊(cè)向上滑動(dòng)回調(diào)并返回回調(diào)id function addSlideDownPage(f) { let key = callbacks.onSlideDownPage.generator.next().value; callbacks.onSlideDownPage.callbacks[key] = f; return key; } //注冊(cè)向下滑動(dòng)回調(diào)并返回回調(diào)id function removeSlideUpPage(key) { delete callbacks.onSlideUpPage.callbacks[key]; } //使用回調(diào)id刪除向上滑動(dòng)回調(diào) function removeSlideDownPage(key) { delete callbacks.onSlideDownPage.callbacks[key]; } //使用回調(diào)id刪除向下滑動(dòng)回調(diào) export default { onTouchEnd, onTouchMove, onTouchStart, addSlideDownPage, addSlideUpPage, removeSlideDownPage, removeSlideUpPage }; //輸出所有接口函數(shù)
登錄后復(fù)制
這沒啥說(shuō)的,就是折麼簡(jiǎn)單粗暴。接下來(lái),就在react中使用吧!
在next.js中使用
我使用的next.js+create-next-app。在pages目錄下的_app.js文件中綁定所有touch事件。
//pages/_app.js import App, { Container } from "next/app"; import React from "react"; import withReduxStore from "../redux/with-redux-store"; import { Provider } from "react-redux"; import touch from "../components/touch"; class MyApp extends App { render() { const { Component, pageProps, reduxStore } = this.props; return ( <Container> <Provider store={reduxStore}> <div onTouchEnd={touch.onTouchEnd} onTouchStart={touch.onTouchStart} onTouchMove={touch.onTouchMove} > <Component {...pageProps} /> </div> { // 將所有導(dǎo)出的touch事件綁定在最外層的div上 // 這樣就可以全局注冊(cè)事件了 } </Provider> </Container> ); } } export default withReduxStore(MyApp);
登錄后復(fù)制
接下來(lái)看看如何使用。
import React, {useEffect} from "react"; import touch from "../touch"; const Example = () => { useEffect(() => { let key = touch.addSlideDownPage(() => { console.log("try to slideDownPage!!") }); return () => { touch.removeSlideDownPage(key) // 用完別忘了刪除事件 }; }, []); return ( <div>This is an example!!</div> ); };
登錄后復(fù)制
在原生react中使用
這個(gè)項(xiàng)目使用create-react-app生成的
//src/App.js import React from 'react'; import logo from './logo.svg'; import './App.css'; import touch from "./components/touch"; function App() { return ( <div className="App" onTouchEnd={touch.onTouchEnd} onTouchStart={touch.onTouchStart} onTouchMove={touch.onTouchMove} > <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p> Edit <code>src/App.js</code> and save to reload. </p> <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React </a> </header> </div> ); }
登錄后復(fù)制
結(jié)語(yǔ)
如果真的有人仔細(xì)看了代碼,可能會(huì)有個(gè)問(wèn)題,這個(gè)touch.js里的內(nèi)容除了使用了react的合成事件,然后就沒react什么事了,好像不太常規(guī)。
的確是這樣,就沒關(guān)react什么事了。解釋就是這些數(shù)據(jù)不用通過(guò)react的state或者redux的state太傳遞,一來(lái)是在性能上,一更新redux或者react的state就會(huì)觸發(fā)react的重新渲染,沒有必要,二就是希望可以全局使用這些接口,所以就并沒有借助react的機(jī)制。其實(shí)這就像是react所說(shuō)的uncontrolled components。
最后附上完整的touch.js
//touch.js const touchData = { touching: false, trace: [] }; function* idGenerator() { let start = 0; while (true) { yield start; start += 1; } } const callbacks = { onSlideUpPage: { generator: idGenerator(), callbacks: {} }, onSlideDownPage: { generator: idGenerator(), callbacks: {} } }; function onTouchStart(evt) { if (evt.touches.length !== 1) { touchData.touching = false; touchData.trace = []; return; } touchData.touching = true; touchData.trace = [{ x: evt.touches[0].screenX, y: evt.touches[0].screenY }]; } function onTouchMove(evt) { if (!touchData.touching) return; touchData.trace.push({ x: evt.touches[0].screenX, y: evt.touches[0].screenY }); } function onTouchEnd() { if (!touchData.touching) return; let trace = touchData.trace; touchData.touching = false; touchData.trace = []; handleTouch(trace); } function handleTouch(trace) { let start = trace[0]; let end = trace[trace.length - 1]; if (end.y - start.y > 200) { Object.keys(callbacks.onSlideUpPage.callbacks).map(key => callbacks.onSlideUpPage.callbacks[key]() ); } else if (start.y - end.y > 200) { Object.keys(callbacks.onSlideDownPage.callbacks).map(key => callbacks.onSlideDownPage.callbacks[key]() ); } } function addSlideUpPage(f) { let key = callbacks.onSlideUpPage.generator.next().value; callbacks.onSlideUpPage.callbacks[key] = f; return key; } function addSlideDownPage(f) { let key = callbacks.onSlideDownPage.generator.next().value; callbacks.onSlideDownPage.callbacks[key] = f; return key; } function removeSlideUpPage(key) { delete callbacks.onSlideUpPage.callbacks[key]; } function removeSlideDownPage(key) { delete callbacks.onSlideDownPage.callbacks[key]; } export default { onTouchEnd, onTouchMove, onTouchStart, addSlideDownPage, addSlideUpPage, removeSlideDownPage, removeSlideUpPage };
登錄后復(fù)制
推薦學(xué)習(xí):《react視頻教程》