溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何實(shí)現(xiàn)react版模擬亞馬遜人機(jī)交互菜單

發(fā)布時(shí)間:2022-02-08 14:52:58 來(lái)源:億速云 閱讀:128 作者:小新 欄目:開(kāi)發(fā)技術(shù)

小編給大家分享一下如何實(shí)現(xiàn)react版模擬亞馬遜人機(jī)交互菜單,希望大家閱讀完這篇文章之后都有所收獲,下面讓我們一起去探討吧!

    需求介紹

    本文都是在web端的需求

    參考亞馬遜和京東商城的首頁(yè)左側(cè)菜單效果,實(shí)現(xiàn)一個(gè)react版本的組件,以供業(yè)務(wù)使用。

    我們先看下亞馬遜和京東商城的效果:

    亞馬遜商城

    如何實(shí)現(xiàn)react版模擬亞馬遜人機(jī)交互菜單

    京東商城

    如何實(shí)現(xiàn)react版模擬亞馬遜人機(jī)交互菜單

    從上面的效果得出我們的菜單效果需求點(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)方案

    要實(shí)現(xiàn)我們的需求,復(fù)雜點(diǎn)主要是在如何實(shí)現(xiàn)上述的需求3。需求1和需求2 的基本切換效果我就不再說(shuō)了,直接進(jìn)入需求3

    實(shí)現(xià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é)束位置

    如何實(shí)現(xiàn)react版模擬亞馬遜人機(jī)交互菜單

    從上圖我們可以得出:

    如果鼠標(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)
        }
      }

    實(shí)現(xiàn)效果

    如何實(shí)現(xiàn)react版模擬亞馬遜人機(jī)交互菜單

    看完了這篇文章,相信你對(duì)“如何實(shí)現(xiàn)react版模擬亞馬遜人機(jī)交互菜單”有了一定的了解,如果想了解更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

    向AI問(wèn)一下細(xì)節(jié)

    免責(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)容。

    AI