我一直在开发一个网站,其中会向用户显示大型图片。我决定尝试制作一些更具互动性和趣味性的东西,而不是为这些大型图片创建典型的灯箱效果(带有黑色叠加层的放大动画)。我最终编写了一个图像容器,它会在用户将鼠标光标悬停在其上时倾斜。
这是最终版本
查看 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
对象。
总结
我们完成了!现在,我们有一个容器,可以使元素更具互动性。本文开头处的演示在容器内使用了图像,但它可以用于图像以外的其他事物,包括表单、模态框,以及您放入容器中的几乎任何其他内容。快去尝试一下吧!
CSS 版本 :)
我觉得这非常聪明,但是……您使用 100 个空锚点来产生这种效果。
做得好,但不能在生产环境中使用。
将鼠标事件计算与样式更新分离会提高性能吗?
理论上可以,但我将 refreshRate 降至 1,测试并比较后,并没有发现任何区别……
原始的 refreshRate 降至 1:https://codepen.io/asiankingofwhales/pen/GxWOBL?editors=1010
将鼠标计算与样式更新分离:https://codepen.io/asiankingofwhales/pen/VXprjX?editors=0010
嗨魏丽粉丝!
我认为如果你注释掉这些 CSS 代码,你会更好地理解
isTimeToUpdate
方法。使用 1 或 0 的
updateRate
,你的 内部 div 会在你的鼠标每次移动(在每个像素处)时更新!isTimeToUpdate
方法背后的想法是减少对update
方法的调用次数。通过这样做,我们也减少了客户端计算机执行的计算次数。然而,如果单独保留,这会导致 更新之间的笨拙过渡。这就是我们应用 CSS 过渡 的原因!我们现在在每次更新之间都有一个 美观 且 流畅 的过渡。
尝试将你的
updateRate
设置得足够高并注释掉这些 CSS 代码。你会更清楚地看到你实际上为你的 内部 div 计算新 3D 旋转的频率。希望这有帮助!:)
不错的文章。你考虑过 https://micku7zu.github.io/vanilla-tilt.js/ 吗?
嘿尼科!
这篇文章的主要目的是提供一个酷炫的 CSS 技巧的示例并解释如何实现它。:)
顺便说一句,你发现了很棒的东西!