前端趣味交互:如何精准判断鼠标从哪个方向进入元素?

当我们希望一个元素在鼠标移入时,能像一扇门一样从正确的一侧滑开,或者让提示信息从鼠标来的方向自然浮现,这个看似"智能"的效果,其核心就是一个经典的几何计算:判断鼠标的进入方向

核心原理

想象一下,你正站在一个房间的正中央。有人从门口进来,你怎么判断他是从左边门还是右边门进来的?你会对比他出现的位置和你(中心点)的左右关系。我的思路是:

  1. 算出元素中心点坐标
  2. 根据中心坐标建立坐标象限
  3. 每个象限分为两个部分
  4. 依据鼠标进入的位置,算出鼠标进入时的坐标点
  5. 根据坐标点判断进入方向

第一版代码

html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>判断鼠标进入方向</title>
  </head>
  <style>
    * {
      margin: 0;
      padding: 0;
    }
    body {
      width: 100vw;
      height: 100vh;
      display: flex;
      align-items: center;
      justify-content: center;
    }
    .box {
      width: 600px;
      height: 600px;
      background-color: aquamarine;
    }
  </style>
  <body>
    <div class="box"></div>
  </body>
  <script>
    const box = document.querySelector(".box");

    box.addEventListener("mouseenter", (event) => {
      // 计算元素中心点坐标
      const rect = box.getBoundingClientRect();
      const centerX = rect.x + rect.width / 2;
      const centerY = rect.y + rect.height / 2;
      
      // 鼠标位置
      const clientX = event.clientX;
      const clientY = event.clientY;
      
      // 坐标
      const x = clientX - centerX;
      const y = centerY - clientY;

      // 第一象限
      if (x > 0 && y > 0) {
        if (y > x) {
          console.log("top");
        } else {
          console.log("right");
        }
      }
      // 第四象限
      if (x > 0 && y < 0) {
        if (x > -y) {
          console.log("right");
        } else {
          console.log("bottom");
        }
      }
      // 第三象限
      if (x < 0 && y < 0) {
        if (-x > -y) {
          console.log("left");
        } else {
          console.log("bottom");
        }
      }
      // 第二象限
      if (x < 0 && y > 0) {
        if (y > -x) {
          console.log("top");
        } else {
          console.log("left");
        }
      }
    });
  </script>
</html>

但是第一版代码分支判断过多,我们仔细观察后能发现,实际上核心判断就是"比较水平偏差和垂直偏差哪个更大"

第二版代码

javascript 复制代码
const box = document.querySelector(".box");

box.addEventListener("mouseenter", (event) => {
  // 获取中心点坐标
  const rect = box.getBoundingClientRect();
  const centerX = rect.x + rect.width / 2;
  const centerY = rect.y + rect.height / 2;

  const clientX = event.clientX;
  const clientY = event.clientY;

  // 优化后的核心逻辑
  const deltaX = event.clientX - centerX; // 水平偏差
  const deltaY = event.clientY - centerY; // 垂直偏差

  if (Math.abs(deltaX) > Math.abs(deltaY)) {
    // 水平方向主导
    console.log(deltaX > 0 ? "right" : "left");
  } else {
    // 垂直方向主导
    console.log(deltaY > 0 ? "bottom" : "top");
  }
});

在vue3项目使用示例

我们可以做一个鼠标移入时图片从不同方向进入的效果

xml 复制代码
<template>
  <div class="mouse-direction-container">
    <p>鼠标方向:{{ direction }}</p>

    <div :class="['box', direction]">
      <img class="top-img" src="@/assets/top.jpg" alt="" />
      <img class="right-img" src="@/assets/right.jpg" alt="" />
      <img class="bottom-img" src="@/assets/bottom.jpg" alt="" />
      <img class="left-img" src="@/assets/left.jpg" alt="" />
      <img class="center-img" src="@/assets/center.jpg" alt="" />
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from "vue";

const direction = ref<"top" | "bottom" | "left" | "right" | "">("");

onMounted(() => {
  const box = document.querySelector(".box")!;

  box.addEventListener("mouseenter", (e) => {
    const { clientX, clientY } = e as MouseEvent;
    const { left, top, width, height } = box.getBoundingClientRect();

    const centerX = left + width / 2;
    const centerY = top + height / 2;

    const deltaX = clientX - centerX; // 水平偏差
    const deltaY = clientY - centerY; // 垂直偏差
    if (Math.abs(deltaX) > Math.abs(deltaY)) {
      direction.value = deltaX > 0 ? "right" : "left";
    } else {
      direction.value = deltaY > 0 ? "bottom" : "top";
    }
  });
});
</script>

<style scoped lang="scss">
.mouse-direction-container {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 100px;
}

.box {
  width: 400px;
  height: 300px;
  background-color: red;
  position: relative;
  overflow: hidden;
  &.top {
    .top-img {
      transform: translateY(0);
    }
  }
  &.right {
    .right-img {
      transform: translateX(0);
    }
  }
  &.bottom {
    .bottom-img {
      transform: translateY(0);
    }
  }
  &.left {
    .left-img {
      transform: translateX(0);
    }
  }
  img {
    position: absolute;
    transition: all 0.3s linear;
    &.center-img {
      top: 0;
      left: 0;
      z-index: 1;
    }
    &.top-img {
      transform: translateY(-100%);
      z-index: 2;
    }
    &.right-img {
      transform: translateX(100%);
      z-index: 2;
    }
    &.bottom-img {
      transform: translateY(100%);
      z-index: 2;
    }
    &.left-img {
      transform: translateX(-100%);
      z-index: 2;
    }
  }
}
</style>
相关推荐
雨季66619 分钟前
Flutter 三端应用实战:OpenHarmony “专注时光盒”——在碎片洪流中守护心流的数字容器
开发语言·前端·安全·flutter·交互
tao35566737 分钟前
【用AI学前端】HTML-02-HTML 常用标签(基础)
前端·html
2601_9495328440 分钟前
Psello HTML Template: A Developer‘s Deep-Dive Review and Guide - Download Free
前端·windows·html·seo·wordpress·gpl
CappuccinoRose41 分钟前
CSS前端布局总指南
前端·css·学习·布局·flex布局·grid布局·float布局
穿过锁扣的风1 小时前
如何操作HTML网页
前端·javascript·html
San30.1 小时前
从零构建坚固的前端堡垒:TypeScript 与 React 实战深度指南
前端·react.js·typescript
yunhuibin1 小时前
VideoPipe环境搭建及编译ubuntu240403
前端·人工智能
CHANG_THE_WORLD2 小时前
PDF文档结构分析 一
前端·pdf
东东5162 小时前
果园预售系统的设计与实现spingboot+vue
前端·javascript·vue.js·spring boot·个人开发
rainbow68892 小时前
Python学生管理系统:JSON持久化实战
java·前端·python