使用 JavaScript 创建鼠标“注视”脚本
不久前,我看到一个页面,上面有很多人物小图片。当鼠标在页面上移动时,图片里的人物仿佛在注视着鼠标指针。鼠标在屏幕上移动时,图片中的每张脸都能看向 8 个不同的方向,这引起了我的兴趣,于是我去研究它是如何实现的。
该页面结合固定元素尺寸和图像的背景定位,每次只显示图像的一部分。再结合少量 JavaScript 代码,页面无需加载大量图片,只需为每个团队成员准备一张图片,就能创建出互动式图像。这张图片很简单,是一个包含 9 张小图的正方形,每张小图对应鼠标所在的一个方向,还有一张位于中心的小图,当鼠标悬停在图像上时,人物会直视前方。
我围绕这段代码的思路思考了一段时间,所以想在这篇文章中整理几个实现方法的示例。在 Drupal开发中,有时也会用到类似这种创意的交互效果,为页面增添趣味性。
在这篇文章中,我们将探讨如何准备实现此功能所需的图像,然后编写 JavaScript 代码来改变图像,使其看起来像是在环顾页面。
二、准备工作
我们要做的第一件事是创建一个图像,用于模拟当鼠标在屏幕上移动时,人物的脸看向鼠标的效果。我在 GIMP 中创建了一个指南,其中包含一个 480 像素的正方形图像,并用等间距的网格线将图像分成 9 个不同的部分。这是在 GIMP 中添加了一个小机器人脸后的指南截图。
获取这个图像指南并根据你的需求进行修改。
这是我们将在本文示例中使用的图像。
只需右键点击图像即可下载,并在下面的示例中使用。感谢我公司为我创建了这张图像,真的很棒!
使用带有 data-image 属性的 div 标签将图像放置在页面上,该属性包含图像的路径。使用 div 是因为我们需要将块设置为特定的大小,并相应地设置背景位置。
<div id="facelook1" data-image="robot_face.png"></div>
我们本可以在这里使用图像标签,实际上我最初也是尝试使用图像标签,但这样存在一些问题。你可以设置图像元素的 src 属性,但这会影响背景效果,破坏整个视觉效果。也可以完全省略图像的 src 属性,但添加到元素中的任何替代文本会显示在背景上方。而一个简单的 div 则不会有任何不必要的副作用。
为了设置这个元素的大小和背景,我们需要使用一个小函数,该函数接受元素 ID 并添加所需的样式。
function prepareElement(elementId) {
let element = document.getElementById(elementId);
element.style.backgroundImage = 'url(' + element.dataset.image + ')';
element.style.backgroundPosition = '-160 -160';
element.style.width = '160px';
element.style.height = '160px';
return element;
}
这部分代码可以转化为一小段 CSS,但由于需要动态替换图像,意味着我们需要在页面加载时动态分配图像尺寸。如果需要,你可以添加额外的样式来改善块的初始外观和感觉,但这超出了本文的范围。
需要将 prepareElement 函数包裹在一个窗口加载函数中,以便在 DOM 准备好时触发该函数。我们创建一个包含团队成员脸部图像的元素列表,这样页面上的多张图片就都能跟随鼠标移动。在同一个回调函数中,我们还添加了一个事件监听器,用于监听鼠标移动并触发相应事件。
window.onload = function windowOnload() {
var elements = [
prepareElement("facelook1"),
];
document.addEventListener("mousemove", (event) => {
// 在此处添加注视逻辑函数
});
}
现在我们要做的就是填充事件监听器回调函数的内容。有几种方法可以实现模拟人物看着鼠标在屏幕上移动的效果,我们将介绍使用边界框方法(简单方法)和角度计算方法(不太简单的方法)。
三、简单方法
实现这一效果的简单方法是,确定鼠标当前所在的屏幕区域,然后按照预先计算的偏移量移动图像的背景。在 Drupal模块开发里,也会有类似根据条件进行偏移或定位的需求。
这个方法可以写成一大串 if 语句,但我发现将所有条件收集到一个数组中,然后遍历这个数组直到找到正确的解决方案会更好一些。数组的每个部分有两个元素,一个用于测试鼠标位置,另一个用于设置背景偏移量。以下是完整的代码。
document.addEventListener("mousemove", (event) => {
var currentx = event.pageX;
var currenty = event.pageY;
elements.forEach(element => {
var rect = element.getBoundingClientRect();
var tests = [
// 西北方向
[rect.left > currentx && rect.top > currenty, '0px 0px'],
// 正北方向
[rect.left + rect.width > currentx && rect.left < currentx && rect.top > currenty, '-' + rect.width + 'px 0px'],
// 东北方向
[rect.left + rect.width < currentx && rect.top > currenty, '-' + (rect.width * 2) + 'px 0px'],
// 正西方向
[rect.left > currentx && rect.top + rect.width > currenty && rect.top < currenty, '0px -' + rect.width + 'px'],
// 正上方(鼠标在图像上方)
[rect.left + rect.width > currentx && rect.left < currentx && rect.top + rect.width > currenty && rect.top < currenty, '-' + rect.width + 'px -' + rect.width + 'px'],
// 正东方向
[rect.left + rect.width < currentx && rect.top + rect.width > currenty && rect.top < currenty, '-' + (rect.width * 2) + 'px -' + rect.width + 'px'],
// 西南方向
[rect.left > currentx && rect.top + rect.width < currenty, '0px -' + (rect.width * 2) + 'px'],
// 正南方向
[rect.left + rect.width > currentx && rect.left < currentx && rect.top + rect.width < currenty, '-' + rect.width + 'px -' + (rect.width * 2) + 'px'],
// 东南方向
[rect.left + rect.width < currentx && rect.top + rect.width < currenty, '-' + (rect.width * 2) + 'px -' + (rect.width * 2) + 'px'],
];
tests.forEach(function (item, index, array) {
if (item[0]) {
element.style.backgroundPosition = item[1];
return;
}
});
});
});
我们能够实现这一效果,是因为事件具有坐标属性,并且使用了 getBoundingClientRect 函数。当 JavaScript 中的一个事件被触发时,你可以使用 pageX 和 pageY 属性提取事件的坐标。getBoundingClientRect 函数是 JavaScript 内置的一个函数,它返回与相关元素有关的多个不同属性。在这段代码中,我们结合使用 left、top 和 width 属性来计算图像的中心位置,以及鼠标指针相对于该中心的位置。
四、不太简单的方法
第二种方法同样利用了事件的 pageX 和 pageY 坐标以及 getBoundingClientRect() 函数,但在这里我们使用了一点数学知识来计算鼠标指针相对于相关图像的角度。在 Drupal11的开发中,也可能会遇到需要运用数学计算来实现特定交互的场景。
calculateAngle() 函数实现了这一计算,它接受鼠标的 x、y 坐标和图像的 x、y 坐标作为参数。
// 计算鼠标相对于图像的角度
function calculateAngle(mouseX, mouseY, positionX, positionY) {
// 将坐标偏移到中心
xD = mouseX - (positionX);
yD = mouseY - (positionY);
// 获取鼠标相对于元素中心位置的角度
return Math.atan2(yD, xD) * 180 / Math.PI;
}
这个函数的返回值是一个角度,我们可以使用 if 或 switch 语句将背景位置重新定位到正确的区域。当鼠标指针悬停在相关元素上时,我们在开头还有一个简单的条件判断。
document.addEventListener("mousemove", (event) => {
var currentx = event.pageX;
var currenty = event.pageY;
elements.forEach(element => {
var rect = element.getBoundingClientRect();
if (currentx >= rect.x && currentx <= rect.right && currenty >= rect.y && currenty <= rect.bottom) {
// 鼠标在元素上,直视前方
element.style.backgroundPosition = '50% 50%';
return;
}
var centerX = rect.x + (rect.width / 2);
var centerY = rect.y + (rect.height / 2);
angle = calculateAngle(currentx, currenty, centerX, centerY);
switch (true) {
case (angle <= 22.5 && angle > -22.5):
// 正东方向
element.style.backgroundPosition = 'right';
break;
case (angle <= 67.5 && angle > 22.5):
// 东南方向
element.style.backgroundPosition = 'bottom right';
break;
case (angle <= 112.5 && angle > 67.5):
// 正南方向
element.style.backgroundPosition = 'bottom';
break;
case (angle <= 157.5 && angle > 112.5):
// 西南方向
element.style.backgroundPosition = 'bottom left';
break;
case (angle <= -157.5 || angle > 157.5):
// 正西方向
element.style.backgroundPosition = 'left';
break;
case (angle <= -112.5 && angle > -157.5):
// 西北方向
element.style.backgroundPosition = 'top left';
break;
case (angle <= -67.5 && angle > -112.5):
// 正北方向
element.style.backgroundPosition = 'top';
break;
case (angle <= -22.5 && angle > -67.5):
// 东北方向
element.style.backgroundPosition = 'top right';
break;
}
});
});
这种方法效果非常好。实际上,我在这里详细介绍的两种方法都能实现相同的效果。此外,你可以在页面上添加多张图片,让它们都能独立地跟随鼠标指针移动。如果你在 Drupal升级后使用这些交互效果,也能为你的网站增添新的活力。
如果你想尝试这种效果,我创建了一个演示示例。
这里的所有代码,包括图片和 Gimp 模板,都可以免费获取。


