您好,登錄后才能下訂單哦!
小編給大家分享一下如何實(shí)現(xiàn)react版模擬亞馬遜人機(jī)交互菜單,希望大家閱讀完這篇文章之后都有所收獲,下面讓我們一起去探討吧!
本文都是在web端的需求
參考亞馬遜和京東商城的首頁(yè)左側(cè)菜單效果,實(shí)現(xiàn)一個(gè)react版本的組件,以供業(yè)務(wù)使用。
我們先看下亞馬遜和京東商城的效果:
亞馬遜商城
京東商城
從上面的效果得出我們的菜單效果需求點(diǎn):
當(dāng)我們的鼠標(biāo)懸浮在左側(cè)菜單的時(shí)候,右側(cè)會(huì)對(duì)應(yīng)展示它對(duì)應(yīng)的子菜單項(xiàng),
當(dāng)我們的鼠標(biāo)在左側(cè)菜單上下移動(dòng)時(shí),左側(cè)可以快速切換為對(duì)應(yīng)的子菜單
當(dāng)我們的鼠標(biāo)移動(dòng)以一定的傾斜角度移動(dòng)到右側(cè)的時(shí)候,鼠標(biāo)雖然會(huì)經(jīng)過(guò)其它的左側(cè)菜單,但是不會(huì)執(zhí)行切換。
到目前為止,我們就搞情況了我們的需求。接下來(lái)就要去實(shí)現(xiàn)我們的方案了。
要實(shí)現(xiàn)我們的需求,復(fù)雜點(diǎn)主要是在如何實(shí)現(xiàn)上述的需求3。需求1和需求2 的基本切換效果我就不再說(shuō)了,直接進(jìn)入需求3
如果要實(shí)現(xiàn)這個(gè)需求,我們需要記錄鼠標(biāo)從左往右(從左側(cè)菜單區(qū)域移動(dòng)到右側(cè)菜單區(qū)域)的移動(dòng)軌跡,然后根據(jù)它的移動(dòng)軌跡去判斷它是否是在一個(gè)三角形的區(qū)域之內(nèi),如果在的話,就不讓它切換菜單。
我們先看一張圖:
P1:鼠標(biāo)的起始位置
P2:左側(cè)菜單的固定點(diǎn)1,鼠標(biāo)在左側(cè)區(qū)域的最大位移點(diǎn)
P3:左側(cè)菜單的固定點(diǎn)2,鼠標(biāo)在左側(cè)區(qū)域的最大位移點(diǎn)
M:鼠標(biāo)在左側(cè)菜單移動(dòng)的結(jié)束位置
從上圖我們可以得出:
如果鼠標(biāo)的起始點(diǎn)是在 P1 的話,當(dāng)鼠標(biāo)移動(dòng)到右側(cè)區(qū)域,鼠標(biāo)可能經(jīng)過(guò)的三角形區(qū)域就是 P1-P2-P3所在的三角形,M點(diǎn)是鼠標(biāo)的結(jié)束位置。所以我們判斷鼠標(biāo)的運(yùn)動(dòng)軌跡是否在三角形中就可以了。
const [active, setActive] = useState(null) // 選中的菜單 const [showSub, setShowSub] = useState(false) // 是否顯示子菜單 let timeout = useRef(null) // 設(shè)置延遲定時(shí)器,防止鼠標(biāo)移到tab內(nèi)容經(jīng)過(guò)菜單時(shí)的切換 let mouseLocs = useRef([]) // 記錄鼠標(biāo)移動(dòng)時(shí)的坐標(biāo)數(shù)組 let firstSlope = useRef(null) // 菜單欄的固定點(diǎn)1, 根據(jù)菜單欄和內(nèi)容的位置而改變 let secondSlope = useRef(null) // 菜單欄的固定點(diǎn)2, 根據(jù)菜單欄和內(nèi)容的位置而改變 const refNavigation = useRef(null) const refNav = useRef(null) const refSubnav = useRef(null) /** * 根據(jù)內(nèi)容欄相對(duì)于菜單欄的位置,判斷移動(dòng)過(guò)程中的點(diǎn)是否在三角形內(nèi) * @param {Object} p1 開(kāi)始位置 * @param {Object} p2 菜單欄固定點(diǎn)1 * @param {Object} p3 菜單欄固定點(diǎn)2 * @param {Object} m 結(jié)束位置 * @return {*} */ function proPosInTriangle(p1, p2, p3, m) { // 結(jié)束時(shí)鼠標(biāo)坐標(biāo)位置 let x = m.x, y = m.y, // 開(kāi)始鼠標(biāo)坐標(biāo)位置 x1 = p1.x, y1 = p1.y, // 菜單欄包裹層右上角坐標(biāo) x2 = p2.x, y2 = p2.y, // 右下角坐標(biāo) x3 = p3.x, y3 = p3.y, // (y2 - y1) / (x2 - x1)為兩坐標(biāo)連成直線的斜率 // 因?yàn)橹本€的公式為y=kx+b;當(dāng)斜率相同時(shí),只要比較 // b1和b2的差值就可以知道該點(diǎn)是在 // (x1,y1),(x2,y2)的直線的哪個(gè)方向 // 當(dāng)r1大于0,說(shuō)明該點(diǎn)在直線右側(cè),其它以此類(lèi)推 r1 = y - y1 - ((y2 - y1) / (x2 - x1)) * (x - x1), r2 = y - y2 - ((y3 - y2) / (x3 - x2)) * (x - x2), r3 = y - y3 - ((y1 - y3) / (x1 - x3)) * (x - x3), compare compare = r1 * r2 * r3 < 0 && r1 > 0 // 返回是否在三角形內(nèi)的結(jié)果 return compare } /** * 獲取元素相對(duì)于瀏覽器左上角的坐標(biāo)位置,為正值 * @param element * @return {{x: Number, y: Number}} * @constructor */ function LocFromdoc(element) { const { x, y, width, height } = element.getBoundingClientRect() return { x: x, y: y, width, height, } } /** * 記錄元素的位置信息 * @param element * @return {{top: *, topAndHeight: number, left: *, leftAndWidth: number}} */ function getInfo(element) { const location = LocFromdoc(element) return { top: location.y, topAndHeight: location.y + element.offsetHeight, // offsetHeight 元素的像素高度, 高度包含該元素的垂直內(nèi)邊距和邊框,且是一個(gè)整數(shù) left: location.x, leftAndWidth: location.x + element.offsetWidth, } } /** * 根據(jù)內(nèi)容欄相對(duì)于菜單欄的位置, 返回菜單欄的固定點(diǎn)1,和固定點(diǎn)2,保存在this.firstSlope和this.secondSlope對(duì)象里 * 即 左側(cè)菜單欄的右上角和右下角的位置 */ function ensureTriangleDots() { // 獲取菜單欄的位置 const info = getInfo(refNav.current) const x1 = info.leftAndWidth const y1 = info.top const x2 = x1 const y2 = info.topAndHeight firstSlope.current = { x: x1, y: y1, } secondSlope.current = { x: x2, y: y2, } } const onMouseOver = useCallback( obj => { let diff try { // 是否在指定三角形內(nèi) diff = proPosInTriangle( mouseLocs.current[0], firstSlope.current, secondSlope.current, mouseLocs.current[2] ) } catch (ex) {} // 是就啟動(dòng)延遲顯示, // 否則不延遲 if (diff) { timeout.current = setTimeout(() => { setActive(obj.key) setShowSub(true) }, 300) } else { setActive(obj.key) setShowSub(true) } }, [mouseLocs, timeout] ) const onMouseEnter = () => { // 計(jì)算位置 if (refNav.current) { ensureTriangleDots() } setShowSub(true) } // 移出菜單所在區(qū)域 const onMouseLeave = () => { if (refSubnav.current) { setActive(null) setShowSub(false) } } // 記錄鼠標(biāo)在菜單欄中移動(dòng)的最后三個(gè)坐標(biāo)位置 const onMousemove = event => { mouseLocs.current.push({ x: event.pageX, y: event.pageY, }) if (mouseLocs.current.length > 3) { // 移除超過(guò)三項(xiàng)的數(shù)據(jù) mouseLocs.current.shift() } } // 鼠標(biāo)移出的時(shí)候,清除延時(shí)器 const onMouseout = () => { if (timeout.current) { clearTimeout(timeout.current) } }
看完了這篇文章,相信你對(duì)“如何實(shí)現(xiàn)react版模擬亞馬遜人機(jī)交互菜單”有了一定的了解,如果想了解更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。