JavaScript实现鼠标移入移出动画效果

作者:流萤时间:2026-10-31 03:26:57浏览:327

本文介绍如何使用JavaScript结合CSS3 transition实现类似拉勾网的鼠标悬停动画效果。重点解析如何通过MouseEvent获取坐标,利用正切函数判断鼠标移入移除方向,并封装通用的动画执行函数,确保动画流畅且逻辑清晰。

首先展示最终实现的视觉效果(注:此处为自行录制的GIF演示,制作工具为licecap,画质可能不够精致,敬请谅解)。

实现思路与HTML结构 

实现思路与HTML结构

接下来梳理实现该效果的核心思路。

构建基础的HTML结构是实现交互的前提。


  <ul>
    <li>
      <div class="bg">
        <p>JS</p>
      </div>
    </li>
    .....
  </ul>

在HTML结构中,<li>元素被设定为触发鼠标移入(mouseenter)和鼠标移出(mouseleave)事件的对象。

<div>元素则作为执行具体动画效果的载体。

CSS定位与样式设置

样式方面,CSS的设置至关重要。

<div>元素采用absolute定位方式,通过动态修改top和left属性来改变其在页面中的位置。

由于动画过程中div的top和left值可能会超出<li>元素的边界,因此必须为<li>设置overflow:hidden;属性,以隐藏溢出内容。

JavaScript动画与方向判断逻辑

逻辑控制部分,主要依赖JavaScript代码实现。

第一步,利用JavaScript动态控制CSS3的transition属性,从而实现平滑的动画过渡效果。

第二步,核心难点在于如何准确判断鼠标移入或移除的具体方向。

为了准确判断方向,首先需要掌握鼠标坐标的相关基础知识。

这涉及到MouseEvent对象中提供的各种坐标属性。

下面详细介绍MouseEvent对象中几种关键坐标属性的含义:

(clientX, clientY): 表示以浏览器可视区域为参考系的坐标位置。

(pageX, pageY): 表示以整个页面文档为参考系的坐标位置,包含了滚动条卷出的区域。

(screenX, screenY):表示以用户电脑屏幕物理尺寸为参考系的坐标位置。

此外,还需要掌握如何获取目标元素内部的相对坐标。


  function pointTo(element, e) {
    var elementBox = element.getBoundingClientRect();
    return {
      x: e.clientX - elementBox.left,
      y: e.clientY - elementBox.top
    };
  }

计算元素左上角在页面中的绝对坐标是基础步骤之一。


  function startPoint(element){
    var x = 0,y = 0;
    while(element != null) {
      x += element.offsetLeft;
      y += element.offsetTop;
      element = element.offsetParent;
    }
    return {
      x: x,
      y: y
    }
  }

获取元素的宽度和高度时,需注意应使用offsetWidth和offsetHeight,新手常误用width和height属性,导致计算偏差。


  offsetHeight与offsetWidth

为了代码复用,可以对CSS3 transition动画进行简单的封装。


/* options参数: obj: 运动的对象 speed: 运动的持续时间(可选) changeStyle: 改变的属性,这里可能多个,所以采用函数的方式(可选) callback: 回调函数(可选) */
  function animation(options){
    if(!options.obj) {
      return false;
    }
    //设置默认持续时间
    options.speed = options.speed || '.5s';
    options.obj.style.transition = "all " + options.speed + " ease-in-out";

    options.changeStyle.call(options.obj);

    var flag = false;
    options.obj.addEventListener('transitionend',function(){
      //这里主要由于transitionend在每个属性的动画执行完多会走一遍,所以我们要让它只执行一次。
      if(!flag) {

        options.callback && options.callback();
      }
    },false);
  }

明确了坐标获取后,接下来重点讨论如何确定运动方向。

这里需要运用数学中关于正切函数的概念,作者手绘了一张示意图辅助理解(图示可能较为简陋,请见谅)。

实现思路与HTML结构 

通过计算,最终得出元素的运动方向。


  function getDirection(element,startPoint,pagePoint){
    var halfWidth = element.offsetWidth / 2,halfHeight = element.offsetHeight / 2;
    //得到中心点
    var center = {
      x: startPoint.x + halfWidth,
      y: startPoint.y + halfHeight
    }
    //得到鼠标偏离中心点的距离
    var disX = pagePoint.x - center.x;
    var disY = pagePoint.y - center.y;
    if(disY < 0 && Math.abs(disY / disX) >= 1) {
      //上方
      return 1;
    }
    else if(disY > 0 && Math.abs(disY / disX) >= 1) {
      //下
      return 2;
    }
    else if(disX < 0 && Math.abs(disY / disX) < 1) {
      //左
      return 3;
    }
    else {
      //右
      return 4;
    }
  }

以下是启动事件监听的具体代码,其中包含详细注释说明。


/* options中的参数: 触发事件的载体: targetElement 执行动画的载体: animationElement */
  function HoverAction(options) {
    if(!options.targetElement || !options.animationElement) {
      return false;
    }
    this.targetElement = options.targetElement;
    this.animationElement = options.animationElement;
    this.timeId = null;
    this.speed = "0.3s";
  }
  HoverAction.prototype.addEvent = function() {
    //保存this的指向
    var _this = this;
    _this.targetElement.addEventListener('mouseenter',function(e){
      //得到鼠标的坐标
      var point = {
        x: e.pageX,
        y: e.pageY
      }
      console.log(point);
      //获得方向
      var dir = getDirection(_this.targetElement,startPoint(_this.targetElement),point);
      clearTimeout(_this.timeId);
      //取消过渡动画(防止重置动画载体位置时触发过渡效果)
      _this.animationElement.style.transition = "";
      //得到运动的方向,要确定动画载体的开始位置
      switch(dir){
        case 1:
          _this.animationElement.style.top = "-100%";
          _this.animationElement.style.left = "0";
          break;
        case 2:
          _this.animationElement.style.top = "100%";
          _this.animationElement.style.left = "0";
          break;
        case 3:
          _this.animationElement.style.top = "0";
          _this.animationElement.style.left = "-100%";
          break;
        case 4:
          _this.animationElement.style.top = "0";
          _this.animationElement.style.left = "100%";
          break;
      }
      //异步执行
      _this.timeId = setTimeout(function(){
        animation({
          obj: _this.animationElement,
          speed: _this.speed,
          changeStyle: function(){
            this.style.top = "0";
            this.style.left = "0";
          }
        });
      },20);
    },false);
    _this.targetElement.addEventListener('mouseleave',function(e){
      var left,top;
      var point = {
        x: e.pageX,
        y: e.pageY
      }
      clearTimeout(_this.timeId);
      _this.animationElement.style.transition = "";
      var dir = getDirection(_this.targetElement,startPoint(_this.targetElement),point);
      switch(dir) {
        case 1:
          top = '-100%';
          left = '0';
          break;
        case 2:
          top = '100%';
          left = "0";
          break;
        case 3:
          left = "-100%";
          top = "0";
          break;
        case 4:
          left = "100%";
          top = "0";
          break;
      }
      _this.timeId = setTimeout(function(){
        animation({
          obj: _this.animationElement,
          speed: _this.speed,
          changeStyle: function(){
            this.style.top = top;
            this.style.left = left;
          }
        });
      },20);
    },false);

  }

总结

最后对整体实现过程进行总结。

以上即为本文介绍的全部内容,希望这些技术细节对大家的JavaScript学习或实际工作有所启发。若有任何疑问,欢迎在评论区留言交流。

作者最新文章