在现代电商网站中,商品图片的细节展示对于提升用户体验和促进购买决策至关重要。放大镜效果作为一种经典的商品图片展示方式,能够让用户清晰地查看商品的每一个细节。本文将详细介绍如何使用 HTML、CSS 和 JavaScript 实现一个功能完整的商品图片放大镜效果。效果演示
该放大镜功能主要包括以下几个交互体验:
左侧缩略图列表,支持点击切换主图
主图区域鼠标悬停时显示放大镜效果
右侧同步显示放大的局部图像
支持左右导航按钮切换图片
缩略图悬停时自动预览对应图片
用户可以通过点击缩略图或者使用导航按钮来浏览不同角度的商品图片,当鼠标移动到主图上时,会显示出一个放大镜区域,并在右侧展示对应的高清放大图像。
页面结构
整个页面采用了经典的两栏布局设计,左侧为缩略图区域,右侧为主图及放大预览区域。
缩略图区域
缩略图区域位于页面左侧,垂直排列多个小尺寸的商品图片缩略图。每个缩略图都带有边框高亮效果,当前选中的缩略图会有橙色边框标识。<div class="thumbnails" id="thumbnailsContainer"></div>
主图与放大镜区域
主图区域占据页面右侧大部分空间,包含实际展示的商品大图、放大镜遮罩层以及放大预览窗口。<div class="product-viewer"> <div class="main-image-container" id="mainContainer"> <img class="main-image" id="mainImage" src="" alt="Product Image"> <div class="magnifier-lens" id="magnifierLens"></div> <button class="nav-btn prev-btn" id="prevBtn">‹</button> <button class="nav-btn next-btn" id="nextBtn">›</button> </div> <div class="zoom-preview" id="zoomPreview"></div></div>
核心功能实现
渲染缩略图
renderThumbnails() 函数负责动态生成并显示商品图片的缩略图列表。function renderThumbnails() { thumbnailsContainer.innerHTML = ''; imageData.forEach(function(image, index) { var thumbnail = document.createElement('div'); thumbnail.className = 'thumbnail ' + (index === currentState.currentIndex ? 'active' : ''); thumbnail.dataset.index = index; thumbnail.innerHTML = '<img src="' + image.small + '" alt="Thumbnail ' + index + '">'; thumbnailsContainer.appendChild(thumbnail); });}
更新主图
updateMainImage() 函数负责更新主图显示区域和放大预览区域的图片内容。function updateMainImage() { var currentImage = imageData[currentState.currentIndex]; mainImage.src = currentImage.small; zoomPreview.style.backgroundImage = 'url(' + currentImage.large + ')';
mainImage.onload = function() { setBackgroundSize(); };
if (mainImage.complete) { setBackgroundSize(); }}
放大镜交互逻辑
放大镜的核心在于精确计算放大镜的位置和大小,并同步控制预览区域的背景位置。当鼠标进入主图容器时触发 showMagnifier() 显示放大镜和预览窗口;离开时通过 hideMagnifier() 隐藏它们。在鼠标移动过程中,moveMagnifier() 函数实时计算并更新放大镜位置及其在预览区的对应显示区域。
function moveMagnifier(e) { var containerRect = mainContainer.getBoundingClientRect(); var x = e.clientX - containerRect.left; var y = e.clientY - containerRect.top;
var lensWidth = Math.floor(mainContainer.offsetWidth / currentState.zoomFactor); var lensHeight = Math.floor(mainContainer.offsetHeight / currentState.zoomFactor);
var lensX = Math.round(x - lensWidth / 2); var lensY = Math.round(y - lensHeight / 2);
lensX = Math.max(0, Math.min(lensX, containerRect.width - lensWidth)); lensY = Math.max(0, Math.min(lensY, containerRect.height - lensHeight));
magnifierLens.style.left = lensX + 'px'; magnifierLens.style.top = lensY + 'px'; magnifierLens.style.width = lensWidth + 'px'; magnifierLens.style.height = lensHeight + 'px';
var percentX = x / containerRect.width; var percentY = y / containerRect.height;
var bgWidth = mainContainer.offsetWidth * currentState.zoomFactor; var bgHeight = mainContainer.offsetHeight * currentState.zoomFactor;
var previewWidth = zoomPreview.offsetWidth; var previewHeight = zoomPreview.offsetHeight;
var previewBgX = -(percentX * bgWidth - previewWidth / 2); var previewBgY = -(percentY * bgHeight - previewHeight / 2);
if (bgWidth <= previewWidth) { previewBgX = (previewWidth - bgWidth) / 2; } else { previewBgX = Math.max(previewWidth - bgWidth, Math.min(0, previewBgX)); }
if (bgHeight <= previewHeight) { previewBgY = (previewHeight - bgHeight) / 2; } else { previewBgY = Math.max(previewHeight - bgHeight, Math.min(0, previewBgY)); }
zoomPreview.style.backgroundPosition = previewBgX + 'px ' + previewBgY + 'px';}
完整代码
git地址:https://gitee.com/ironpro/hjdemo/blob/master/magnifier/index.html<!DOCTYPE html><html lang="zh-CN"><head> <meta charset="UTF-8"> <title>商品图片放大镜</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; }
body { background-color: #f5f5f5; padding: 30px; } .container { max-width: 1200px; margin: 0 auto; display: flex; gap: 30px; } .thumbnails { width: 68px; display: flex; flex-direction: column; gap: 10px; }
.thumbnail { width: 100%; height: 68px; border: 2px solid transparent; cursor: pointer; overflow: hidden; transition: border-color 0.3s; }
.thumbnail.active { border-color: #ff6600; }
.thumbnail img { width: 100%; height: 100%; object-fit: cover; }
.product-viewer { flex: 1; position: relative; display: flex; gap: 30px; }
.main-image-container { position: relative; width: 500px; height: 500px; background: white; overflow: hidden; }
.main-image { width: 100%; height: 100%; object-fit: contain; }
.magnifier-lens { position: absolute; box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); cursor: none; display: none; pointer-events: none; background-color: rgba(255, 255, 255, 0.3); }
.zoom-preview { width: 500px; height: 500px; border: 1px solid #ddd; background-color: white; background-repeat: no-repeat; display: none; overflow: hidden; }
.nav-btn { position: absolute; top: 50%; transform: translateY(-50%); background: rgba(0, 0, 0, 0.5); color: white; border: none; width: 40px; height: 40px; font-size: 18px; cursor: pointer; z-index: 10; }
.prev-btn { left: 10px; }
.next-btn { right: 10px; }
.nav-btn:hover { background: rgba(0, 0, 0, 0.8); } </style></head><body> <div class="container"> <div class="thumbnails" id="thumbnailsContainer"></div>
<div class="product-viewer"> <div class="main-image-container" id="mainContainer"> <img class="main-image" id="mainImage" src="" alt="Product Image"> <div class="magnifier-lens" id="magnifierLens"></div> <button class="nav-btn prev-btn" id="prevBtn">‹</button> <button class="nav-btn next-btn" id="nextBtn">›</button> </div> <div class="zoom-preview" id="zoomPreview"></div> </div> </div> <script> var imageData = [ { small: 'https://picsum.photos/seed/a/450/450', large: 'https://picsum.photos/seed/a/1125/1125' }, { small: 'https://picsum.photos/seed/b/450/450', large: 'https://picsum.photos/seed/b/1125/1125' }, { small: 'https://picsum.photos/seed/c/450/450', large: 'https://picsum.photos/seed/c/1125/1125' }, { small: 'https://picsum.photos/seed/d/450/450', large: 'https://picsum.photos/seed/d/1125/1125' }, { small: 'https://picsum.photos/seed/e/450/450', large: 'https://picsum.photos/seed/e/1125/1125' }, { small: 'https://picsum.photos/seed/f/450/450', large: 'https://picsum.photos/seed/f/1125/1125' } ]; var thumbnailsContainer = document.getElementById('thumbnailsContainer'); var mainContainer = document.getElementById('mainContainer'); var mainImage = document.getElementById('mainImage'); var magnifierLens = document.getElementById('magnifierLens'); var zoomPreview = document.getElementById('zoomPreview'); var prevBtn = document.getElementById('prevBtn'); var nextBtn = document.getElementById('nextBtn');
var currentState = { currentIndex: 0, zoomFactor: 2.5 }; thumbnailsContainer.addEventListener('mouseover', function(e) { var thumbnail = e.target.closest('.thumbnail'); if (thumbnail && !thumbnail.classList.contains('active')) { var index = parseInt(thumbnail.dataset.index); switchImage(index); } });
mainContainer.addEventListener('mouseenter', showMagnifier); mainContainer.addEventListener('mouseleave', hideMagnifier); mainContainer.addEventListener('mousemove', moveMagnifier);
prevBtn.addEventListener('click', previousImage); nextBtn.addEventListener('click', nextImage);
function render() { renderThumbnails(); updateMainImage(); }
function renderThumbnails() { thumbnailsContainer.innerHTML = ''; imageData.forEach(function(image, index) { var thumbnail = document.createElement('div'); thumbnail.className = 'thumbnail ' + (index === currentState.currentIndex ? 'active' : ''); thumbnail.dataset.index = index; thumbnail.innerHTML = '<img src="' + image.small + '" alt="Thumbnail ' + index + '">'; thumbnailsContainer.appendChild(thumbnail); }); }
function updateMainImage() { var currentImage = imageData[currentState.currentIndex]; mainImage.src = currentImage.small; zoomPreview.style.backgroundImage = 'url(' + currentImage.large + ')';
mainImage.onload = function() { setBackgroundSize(); };
if (mainImage.complete) { setBackgroundSize(); } }
function setBackgroundSize() { var containerWidth = mainContainer.offsetWidth; var containerHeight = mainContainer.offsetHeight;
var bgWidth = containerWidth * currentState.zoomFactor; var bgHeight = containerHeight * currentState.zoomFactor;
zoomPreview.style.backgroundSize = bgWidth + 'px ' + bgHeight + 'px'; }
function switchImage(index) { currentState.currentIndex = index; render(); }
function previousImage() { currentState.currentIndex = (currentState.currentIndex - 1 + imageData.length) % imageData.length; render(); }
function nextImage() { currentState.currentIndex = (currentState.currentIndex + 1) % imageData.length; render(); }
function showMagnifier() { magnifierLens.style.display = 'block'; zoomPreview.style.display = 'block'; }
function hideMagnifier() { magnifierLens.style.display = 'none'; zoomPreview.style.display = 'none'; }
function moveMagnifier(e) { var containerRect = mainContainer.getBoundingClientRect(); var x = e.clientX - containerRect.left; var y = e.clientY - containerRect.top;
var lensWidth = Math.floor(mainContainer.offsetWidth / currentState.zoomFactor); var lensHeight = Math.floor(mainContainer.offsetHeight / currentState.zoomFactor);
var lensX = Math.round(x - lensWidth / 2); var lensY = Math.round(y - lensHeight / 2);
lensX = Math.max(0, Math.min(lensX, containerRect.width - lensWidth)); lensY = Math.max(0, Math.min(lensY, containerRect.height - lensHeight));
magnifierLens.style.left = lensX + 'px'; magnifierLens.style.top = lensY + 'px'; magnifierLens.style.width = lensWidth + 'px'; magnifierLens.style.height = lensHeight + 'px';
var percentX = x / containerRect.width; var percentY = y / containerRect.height;
var bgWidth = mainContainer.offsetWidth * currentState.zoomFactor; var bgHeight = mainContainer.offsetHeight * currentState.zoomFactor;
var previewWidth = zoomPreview.offsetWidth; var previewHeight = zoomPreview.offsetHeight;
var previewBgX = -(percentX * bgWidth - previewWidth / 2); var previewBgY = -(percentY * bgHeight - previewHeight / 2);
if (bgWidth <= previewWidth) { previewBgX = (previewWidth - bgWidth) / 2; } else { previewBgX = Math.max(previewWidth - bgWidth, Math.min(0, previewBgX)); }
if (bgHeight <= previewHeight) { previewBgY = (previewHeight - bgHeight) / 2; } else { previewBgY = Math.max(previewHeight - bgHeight, Math.min(0, previewBgY)); }
zoomPreview.style.backgroundPosition = previewBgX + 'px ' + previewBgY + 'px'; } render(); </script></body></html>
阅读原文:原文链接
该文章在 2025/12/15 9:03:57 编辑过