「原生练手」🚀打造酷炫分屏动画

140 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情

本篇文章我们会实现下面这样一个酷炫的分屏动画,会随着鼠标的移动自动进行分屏,用到的知识点有:

  • 使用事件捕获机制完成事件代理
  • css过渡动画
  • css水平垂直居中
  • css显示图片

分屏动画.gif

页面结构

首先把整体页面框架搭建起来

<!DOCTYPE html>
<html lang="en">

<head>
  <title>Split Landing Page</title>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <link href="style.css" rel="stylesheet" />
</head>

<body>
  <div class="container">
    <div class="left">
      <h1>Vue</h1>
      <a href="https://staging-cn.vuejs.org/">Document</a>
    </div>
    <div class="right">
      <h1>React</h1>
      <a href="https://zh-hans.reactjs.org/">Document</a>
    </div>
  </div>

  <script src="index.js"></script>
</body>

</html>

思路分析

我们的思路就是监听鼠标的mouseentermouseout事件,enter的时候给container元素添加一个hover-left/hover-right的类名,然后通过css控制对应类名的样式,将宽度改变,并添加过渡动画,让jscss职责分离,从而实现这样一个动画的效果,接下来看看css怎么写

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
  height: 100vh;
  width: 100vw;
}

h1 {
  font-size: 64px;
}

a {
  text-decoration: none;
  color: white;
  border: 3px solid white;
  padding: 20px 60px;
  transition: background-color 1s ease;
}

.container {
  display: flex;
  height: 100%;
  width: 100%;
}

.left {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 10px;
  height: 100%;
  width: 50%;
  color: white;
  transition: width 1s ease;
  background: url("img/vue.png");
  background-repeat: no-repeat;
  background-size: cover;
}

.right {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 10px;
  height: 100%;
  width: 50%;
  color: white;
  transition: width 1s ease;
  background-image: url("img/react.jpg");
  background-repeat: no-repeat;
  background-size: cover;
}

.left a:hover {
  background-color: #42b883;
}

.right a:hover {
  background-color: #61dafb;
}

.container.hover-left .left,
.container.hover-right .right {
  width: 150%;
}

关键点有下面几个:

  1. 使用.container.hover-left .left选择器为选中左侧分屏时修改宽度
  2. 使用.container.hover-right .right选择器为选中右侧分屏时修改宽度
  3. .left.right本身添加transition过渡动画,在宽度修改的时候有一个过渡效果
  4. 通过flex布局将标题和跳转链接按钮垂直居中显示
  5. 通过gap给标题和跳转链接按钮添加一定的距离间隔
  6. 使用background的几个属性完成背景图片的显示,并且要让图片的大小正常显示,因此使用cover,否则可能会出现比例显示不正确的问题

有了csshover-lefthover-right类名的样式定义,我们就只需要在js中去控制这两个类名的增加和移除即可,在合适的时机合适的元素添加合适的类名

注意这三个合适,是整个动画的关键!!!

  • 合适的时机:在mouseentermouseout的时候要做一些事情
  • 合适的元素:鼠标进入哪个元素,就要给容器元素添加相应的类名,比如鼠标进入左边屏幕区域的元素,就需要给.container添加一个.hover-left的类名,右侧类似
  • 合适的类名:就是.hover-left.hover-right

但是在写js之前,我们还需要了解一下使用事件捕获实现事件代理

事件捕获实现事件代理

通常来说,如果要给多个元素添加事件监听器,可以逐个元素遍历添加,也可以利用事件冒泡的机制,直接在父元素中添加一个事件监听器即可,让父元素代理监听子元素的事件触发并作出响应

但是现在这个场景有点特殊,我们的场景是mouseenter,鼠标移入的时候,最先触发的总会是.container父元素,这就导致如果使用事件冒泡,我们没法让事件传递到子元素中,从而无法判断当前触发事件的是左边还是右边子元素

那么怎么办呢?addEventListener的第三个参数正是用于控制事件捕获/事件冒泡的,参数名为useCapture,默认值为false,顾名思义,默认就是以事件冒泡的机制触发事件流的,而我们现在需要的是事件捕获机制,让事件从父元素流向子元素,所以只需要给该参数传入true即可

这样一来,鼠标移入的时候,我们根据元素类名是left还是right,动态给父元素添加hover-left/hover-right即可

((doc) => {
  /** @type { HTMLDivElement } */
  const oContainer = doc.querySelector(".container");

  const init = () => {
    bindEvent();
  };

  const handleMouseEnter = (e) => {
    /** @type { HTMLDivElement } */
    const el = e.target;

    if (el.className === "left" || el.className === "right") {
      const className = `hover-${el.className}`;
      oContainer.classList.add(className);
    }
  };

  const handleMouseOut = (e) => {
    // 把 hover-left 和 hover-right 都移除
    oContainer.classList.remove("hover-left");
    oContainer.classList.remove("hover-right");
  };

  const bindEvent = () => {
    // 因为 mouseenter 事件最先触发的一定是容器元素
    // 所以只能利用事件捕获将事件传递到子元素
    // 而不能使用默认的事件冒泡机制
    // 利用事件捕获进行事件代理 给容器元素设置监听器
    // 通过事件捕获传递到 left 或者 right 元素获取到
    oContainer.addEventListener("mouseenter", handleMouseEnter, true);
    oContainer.addEventListener("mouseout", handleMouseOut, true);
  };

  init();
})(document);

当然,这里子元素比较少,就两个,所以直接单独给两个子元素添加事件监听器也没毛病,但我就是想通过这个小案例来展示一下与平常不同的事件代理实现哈哈