如何借助共享元素实现屏幕间导航与图片平滑缩放过渡?
实现缩略图平滑放大弹窗及返回效果
核心思路
通过CSS过渡(transition)结合JavaScript动态计算元素位置/尺寸,让弹窗内的图片从缩略图的位置、大小平滑过渡到全屏状态,关闭时反向过渡回原位置。同时通过transform: scale实现缩放功能。
1. HTML结构
需要列表缩略图容器和全屏弹窗容器,弹窗内包含原图、缩放按钮、关闭按钮:
<!-- 列表缩略图 --> <div class="thumbnail-list"> <img class="thumbnail" src="thumb-1.jpg" data-original="original-1.jpg" alt="示例图片" > <!-- 更多缩略图... --> </div> <!-- 全屏弹窗 --> <div class="image-modal" id="imageModal"> <button class="modal-close" id="modalClose">×</button> <div class="modal-controls"> <button class="zoom-btn" id="zoomIn">+</button> <button class="zoom-btn" id="zoomOut">-</button> </div> <img class="modal-image" id="modalImage" src="" alt=""> </div>
2. CSS样式
重点是给弹窗图片设置过渡属性,同时控制弹窗的全屏覆盖、层级:
/* 缩略图样式 */ .thumbnail { width: 200px; height: 150px; object-fit: cover; cursor: zoom-in; margin: 10px; } /* 弹窗容器 */ .image-modal { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,0.8); display: none; align-items: center; justify-content: center; z-index: 999; overflow: hidden; } .image-modal.active { display: flex; } /* 弹窗图片 */ .modal-image { position: absolute; transition: transform 0.3s ease, opacity 0.3s ease; cursor: grab; max-width: 90vw; max-height: 90vh; opacity: 0; } .modal-image.animate-in { opacity: 1; transform: translate(0, 0) scale(1); } /* 控制按钮样式 */ .modal-close { position: absolute; top: 20px; right: 20px; color: white; background: transparent; border: none; font-size: 32px; cursor: pointer; } .modal-controls { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); display: flex; gap: 10px; } .zoom-btn { padding: 8px 16px; font-size: 20px; cursor: pointer; border: none; border-radius: 4px; background: rgba(255,255,255,0.9); }
3. JavaScript逻辑
分四个核心功能:打开弹窗的平滑过渡、缩放控制、关闭弹窗的平滑返回、防止滚动穿透:
const modal = document.getElementById('imageModal'); const modalImg = document.getElementById('modalImage'); const closeBtn = document.getElementById('modalClose'); const zoomInBtn = document.getElementById('zoomIn'); const zoomOutBtn = document.getElementById('zoomOut'); const thumbnails = document.querySelectorAll('.thumbnail'); let currentScale = 1; let currentThumbnail = null; let initialRect = null; // 打开弹窗逻辑 thumbnails.forEach(thumb => { thumb.addEventListener('click', function() { currentThumbnail = this; initialRect = this.getBoundingClientRect(); currentScale = 1; // 设置弹窗图片初始位置和大小 modalImg.src = this.dataset.original; modalImg.style.transform = `translate(${initialRect.left}px, ${initialRect.top}px) scale(${initialRect.width / modalImg.naturalWidth})`; modalImg.style.opacity = '0'; // 显示弹窗后触发过渡 modal.classList.add('active'); // 强制重绘确保过渡生效 void modalImg.offsetWidth; modalImg.classList.add('animate-in'); // 禁止背景滚动 document.body.style.overflow = 'hidden'; }); }); // 缩放控制 zoomInBtn.addEventListener('click', () => { if (currentScale < 3) { currentScale += 0.2; modalImg.style.transform = `scale(${currentScale})`; } }); zoomOutBtn.addEventListener('click', () => { if (currentScale > 0.5) { currentScale -= 0.2; modalImg.style.transform = `scale(${currentScale})`; } }); // 关闭弹窗逻辑 closeBtn.addEventListener('click', closeModal); function closeModal() { // 移除过渡类,触发反向动画 modalImg.classList.remove('animate-in'); // 还原到缩略图位置和大小 modalImg.style.transform = `translate(${initialRect.left}px, ${initialRect.top}px) scale(${initialRect.width / modalImg.naturalWidth})`; // 等待过渡完成后隐藏弹窗 setTimeout(() => { modal.classList.remove('active'); document.body.style.overflow = ''; }, 300); // 与CSS过渡时间一致 } // 点击背景关闭弹窗 modal.addEventListener('click', (e) => { if (e.target === modal) { closeModal(); } });
关键细节说明
- 过渡触发时机:通过
void modalImg.offsetWidth强制浏览器重绘,确保初始状态被正确识别后再触发过渡动画。 - 位置计算:使用
getBoundingClientRect()获取缩略图的视口坐标,保证弹窗图片从精确位置开始过渡。 - 缩放边界限制:设置缩放范围(0.5-3倍)避免图片过小或过大超出视口。
- 滚动穿透处理:打开弹窗时禁用
body的滚动,关闭后恢复。
内容的提问来源于stack exchange,提问作者KonYaco
相关产品推荐
相关产品推荐

