React實(shí)現(xiàn)前端選區(qū)的示例代碼
什么是選區(qū)
前端選區(qū)非常常見,通過鼠標(biāo)的點(diǎn)擊和移動(dòng)來創(chuàng)建一個(gè)選中頁面,在多個(gè)元素需要被選中的情況下,一個(gè)個(gè)點(diǎn)擊顯的非常拖沓,所以需要通過建立選區(qū)來應(yīng)對多個(gè)元素的操作。以下是簡單的建立選區(qū)圖片例子:
如何建立選區(qū)
瀏覽器繪制選區(qū)方式
在瀏覽器中繪制一個(gè)矩形,通常是通過其元素的top和left來決定,瀏覽器通過確定元素的top和left位置,并監(jiān)聽鼠標(biāo)在移動(dòng)過程中的偏移量offsetX和offsetY來計(jì)算元素的總寬高,最后繪制成元素的總體形狀,固定好元素的top和left極為重要。如下是瀏覽器繪制的示意圖:
瀏覽器在繪制時(shí),總是以左上角的頂點(diǎn)作為元素的起始位置,也就是說如果你的選區(qū)是從右往左或者從下往上建立,瀏覽器都會以最終圖形的左上角頂點(diǎn)作為元素的起始位置進(jìn)行繪制。所以確定左上角頂點(diǎn)位置尤為關(guān)鍵。
React計(jì)算選區(qū)范圍
通過監(jiān)聽瀏覽器的鼠標(biāo)移動(dòng)事件來獲取鼠標(biāo)移動(dòng)的范圍,在這里我們需要先獲取鼠標(biāo)初始位置,通過movedown事件確定鼠標(biāo)起始位置,通過isMove來判斷是否鼠標(biāo)落下并開始移動(dòng),記錄鼠標(biāo)落點(diǎn)位置。代碼如下圖所示:
const [isMove, setIsMove] = useState<boolean>(false); const [downAndUpPosition, setDownAndUpPosition] = useState<Position>(); const handleMouseDown = (event: React.MouseEvent) => { setIsMove(true); let mouseDownPosition: Position = { offsetX: event.pageX - left, offsetY: event.pageY, }; setDownAndUpPosition(mouseDownPosition); };
記錄鼠標(biāo)落點(diǎn)后,通過mousemove事件記錄鼠標(biāo)移動(dòng)坐標(biāo)點(diǎn),如果鼠標(biāo)未落下則不觸發(fā)移動(dòng)事件,避免重復(fù)渲染,最后在moveup事件中取消移動(dòng)標(biāo)記即可:
const [movePosition, setMovePosition] = useState<Position>(); const handleMouseMove = (event: React.MouseEvent) => { if (!isMove) return; let movePosition: Position = { offsetX: event.pageX - left, offsetY: event.pageY, }; setMovePosition(movePosition); }; const handleMouseUp = () => { setIsMove(false); };
得到鼠標(biāo)落點(diǎn)位置和鼠標(biāo)移動(dòng)坐標(biāo)后,我們需要去計(jì)算鼠標(biāo)移動(dòng)坐標(biāo)與起始位置的坐標(biāo)象限,如果起始位置的left與top均小于鼠標(biāo)移動(dòng)后坐標(biāo),則選區(qū)在第四象限。以起始位置為坐標(biāo)原點(diǎn)去計(jì)算。代碼如下所示:
const returnDivPosition = ( downAndUpPosition: Position, movePosition: Position ) => { const downPageX = downAndUpPosition.offsetX; const downPageY = downAndUpPosition.offsetY; const movePageX = movePosition.offsetX; const movePageY = movePosition.offsetY; if (downPageX >= movePageX && downPageY >= movePageY) { return 1; } if (downPageX <= movePageX && downPageY >= movePageY) { return 2; } if (downPageX >= movePageX && downPageY <= movePageY) { return 3; } if (downPageX <= movePageX && downPageY <= movePageY) { return 4; } };
計(jì)算出鼠標(biāo)最終位置處于哪個(gè)象限后,我們就可以計(jì)算出選區(qū)的top和left。如果為第一象限則鼠標(biāo)移動(dòng)的最終位置就是元素偏移量,如果為第二象限則鼠標(biāo)移動(dòng)最終位置的top為元素偏移top,鼠標(biāo)落點(diǎn)left為元素偏移left,以此類推即可。代碼如下圖:
const offsetPosition = useMemo(() => { if (downAndUpPosition && movePosition) { const quadrant = returnDivPosition(downAndUpPosition, movePosition); switch (quadrant) { case 1: return { top: movePosition.offsetY, left: movePosition.offsetX, }; case 2: return { top: movePosition.offsetY, left: downAndUpPosition.offsetX, }; case 3: return { top: downAndUpPosition.offsetY, left: movePosition.offsetX, }; case 4: return { top: downAndUpPosition.offsetY, left: downAndUpPosition.offsetX, }; } } return { top: 0, left: 0, }; }, [downAndUpPosition, movePosition]);
最后我們計(jì)算選區(qū)的寬度和高度,通過計(jì)算落點(diǎn)位置與鼠標(biāo)移動(dòng)后最終位置的差值可獲取寬高。代碼如下:
const offsetSize = useMemo(() => { if (downAndUpPosition && movePosition) { return { width: Math.abs(downAndUpPosition.offsetX - movePosition.offsetX), height: Math.abs(downAndUpPosition.offsetY - movePosition.offsetY), }; } return { width: 0, height: 0, }; }, [downAndUpPosition, movePosition]);
這樣就能夠創(chuàng)建一個(gè)選區(qū),完整代碼如下圖:
type Position = { offsetX: number; offsetY: number; }; const boardStyle: CSSProperties = { width: "100%", height: "calc(100vh - 10px)", position: "relative", }; const SelectArea = ()=>{ const [isMove, setIsMove] = useState<boolean>(false); const [downAndUpPosition, setDownAndUpPosition] = useState<Position>(); const [movePosition, setMovePosition] = useState<Position>(); const handleMouseDown = (event: React.MouseEvent) => { setIsMove(true); let mouseDownPosition: Position = { offsetX: event.pageX - left, offsetY: event.pageY, }; setDownAndUpPosition(mouseDownPosition); }; const handleMouseMove = (event: React.MouseEvent) => { if (!isMove) return; let movePosition: Position = { offsetX: event.pageX - left, offsetY: event.pageY, }; setMovePosition(movePosition); }; const handleMouseUp = () => { setIsMove(false); }; const returnDivPosition = ( downAndUpPosition: Position, movePosition: Position ) => { const downPageX = downAndUpPosition.offsetX; const downPageY = downAndUpPosition.offsetY; const movePageX = movePosition.offsetX; const movePageY = movePosition.offsetY; if (downPageX >= movePageX && downPageY >= movePageY) { return 1; } if (downPageX <= movePageX && downPageY >= movePageY) { return 2; } if (downPageX >= movePageX && downPageY <= movePageY) { return 3; } if (downPageX <= movePageX && downPageY <= movePageY) { return 4; } }; const offsetSize = useMemo(() => { if (downAndUpPosition && movePosition) { return { width: Math.abs(downAndUpPosition.offsetX - movePosition.offsetX), height: Math.abs(downAndUpPosition.offsetY - movePosition.offsetY), }; } return { width: 0, height: 0, }; }, [downAndUpPosition, movePosition]); const offsetPosition = useMemo(() => { if (downAndUpPosition && movePosition) { const quadrant = returnDivPosition(downAndUpPosition, movePosition); switch (quadrant) { case 1: return { top: movePosition.offsetY, left: movePosition.offsetX, }; case 2: return { top: movePosition.offsetY, left: downAndUpPosition.offsetX, }; case 3: return { top: downAndUpPosition.offsetY, left: movePosition.offsetX, }; case 4: return { top: downAndUpPosition.offsetY, left: downAndUpPosition.offsetX, }; } } return { top: 0, left: 0, }; }, [downAndUpPosition, movePosition]); return ( <div onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} onMouseUp={handleMouseUp} style={boardStyle} > <Electorate top={offsetPosition.top} left={offsetPosition.left} width={offsetSize.width} height={offsetSize.height} /> </div> ); } type Props = { top: number; left: number; width: number; height: number; }; const Electorate: FC<Props> = ({ top, left, width, height }) => { return ( <div style={{ top: `${top}px`, left: `${left}px`, width: `${Math.abs(width)}px`, height: `${Math.abs(height)}px`, }} className="electorate" /> ); }; //electorate樣式 .electorate { position: absolute; border: 1px solid rgba(33, 127, 235, 0.534); background-color: rgba(33, 127, 235, 0.3); }
到此這篇關(guān)于React實(shí)現(xiàn)前端選區(qū)的示例代碼的文章就介紹到這了,更多相關(guān)React 前端選區(qū)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
react-router-dom 嵌套路由的實(shí)現(xiàn)
這篇文章主要介紹了react-router-dom 嵌套路由的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05使用react+redux實(shí)現(xiàn)彈出框案例
這篇文章主要為大家詳細(xì)介紹了使用react+redux實(shí)現(xiàn)彈出框案例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08React動(dòng)畫實(shí)現(xiàn)方案Framer Motion讓頁面自己動(dòng)起來
這篇文章主要為大家介紹了React動(dòng)畫實(shí)現(xiàn)方案Framer Motion讓頁面自己動(dòng)起來,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10react中使用echarts,并實(shí)現(xiàn)tooltip循環(huán)輪播方式
這篇文章主要介紹了react中使用echarts,并實(shí)現(xiàn)tooltip循環(huán)輪播方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01React路由中的redux和redux知識點(diǎn)拓展
這篇文章主要介紹了React路由中的redux和redux知識點(diǎn)拓展,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的朋友可以參考學(xué)習(xí)一下2022-08-08react-native ListView下拉刷新上拉加載實(shí)現(xiàn)代碼
本篇文章主要介紹了react-native ListView下拉刷新上拉加載實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08