React實(shí)現(xiàn)多個(gè)場(chǎng)景下鼠標(biāo)跟隨提示框詳解
前言
鼠標(biāo)跟隨框的作用如下圖所示,可以在前端頁(yè)面上,為我們后續(xù)的鼠標(biāo)操作進(jìn)行提示說(shuō)明,提升用戶的體驗(yàn)。本文將通過(guò)多種方式去實(shí)現(xiàn),從而滿足不同場(chǎng)景下的需求。
實(shí)現(xiàn)原理
實(shí)現(xiàn)鼠標(biāo)跟隨框的原理很簡(jiǎn)單,就是監(jiān)聽(tīng)鼠標(biāo)在頁(yè)面上的坐標(biāo),然后利用相對(duì)定位(position: relative;
)、絕對(duì)定位(position: absolute;
)和固定定位(position: fixed;
)等相關(guān)知識(shí)。
本文是利用的 React,但只要知道原理,技術(shù)棧什么的問(wèn)題都不大。具體怎么實(shí)現(xiàn),咱接著往下看。
固定定位實(shí)現(xiàn)
固定定位的好處是,相對(duì)于瀏覽器窗口定位,而鼠標(biāo)跟隨框的通用場(chǎng)景就是跟隨鼠標(biāo)移動(dòng)。
MousePositionDemo
我們先寫一個(gè)頁(yè)面,用來(lái)引入鼠標(biāo)跟隨框:
index.tsx
import React, { useEffect, useState } from 'react'; import './index.less'; import { Button } from 'antd'; import MousePositionModal from './MousePositionModal'; const MousePositionDemo = () => { const [visible, setVisible] = useState(false); return ( <div id="mouse-position-demo" className="mouse-position-demo"> <Button onClick={() => { setVisible(true) }}>點(diǎn)擊顯示</Button> <Button onClick={() => { setVisible(false) }}>點(diǎn)擊關(guān)閉</Button> {/* 鼠標(biāo)跟隨框 */} <MousePositionModal visible={visible} content="鼠標(biāo)跟隨" defaultPosition={{ x: 32, y: 32 }} /> </div> ) } export default MousePositionDemo;
index.less
.mouse-position-demo { margin: 0 auto; height: 500px; width: 500px; background-color: #fff; padding: 24px 24px; }
MousePositionModal
這里我們首先通過(guò) clientX
, clientY
來(lái)返回當(dāng)事件被觸發(fā)時(shí)鼠標(biāo)指針相對(duì)于瀏覽器頁(yè)面(或客戶區(qū))的水平和垂直坐標(biāo)。
當(dāng)然,僅這樣可能是不夠的,我們會(huì)發(fā)現(xiàn)在鼠標(biāo)靠近瀏覽器頁(yè)面最右側(cè)的時(shí)候,鼠標(biāo)跟隨框的部分頁(yè)面會(huì)被隱藏掉。為了能夠完整的展示鼠標(biāo)跟隨框中的信息,我們需要進(jìn)行一個(gè)簡(jiǎn)單的計(jì)算,當(dāng) 鼠標(biāo)位置的橫坐標(biāo) > 鼠標(biāo)位置橫坐標(biāo) - 鼠標(biāo)選擇框的寬度 時(shí),就讓 鼠標(biāo)跟隨框的橫坐標(biāo) = 鼠標(biāo)位置橫坐標(biāo) - 鼠標(biāo)選擇框的寬度。
鼠標(biāo)跟隨框的具體實(shí)現(xiàn)如下:
index.tsx
import React, { useState, useEffect } from 'react'; import './index.less'; interface IMousePositionModal { visible: boolean; content: string; defaultPosition: { x: number, y: number } } const MousePositionModal = (props: IMousePositionModal) => { const { visible, content, defaultPosition } = props; const [left, setLeft] = useState(defaultPosition.x); const [top, setTop] = useState(defaultPosition.y); useEffect(() => { if (visible) { show(); } }, [visible]); const show = () => { const modal = document.getElementById('mouse-position-modal'); if (modal) { document.onmousemove = (event) => { const { clientX, clientY } = event || window.event; const clientWidth = document.body.clientWidth || document.documentElement.clientWidth; const { offsetWidth } = modal; let x = clientX + 18; const y = clientY + 18; if (x >= clientWidth - offsetWidth) { x = clientWidth - offsetWidth; } setLeft(x); setTop(y); }; } }; return ( <div id="mouse-position-modal" className="mouse-position-modal" style={{ left: `${left}px`, top: `${top}px`, visibility: `${visible ? 'visible' : 'hidden'}`}} > <div className="mouse-position-modal-content">{content}</div> </div> ); }; export default MousePositionModal;
這里有兩個(gè)地點(diǎn)需要注意:一是給鼠標(biāo)跟隨框設(shè)置固定定位,二是要將 z-index
的值設(shè)置的足夠大,不然有可能會(huì)被頁(yè)面上的其他元素遮住。
index.less
.mouse-position-modal { min-width: 240px; height: 57px; background: #fff; box-shadow: 0 4px 12px 0 rgba(0, 0, 0, 0.15); border-radius: 4px; position: fixed; z-index: 2000; padding: 8px 12px; .mouse-position-modal-content { font-size: 16px; color: #262626; } }
絕對(duì)定位(相對(duì)于整個(gè)瀏覽器窗口)
利用絕對(duì)定位我們可以實(shí)現(xiàn)和上面固定定位相似的效果,但是有個(gè)隱患需要注意,如果鼠標(biāo)跟隨框的某個(gè)相近的父元素用了相對(duì)定位,那鼠標(biāo)跟隨框的實(shí)際位置就可能會(huì)亂套了。
絕對(duì)定位不僅要考慮可視范圍內(nèi)的位置,還需要考慮瀏覽器頁(yè)面滾動(dòng)的距離。
具體實(shí)現(xiàn)如下:
MousePositionDemo
和固定定位一樣
MousePositionModal
index.tsx
import React, { useState, useEffect } from 'react'; import './index.less'; interface IMousePositionModal { visible: boolean; content: string; defaultPosition: { x: number, y: number } } const MousePositionModal = (props: IMousePositionModal) => { const { visible, content, defaultPosition } = props; const [left, setLeft] = useState(defaultPosition.x); const [top, setTop] = useState(defaultPosition.y); useEffect(() => { if (visible) { show(); } }, [visible]); const show = () => { const modal = document.getElementById('mouse-position-modal'); if (modal) { document.onmousemove = (event) => { const { clientX, clientY, pageX, pageY } = event || window.event; const sl = document.body.scrollLeft || document.documentElement.scrollLeft; const st = document.body.scrollTop || document.documentElement.scrollTop; const clientWidth = document.body.clientWidth || document.documentElement.clientWidth; const { offsetWidth } = modal; let x = (pageX || clientX + sl) + 18; const y = (pageY || clientY + st) + 18; if (x >= clientWidth - offsetWidth) { x = clientWidth - offsetWidth; } setLeft(x); setTop(y); }; } }; return ( <div id="mouse-position-modal" className="mouse-position-modal" style={{ left: `${left}px`, top: `${top}px`, visibility: `${visible ? 'visible' : 'hidden'}`}} > <div className="mouse-position-modal-content">{content}</div> </div> ); }; export default MousePositionModal;
index.less
.mouse-position-modal { min-width: 240px; height: 57px; background: #fff; box-shadow: 0 4px 12px 0 rgba(0, 0, 0, 0.15); border-radius: 4px; position: absolute; z-index: 2000; padding: 8px 12px; .mouse-position-modal-content { font-size: 16px; color: #262626; } }
絕對(duì)定位和相對(duì)定位(相對(duì)于鼠標(biāo)跟隨框的父元素)
有時(shí)候我們可能并不需要在整個(gè)頁(yè)面進(jìn)行鼠標(biāo)跟隨框的提示,在某些情況下只需要鼠標(biāo)在進(jìn)入頁(yè)面的部分區(qū)域才進(jìn)行提示。
如下圖所示:
這個(gè)時(shí)候就需要同時(shí)用到絕對(duì)定位和相對(duì)定位以及 offsetX
和 offsetY
。
offsetX: 規(guī)定了事件對(duì)象與目標(biāo)節(jié)點(diǎn)的內(nèi)填充邊(padding edge)在 X 軸方向上的偏移量 offsetY: 規(guī)定了事件對(duì)象與目標(biāo)節(jié)點(diǎn)的內(nèi)填充邊(padding edge)在 Y 軸方向上的偏移量
具體實(shí)現(xiàn)如下:
MousePositionDemo
index.tsx
import React, { useEffect, useState } from 'react'; import './index.less'; import { Button } from 'antd'; import MousePositionModal2 from './MousePositionModal2'; // 兼容offsetX const getOffsetX = (e: any) =>{ const event = e || window.event; const srcObj = e.target || e.srcElement; if (event.offsetX){ return event.offsetX; }else{ const rect = srcObj.getBoundingClientRect(); const clientx = event.clientX; return clientx - rect.left; } } // 兼容offsetY const getOffsetY = (e: any) => { const event = e || window.event; const srcObj = e.target || e.srcElement; if (event.offsetY){ return event.offsetY; }else{ const rect = srcObj.getBoundingClientRect(); const clientx = event.clientY; return clientx - rect.top; } } const MousePositionDemo = () => { const [visible, setVisible] = useState(false); const [defaultPosition, setDefaultPosition] = useState({ x: 32, y: 32 }) useEffect(() => { const ele = document.getElementById('mouse-position-demo') as HTMLElement; ele.addEventListener('mouseenter', show) ele.addEventListener('mousemove', mouseMove) ele.addEventListener('mouseleave', hide) return () => { ele.removeEventListener('mouseenter', show) ele.removeEventListener('mousemove', mouseMove) ele.removeEventListener('mouseleave', hide) } }, []) const show = () => { setVisible(true) } const hide = () => { setVisible(false) } const mouseMove = (e: MouseEvent) => { let x = getOffsetX(e) + 18; const y = getOffsetY(e) + 18; setDefaultPosition({ x, y }); } return ( <div id="mouse-position-demo" className="mouse-position-demo"> <MousePositionModal2 visible={visible} content="鼠標(biāo)跟隨" defaultPosition={defaultPosition} /> </div> ) } export default MousePositionDemo;
注意要將這里 position
設(shè)置為 relative
。
index.less
.mouse-position-demo { margin: 0 auto; height: 500px; width: 500px; background-color: #fff; padding: 24px 24px; position: relative; }
MousePositionModal2
index.tsx
import React, { useState, useEffect } from 'react'; import './index.less'; interface IMousePositionModal { visible: boolean; content: string; defaultPosition: { x: number, y: number } } const MousePositionModal2 = (props: IMousePositionModal) => { const { visible, content, defaultPosition } = props; const { x, y } = defaultPosition; return ( <div id="mouse-position-modal" className="mouse-position-modal" style={{ left: `${x}px`, top: `${y}px`, visibility: `${visible ? 'visible' : 'hidden'}` }} > <div className="mouse-position-modal-content">{content}</div> </div> ); }; export default MousePositionModal2;
注意要將這里 position 設(shè)置為 absolute 。
index.less
.mouse-position-modal { min-width: 240px; height: 57px; background: #fff; box-shadow: 0 4px 12px 0 rgba(0, 0, 0, 0.15); border-radius: 4px; position: absolute; z-index: 2000; padding: 8px 12px; .mouse-position-modal-content { font-size: 16px; color: #262626; } }
最后
本文結(jié)合實(shí)例,詳細(xì)的介紹了鼠標(biāo)跟隨框在三種場(chǎng)景下的三種具體實(shí)現(xiàn)的方法
以上就是React實(shí)現(xiàn)多個(gè)場(chǎng)景下鼠標(biāo)跟隨提示框詳解的詳細(xì)內(nèi)容,更多關(guān)于React鼠標(biāo)跟隨提示框的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
教你使用vscode 搭建react-native開(kāi)發(fā)環(huán)境
本文記錄如何使用vscode打造一個(gè)現(xiàn)代化的react-native開(kāi)發(fā)環(huán)境,旨在提高開(kāi)發(fā)效率和質(zhì)量。本文給大家分享我遇到的問(wèn)題及解決方法,感興趣的朋友跟隨小編一起看看吧2021-07-07React + Threejs + Swiper 實(shí)現(xiàn)全景圖效果的完整代碼
全景圖效果非常漂亮給人帶來(lái)極好的用戶體驗(yàn)效果,那么基于前端開(kāi)發(fā)如何實(shí)現(xiàn)這種效果呢,下面小編給大家?guī)?lái)了React + Threejs + Swiper 實(shí)現(xiàn)全景圖效果,感興趣的朋友一起看看吧2021-06-06React 組件中的state和setState()你知道多少
這篇文章主要為大家詳細(xì)介紹了React組件中的state和setState(),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-03-03react-native DatePicker日期選擇組件的實(shí)現(xiàn)代碼
本篇文章主要介紹了react-native DatePicker日期選擇組件的實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,有興趣的可以了解下2017-09-09react實(shí)現(xiàn)移動(dòng)端下拉菜單的示例代碼
這篇文章主要介紹了react實(shí)現(xiàn)移動(dòng)端下拉菜單的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01react-native組件中NavigatorIOS和ListView結(jié)合使用的方法
這篇文章主要給大家介紹了關(guān)于react-native組件中NavigatorIOS和ListView結(jié)合使用的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧。2017-09-09React 模塊聯(lián)邦多模塊項(xiàng)目實(shí)戰(zhàn)詳解
這篇文章主要介紹了React 模塊聯(lián)邦多模塊項(xiàng)目實(shí)戰(zhàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10