图片局部放大是前端开发中常见的交互效果,用户将鼠标悬停在图片特定区域时,旁边会同步展示该区域的放大内容,常用于电商商品详情、图片预览等场景。html5结合canvas绘图能力和鼠标事件监听,可以高效实现这一效果。

实现原理说明
整个效果的核心逻辑分为三个部分:首先是监听鼠标在原图上的移动事件,获取鼠标当前的坐标位置;然后根据坐标计算需要放大的区域范围,以及放大后内容在canvas上的绘制位置和尺寸;最后将原图对应区域的内容绘制到canvas中,完成放大展示。
基础页面结构搭建
我们需要准备原图容器、放大镜遮罩层、放大结果展示的canvas三个核心元素,基础结构代码如下:
<div class="container">
<div class="origin-box">
<img src="test.jpg" class="origin-img" id="originImg" />
<div class="mask" id="mask"></div>
</div>
<canvas id="zoomCanvas" width="400" height="400"></canvas>
</div>
对应的基础样式代码如下,用于调整元素布局和初始状态:
.container {
display: flex;
gap: 20px;
margin: 50px;
}
.origin-box {
position: relative;
width: 400px;
height: 400px;
}
.origin-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.mask {
position: absolute;
width: 100px;
height: 100px;
background: rgba(255, 255, 255, 0.5);
border: 1px solid #ccc;
display: none;
cursor: move;
pointer-events: none;
}
#zoomCanvas {
border: 1px solid #ccc;
}
核心交互逻辑实现
1. 获取元素并初始化变量
首先获取页面中的核心元素,同时定义需要用到的变量,包括原图尺寸、放大倍率、遮罩层尺寸等:
const originImg = document.getElementById('originImg');
const mask = document.getElementById('mask');
const zoomCanvas = document.getElementById('zoomCanvas');
const ctx = zoomCanvas.getContext('2d');
// 放大倍率
const scale = 2;
// 遮罩层尺寸
const maskWidth = 100;
const maskHeight = 100;
// 原图实际尺寸,需要等图片加载完成后获取
let imgWidth = 0;
let imgHeight = 0;
// 等待原图加载完成
originImg.onload = function() {
imgWidth = originImg.offsetWidth;
imgHeight = originImg.offsetHeight;
};
2. 监听鼠标移动事件
给原图容器绑定鼠标移动事件,计算鼠标位置,同时控制遮罩层的显示和位置更新:
const originBox = document.querySelector('.origin-box');
originBox.addEventListener('mousemove', function(e) {
// 显示遮罩层
mask.style.display = 'block';
// 获取鼠标相对于原图容器的坐标
const boxRect = originBox.getBoundingClientRect();
let mouseX = e.clientX - boxRect.left;
let mouseY = e.clientY - boxRect.top;
// 限制遮罩层不超出原图范围
let maskLeft = mouseX - maskWidth / 2;
let maskTop = mouseY - maskHeight / 2;
// 左边界判断
if (maskLeft < 0) {
maskLeft = 0;
}
// 右边界判断
if (maskLeft > imgWidth - maskWidth) {
maskLeft = imgWidth - maskWidth;
}
// 上边界判断
if (maskTop < 0) {
maskTop = 0;
}
// 下边界判断
if (maskTop > imgHeight - maskHeight) {
maskTop = imgHeight - maskHeight;
}
// 更新遮罩层位置
mask.style.left = maskLeft + 'px';
mask.style.top = maskTop + 'px';
// 计算原图中需要放大的区域
const sx = maskLeft;
const sy = maskTop;
const sWidth = maskWidth;
const sHeight = maskHeight;
// 计算canvas上绘制的区域,放大倍率为scale
const dx = 0;
const dy = 0;
const dWidth = maskWidth * scale;
const dHeight = maskHeight * scale;
// 清空canvas之前的内容
ctx.clearRect(0, 0, zoomCanvas.width, zoomCanvas.height);
// 将原图对应区域绘制到canvas中
ctx.drawImage(originImg, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
});
// 鼠标移出原图容器时隐藏遮罩层,清空canvas
originBox.addEventListener('mouseleave', function() {
mask.style.display = 'none';
ctx.clearRect(0, 0, zoomCanvas.width, zoomCanvas.height);
});
效果优化建议
上述代码已经实现了基础的图片局部放大效果,还可以根据实际需求做进一步优化:
- 可以动态调整放大倍率,比如提供倍率选择按钮,让用户自行选择放大比例
- 可以给遮罩层添加更明显的样式,提升用户交互感知
- 如果原图加载较慢,可以添加加载占位效果,避免获取到错误的图片尺寸
- 可以适配移动端触摸事件,让移动端用户也能使用局部放大功能
完整可运行示例
将以下代码保存为html文件,替换图片路径后即可直接运行查看效果:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>html5图片局部放大</title>
<style>
.container {
display: flex;
gap: 20px;
margin: 50px;
}
.origin-box {
position: relative;
width: 400px;
height: 400px;
}
.origin-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.mask {
position: absolute;
width: 100px;
height: 100px;
background: rgba(255, 255, 255, 0.5);
border: 1px solid #ccc;
display: none;
cursor: move;
pointer-events: none;
}
#zoomCanvas {
border: 1px solid #ccc;
}
</style>
</head>
<body>
<div class="container">
<div class="origin-box">
<img src="https://picsum.photos/800/800?random=2" class="origin-img" id="originImg" />
<div class="mask" id="mask"></div>
</div>
<canvas id="zoomCanvas" width="400" height="400"></canvas>
</div>
<script>
const originImg = document.getElementById('originImg');
const mask = document.getElementById('mask');
const zoomCanvas = document.getElementById('zoomCanvas');
const ctx = zoomCanvas.getContext('2d');
const originBox = document.querySelector('.origin-box');
const scale = 2;
const maskWidth = 100;
const maskHeight = 100;
let imgWidth = 0;
let imgHeight = 0;
originImg.onload = function() {
imgWidth = originImg.offsetWidth;
imgHeight = originImg.offsetHeight;
};
originBox.addEventListener('mousemove', function(e) {
mask.style.display = 'block';
const boxRect = originBox.getBoundingClientRect();
let mouseX = e.clientX - boxRect.left;
let mouseY = e.clientY - boxRect.top;
let maskLeft = mouseX - maskWidth / 2;
let maskTop = mouseY - maskHeight / 2;
if (maskLeft < 0) {
maskLeft = 0;
}
if (maskLeft > imgWidth - maskWidth) {
maskLeft = imgWidth - maskWidth;
}
if (maskTop < 0) {
maskTop = 0;
}
if (maskTop > imgHeight - maskHeight) {
maskTop = imgHeight - maskHeight;
}
mask.style.left = maskLeft + 'px';
mask.style.top = maskTop + 'px';
const sx = maskLeft;
const sy = maskTop;
const sWidth = maskWidth;
const sHeight = maskHeight;
const dx = 0;
const dy = 0;
const dWidth = maskWidth * scale;
const dHeight = maskHeight * scale;
ctx.clearRect(0, 0, zoomCanvas.width, zoomCanvas.height);
ctx.drawImage(originImg, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
});
originBox.addEventListener('mouseleave', function() {
mask.style.display = 'none';
ctx.clearRect(0, 0, zoomCanvas.width, zoomCanvas.height);
});
</script>
</body>
</html>