React實(shí)現(xiàn)前端選區(qū)的示例代碼
什么是選區(qū)
前端選區(qū)非常常見(jiàn),通過(guò)鼠標(biāo)的點(diǎn)擊和移動(dòng)來(lái)創(chuàng)建一個(gè)選中頁(yè)面,在多個(gè)元素需要被選中的情況下,一個(gè)個(gè)點(diǎn)擊顯的非常拖沓,所以需要通過(guò)建立選區(qū)來(lái)應(yīng)對(duì)多個(gè)元素的操作。以下是簡(jiǎn)單的建立選區(qū)圖片例子:

如何建立選區(qū)
瀏覽器繪制選區(qū)方式
在瀏覽器中繪制一個(gè)矩形,通常是通過(guò)其元素的top和left來(lái)決定,瀏覽器通過(guò)確定元素的top和left位置,并監(jiān)聽(tīng)鼠標(biāo)在移動(dòng)過(guò)程中的偏移量offsetX和offsetY來(lái)計(jì)算元素的總寬高,最后繪制成元素的總體形狀,固定好元素的top和left極為重要。如下是瀏覽器繪制的示意圖:

瀏覽器在繪制時(shí),總是以左上角的頂點(diǎn)作為元素的起始位置,也就是說(shuō)如果你的選區(qū)是從右往左或者從下往上建立,瀏覽器都會(huì)以最終圖形的左上角頂點(diǎn)作為元素的起始位置進(jìn)行繪制。所以確定左上角頂點(diǎn)位置尤為關(guān)鍵。
React計(jì)算選區(qū)范圍
通過(guò)監(jiān)聽(tīng)瀏覽器的鼠標(biāo)移動(dòng)事件來(lái)獲取鼠標(biāo)移動(dòng)的范圍,在這里我們需要先獲取鼠標(biāo)初始位置,通過(guò)movedown事件確定鼠標(biāo)起始位置,通過(guò)isMove來(lái)判斷是否鼠標(biāo)落下并開(kāi)始移動(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)后,通過(guò)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,以此類(lèi)推即可。代碼如下圖:
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ū)的寬度和高度,通過(guò)計(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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
react-router-dom 嵌套路由的實(shí)現(xiàn)
這篇文章主要介紹了react-router-dom 嵌套路由的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05
使用react+redux實(shí)現(xiàn)彈出框案例
這篇文章主要為大家詳細(xì)介紹了使用react+redux實(shí)現(xiàn)彈出框案例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08
React動(dòng)畫(huà)實(shí)現(xiàn)方案Framer Motion讓頁(yè)面自己動(dòng)起來(lái)
這篇文章主要為大家介紹了React動(dòng)畫(huà)實(shí)現(xiàn)方案Framer Motion讓頁(yè)面自己動(dòng)起來(lái),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
react中使用echarts,并實(shí)現(xiàn)tooltip循環(huán)輪播方式
這篇文章主要介紹了react中使用echarts,并實(shí)現(xiàn)tooltip循環(huán)輪播方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01
React路由中的redux和redux知識(shí)點(diǎn)拓展
這篇文章主要介紹了React路由中的redux和redux知識(shí)點(diǎn)拓展,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的朋友可以參考學(xué)習(xí)一下2022-08-08
react-native ListView下拉刷新上拉加載實(shí)現(xiàn)代碼
本篇文章主要介紹了react-native ListView下拉刷新上拉加載實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08

