使用透视和变换在鼠标悬停时动画化容器

Avatar of Mihai Ionescu
Mihai Ionescu

DigitalOcean 提供适合您旅程每个阶段的云产品。从 200 美元的免费额度 开始!

我一直在开发一个网站,其中会向用户显示大型图片。我决定尝试制作一些更具互动性和趣味性的东西,而不是为这些大型图片创建典型的灯箱效果(带有黑色叠加层的放大动画)。我最终编写了一个图像容器,它会在用户将鼠标光标悬停在其上时倾斜。

这是最终版本

查看 CodePen 上 Mihai (@MihaiIonescu) 的 MrLopq 示例。

此效果通过 CSS 和 JavaScript 实现。我想制作一个简短的教程来解释每个部分的工作原理,以便您可以轻松地复制或扩展它。

我建议您在开始之前阅读有关 透视变换 的年鉴条目。我们将在文章中引用这些属性,熟悉它们是个好主意。

让我们开始吧。

设置

首先,我们需要一个包含另一个内部元素的容器。容器将有助于透视。

<div id="container">
  <div id="inner"></div>
</div>

为了演示目的,让我们将卡片精确地居中在屏幕中间

body {
  /* Full screen width and height */
  width: 100%;
  min-height: 100vh;
    
  /* Centers the container in the middle of the screen */
  display: flex;
  justify-content: center;
  align-items: center;
    
  margin: 0;
  background-color: rgb(220, 220, 220);
}

#container {
  /* This will come into play later */
  perspective: 40px;
}

#inner {
  width: 20em;
  height: 18em;
  background-color: white;
}

这给了我们一张白色的卡片,它直接位于浅灰色背景的中心。请注意,我们将 #container 的透视设置为 40px,此时它没有任何作用,因为我们还没有创建任何变换。这将在后面的 JavaScript 中处理。

查看 CodePen 上 Mihai (@MihaiIonescu) 的 3D 图像容器 - 第 0 部分 示例。

让我们开始编写脚本

以下是我们要执行的操作的概要

var container = document.getElementById('container');
var inner = document.getElementById('inner');
        
var onMouseEnterHandler = function(event) {
  update(event);
};
var onMouseLeaveHandler = function() {
  inner.style = "";
};
var onMouseMoveHandler = function(event) {
  if (isTimeToUpdate()) {
    update(event);
  }
};

container.onmouseenter = onMouseEnterHandler;
container.onmouseleave = onMouseLeaveHandler;
container.onmousemove = onMouseMoveHandler;

以下是所有这些内容的用途(或将要执行的操作)

  • 处理程序函数:这些函数处理发生的事件。我们希望确定当光标进入、在容器上移动以及离开容器时会发生什么,因此每个事件都具有处理程序。
  • 更新函数:我们尚未编写此代码,但它的目标是更新 #inner div 的 3D 旋转。
  • 更新时间函数:这是我们尚未编写的另一个函数,它将在需要更新时返回 true。这是一种减少对 update() 函数的调用次数并提高脚本性能的方法。
  • 事件:这是描述发生的事件的 JavaScript 对象。

上面的代码将

  • 更新内部 div 的 3D 旋转,只要鼠标进入容器即可。
  • 在适当的时间更新内部 div 的 3D 旋转,只要鼠标在容器上移动即可。
  • 重置内部 div 的样式,只要鼠标离开容器即可。

是更新的时候了吗?

让我们添加一个函数,它决定何时更新 #inner div 的 3D 旋转。

var counter = 0;
var updateRate = 10;
var isTimeToUpdate = function() {
  return counter++ % updateRate === 0;
};

counter 达到 updateRate 时,将进行更新。

此时,您可以尝试用 console.log() 替换 update 函数,并使用 updateRate 尝试一下它的工作原理。

鼠标

接下来是鼠标对象。这个对象比其他部分复杂一些。尽管如此,它并不难理解,但代码看起来可能很吓人,尤其是对于 JavaScript 新手来说。

// Init
var container = document.getElementById('container');
var inner = document.getElementById('inner');
// Mouse 
var mouse = {
  _x: 0,
  _y: 0,
  x: 0,
  y: 0,
  updatePosition: function(event) {
    var e = event || window.event;
    this.x = e.clientX - this._x;
    this.y = (e.clientY - this._y) * -1;
  },
  setOrigin: function(e) {
    this._x = e.offsetLeft + Math.floor(e.offsetWidth/2);
    this._y = e.offsetTop + Math.floor(e.offsetHeight/2);
  },
  show: function() { return '(' + this.x + ', ' + this.y + ')'; }
}
// Track the mouse position relative to the center of the container.
mouse.setOrigin(container);

我们再一起逐步分析。

  • show():显示鼠标的当前位置(如果您想在浏览器的控制台中进行一些调试)。
  • setOrigin(e):将鼠标对象的坐标 (0,0) 设置在元素(e)的中心。
  • updatePosition():相对于 (0,0) 更新鼠标对象的当前位置。

最后一行代码 mouse.setOrigin(container) 将鼠标对象的坐标 (0,0) 固定到容器的中心。以下示例说明了这一点。

查看 CodePen 上 Mihai (@MihaiIonescu) 的 3D 图像容器 - 第 1 部分 示例。

所有这些背后的想法是,当您将鼠标移离容器中心时,向 #inner div 添加更多旋转。

根据鼠标位置更新样式

这是我们的更新函数

var update = function(event) {
  mouse.updatePosition(event);
  updateTransformStyle(
    (mouse.y / inner.offsetHeight/2).toFixed(2),
    (mouse.x / inner.offsetWidth/2).toFixed(2)
  );
};

var updateTransformStyle = function(x, y) {
  var style = "rotateX(" + x + "deg) rotateY(" + y + "deg)";
  inner.style.transform = style;
  inner.style.webkitTransform = style;
  inner.style.mozTransform = style;
  inner.style.msTransform = style;
  inner.style.oTransform = style;
};
  • update():更新鼠标位置并更新 #inner div 的样式。
  • updateTransformStyle():更新每个供应商前缀的样式。

我们完成了吗?

我们最好进行一些测试!看起来,当鼠标光标进入和退出卡片时,我们的透视发生了变化,但它不像预期的那样平滑

查看 CodePen 上 Mihai (@MihaiIonescu) 的 3D 图像容器 - 第 2 部分 示例。

哦,对了!我们告诉它每次 counter 达到 updateRate 时就更新 #inner div 的旋转。这会导致更新之间出现笨拙的过渡。

我们如何解决这个问题?CSS 过渡。

添加过渡

#inner {
  transition: transform 0.5s;
}

这些是任意数字。您可以使用透视和变换值来使效果更加或更不明显,具体取决于您的需要。

查看 CodePen 上 Mihai (@MihaiIonescu) 的 3D 图像容器 - 第 3 部分 示例。

请注意,调整页面大小会导致一些问题,因为容器在页面中的位置会发生变化。解决方法是在调整页面大小后,在 container 中重新居中 mouse 对象。

总结

我们完成了!现在,我们有一个容器,可以使元素更具互动性。本文开头处的演示在容器内使用了图像,但它可以用于图像以外的其他事物,包括表单、模态框,以及您放入容器中的几乎任何其他内容。快去尝试一下吧!