温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

如何实现react版模拟亚马逊人机交互菜单

发布时间:2022-02-08 14:52:58 来源:亿速云 阅读:138 作者:小新 栏目:开发技术

小编给大家分享一下如何实现react版模拟亚马逊人机交互菜单,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!

    需求介绍

    本文都是在web端的需求

    参考亚马逊和京东商城的首页左侧菜单效果,实现一个react版本的组件,以供业务使用。

    我们先看下亚马逊和京东商城的效果:

    亚马逊商城

    如何实现react版模拟亚马逊人机交互菜单

    京东商城

    如何实现react版模拟亚马逊人机交互菜单

    从上面的效果得出我们的菜单效果需求点:

    • 当我们的鼠标悬浮在左侧菜单的时候,右侧会对应展示它对应的子菜单项,

    • 当我们的鼠标在左侧菜单上下移动时,左侧可以快速切换为对应的子菜单

    • 当我们的鼠标移动以一定的倾斜角度移动到右侧的时候,鼠标虽然会经过其它的左侧菜单,但是不会执行切换。

    到目前为止,我们就搞情况了我们的需求。接下来就要去实现我们的方案了。

    实现方案

    要实现我们的需求,复杂点主要是在如何实现上述的需求3。需求1和需求2 的基本切换效果我就不再说了,直接进入需求3

    实现需求3

    如果要实现这个需求,我们需要记录鼠标从左往右(从左侧菜单区域移动到右侧菜单区域)的移动轨迹,然后根据它的移动轨迹去判断它是否是在一个三角形的区域之内,如果在的话,就不让它切换菜单。

    我们先看一张图:

    • P1:鼠标的起始位置

    • P2:左侧菜单的固定点1,鼠标在左侧区域的最大位移点

    • P3:左侧菜单的固定点2,鼠标在左侧区域的最大位移点

    • M:鼠标在左侧菜单移动的结束位置

    如何实现react版模拟亚马逊人机交互菜单

    从上图我们可以得出:

    如果鼠标的起始点是在 P1 的话,当鼠标移动到右侧区域,鼠标可能经过的三角形区域就是 P1-P2-P3所在的三角形,M点是鼠标的结束位置。所以我们判断鼠标的运动轨迹是否在三角形中就可以了。

    部分逻辑代码

    const [active, setActive] = useState(null) // 选中的菜单
      const [showSub, setShowSub] = useState(false) // 是否显示子菜单
      let timeout = useRef(null) //  设置延迟定时器,防止鼠标移到tab内容经过菜单时的切换
      let mouseLocs = useRef([]) // 记录鼠标移动时的坐标数组
      let firstSlope = useRef(null) // 菜单栏的固定点1, 根据菜单栏和内容的位置而改变
      let secondSlope = useRef(null) // 菜单栏的固定点2, 根据菜单栏和内容的位置而改变
      const refNavigation = useRef(null)
      const refNav = useRef(null)
      const refSubnav = useRef(null)
    
    
      /**
       * 根据内容栏相对于菜单栏的位置,判断移动过程中的点是否在三角形内
       * @param {Object} p1 开始位置
       * @param {Object} p2 菜单栏固定点1
       * @param {Object} p3 菜单栏固定点2
       * @param {Object} m 结束位置
       * @return {*}
       */
      function proPosInTriangle(p1, p2, p3, m) {
        // 结束时鼠标坐标位置
        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,说明该点在直线右侧,其它以此类推
          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
        // 返回是否在三角形内的结果
        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 元素的像素高度, 高度包含该元素的垂直内边距和边框,且是一个整数
          left: location.x,
          leftAndWidth: location.x + element.offsetWidth,
        }
      }
      /**
       * 根据内容栏相对于菜单栏的位置, 返回菜单栏的固定点1,和固定点2,保存在this.firstSlope和this.secondSlope对象里
       * 即 左侧菜单栏的右上角和右下角的位置
       */
      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 {
            // 是否在指定三角形内
            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)
      }
    
      // 移出菜单所在区域
      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) {
          // 移除超过三项的数据
          mouseLocs.current.shift()
        }
      }
      // 鼠标移出的时候,清除延时器
      const onMouseout = () => {
        if (timeout.current) {
          clearTimeout(timeout.current)
        }
      }

    实现效果

    如何实现react版模拟亚马逊人机交互菜单

    看完了这篇文章,相信你对“如何实现react版模拟亚马逊人机交互菜单”有了一定的了解,如果想了解更多相关知识,欢迎关注亿速云行业资讯频道,感谢各位的阅读!

    向AI问一下细节

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

    AI