react版模擬亞馬遜人機交互菜單的實現(xiàn)
前言
前段時間接了一個需求,實現(xiàn)一個模仿亞馬遜和京東的菜單交互效果,這種效果被稱為模擬人機交互。在網(wǎng)上搜了一下,目前沒有見到有 react和Vue的版本,然后就自己參考了一下現(xiàn)有的方式,實現(xiàn)了一個react版本。
需求介紹
本文都是在web端的需求
參考亞馬遜和京東商城的首頁左側(cè)菜單效果,實現(xiàn)一個react版本的組件,以供業(yè)務(wù)使用。
我們先看下亞馬遜和京東商城的效果:
亞馬遜商城

京東商城

從上面的效果得出我們的菜單效果需求點:
- 當我們的鼠標懸浮在左側(cè)菜單的時候,右側(cè)會對應展示它對應的子菜單項,
- 當我們的鼠標在左側(cè)菜單上下移動時,左側(cè)可以快速切換為對應的子菜單
- 當我們的鼠標移動以一定的傾斜角度移動到右側(cè)的時候,鼠標雖然會經(jīng)過其它的左側(cè)菜單,但是不會執(zhí)行切換。
到目前為止,我們就搞情況了我們的需求。接下來就要去實現(xiàn)我們的方案了。
實現(xiàn)方案
要實現(xiàn)我們的需求,復雜點主要是在如何實現(xiàn)上述的需求3。需求1和需求2 的基本切換效果我就不再說了,直接進入需求3
實現(xiàn)需求3
如果要實現(xiàn)這個需求,我們需要記錄鼠標從左往右(從左側(cè)菜單區(qū)域移動到右側(cè)菜單區(qū)域)的移動軌跡,然后根據(jù)它的移動軌跡去判斷它是否是在一個三角形的區(qū)域之內(nèi),如果在的話,就不讓它切換菜單。
我們先看一張圖:
- P1:鼠標的起始位置
- P2:左側(cè)菜單的固定點1,鼠標在左側(cè)區(qū)域的最大位移點
- P3:左側(cè)菜單的固定點2,鼠標在左側(cè)區(qū)域的最大位移點
- M:鼠標在左側(cè)菜單移動的結(jié)束位置

從上圖我們可以得出:
如果鼠標的起始點是在 P1 的話,當鼠標移動到右側(cè)區(qū)域,鼠標可能經(jīng)過的三角形區(qū)域就是 P1-P2-P3所在的三角形,M點是鼠標的結(jié)束位置。所以我們判斷鼠標的運動軌跡是否在三角形中就可以了。
部分邏輯代碼
const [active, setActive] = useState(null) // 選中的菜單
const [showSub, setShowSub] = useState(false) // 是否顯示子菜單
let timeout = useRef(null) // 設(shè)置延遲定時器,防止鼠標移到tab內(nèi)容經(jīng)過菜單時的切換
let mouseLocs = useRef([]) // 記錄鼠標移動時的坐標數(shù)組
let firstSlope = useRef(null) // 菜單欄的固定點1, 根據(jù)菜單欄和內(nèi)容的位置而改變
let secondSlope = useRef(null) // 菜單欄的固定點2, 根據(jù)菜單欄和內(nèi)容的位置而改變
const refNavigation = useRef(null)
const refNav = useRef(null)
const refSubnav = useRef(null)
/**
* 根據(jù)內(nèi)容欄相對于菜單欄的位置,判斷移動過程中的點是否在三角形內(nèi)
* @param {Object} p1 開始位置
* @param {Object} p2 菜單欄固定點1
* @param {Object} p3 菜單欄固定點2
* @param {Object} m 結(jié)束位置
* @return {*}
*/
function proPosInTriangle(p1, p2, p3, m) {
// 結(jié)束時鼠標坐標位置
let x = m.x,
y = m.y,
// 開始鼠標坐標位置
x1 = p1.x,
y1 = p1.y,
// 菜單欄包裹層右上角坐標
x2 = p2.x,
y2 = p2.y,
// 右下角坐標
x3 = p3.x,
y3 = p3.y,
// (y2 - y1) / (x2 - x1)為兩坐標連成直線的斜率
// 因為直線的公式為y=kx+b;當斜率相同時,只要比較
// b1和b2的差值就可以知道該點是在
// (x1,y1),(x2,y2)的直線的哪個方向
// 當r1大于0,說明該點在直線右側(cè),其它以此類推
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
}
/**
* 獲取元素相對于瀏覽器左上角的坐標位置,為正值
* @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)邊距和邊框,且是一個整數(shù)
left: location.x,
leftAndWidth: location.x + element.offsetWidth,
}
}
/**
* 根據(jù)內(nèi)容欄相對于菜單欄的位置, 返回菜單欄的固定點1,和固定點2,保存在this.firstSlope和this.secondSlope對象里
* 即 左側(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) {}
// 是就啟動延遲顯示,
// 否則不延遲
if (diff) {
timeout.current = setTimeout(() => {
setActive(obj.key)
setShowSub(true)
}, 300)
} else {
setActive(obj.key)
setShowSub(true)
}
},
[mouseLocs, timeout]
)
const onMouseEnter = () => {
// 計算位置
if (refNav.current) {
ensureTriangleDots()
}
setShowSub(true)
}
// 移出菜單所在區(qū)域
const onMouseLeave = () => {
if (refSubnav.current) {
setActive(null)
setShowSub(false)
}
}
// 記錄鼠標在菜單欄中移動的最后三個坐標位置
const onMousemove = event => {
mouseLocs.current.push({
x: event.pageX,
y: event.pageY,
})
if (mouseLocs.current.length > 3) {
// 移除超過三項的數(shù)據(jù)
mouseLocs.current.shift()
}
}
// 鼠標移出的時候,清除延時器
const onMouseout = () => {
if (timeout.current) {
clearTimeout(timeout.current)
}
}實現(xiàn)效果

到此這篇關(guān)于react版模擬亞馬遜人機交互菜單的實現(xiàn)的文章就介紹到這了,更多相關(guān)react 交互菜單內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
react 跳轉(zhuǎn)后路由變了頁面沒刷新的解決方案
最近在學習React的過程中遇到了路由跳轉(zhuǎn)后頁面不刷新的問題,本文就詳細的介紹一下解決方法,需要的朋友們下面隨著小編來一起學習學習吧2021-06-06
next-redux-wrapper使用細節(jié)及源碼分析
這篇文章主要為大家介紹了next-redux-wrapper使用細節(jié)及源碼分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-02-02
React?state結(jié)構(gòu)設(shè)計原則示例詳解
這篇文章主要為大家介紹了React?state結(jié)構(gòu)設(shè)計原則示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-06-06
使用useImperativeHandle時父組件第一次沒拿到子組件的問題
這篇文章主要介紹了使用useImperativeHandle時父組件第一次沒拿到子組件的問題及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-08-08
使用webpack搭建react開發(fā)環(huán)境的方法
本篇文章主要介紹了使用webpack搭建react開發(fā)環(huán)境的方法,在這篇文章中我們開始利用我們之前所學搭建一個簡易的React開發(fā)環(huán)境,用以鞏固我們之前學習的Webpack知識。一起跟隨小編過來看看吧2018-05-05

