Skip to content

Instantly share code, notes, and snippets.

@PrintNow
Created April 30, 2025 07:34
Show Gist options
  • Save PrintNow/51a80f76ab4e7e0ebc011be8e91f732c to your computer and use it in GitHub Desktop.
Save PrintNow/51a80f76ab4e7e0ebc011be8e91f732c to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图片查看器插件</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
}
.post-content {
max-width: 800px;
margin: 0 auto;
border: 1px solid #ddd;
padding: 20px;
}
.post-content img {
max-width: 100%;
cursor: pointer;
}
/* 图片查看器样式 */
.img-viewer-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.9);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 9999;
opacity: 0;
transition: opacity 0.3s;
pointer-events: none;
}
.img-viewer-overlay.active {
opacity: 1;
pointer-events: all;
}
.img-viewer-container {
position: relative;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.img-viewer-img {
max-width: 90vw;
max-height: 80vh;
object-fit: contain;
transform-origin: center;
transition: transform 0.2s;
}
.img-viewer-close {
position: absolute;
top: 15px;
right: 15px;
color: white;
font-size: 30px;
cursor: pointer;
width: 40px;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 50%;
z-index: 10000;
}
.img-viewer-controls {
position: fixed;
bottom: 20px;
left: 0;
width: 100%;
display: flex;
justify-content: center;
gap: 15px;
z-index: 10000;
}
.img-viewer-btn {
background-color: rgba(255, 255, 255, 0.2);
color: white;
border: none;
border-radius: 5px;
padding: 8px 15px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.2s;
}
.img-viewer-btn:hover {
background-color: rgba(255, 255, 255, 0.3);
}
</style>
</head>
<body>
<h1>图片查看器插件演示</h1>
<div class="post-content" itemprop="articleBody">
<h2>示例文章</h2>
<p>这是一个示例文章,包含多张图片。点击图片可以放大查看。</p>
<p><img src="https://picsum.photos/id/1015/600/400" alt="示例图片1"></p>
<p>这是第一张示例图片的说明文字。</p>
<p><img src="https://picsum.photos/id/1016/600/400" alt="示例图片2"></p>
<p>这是第二张示例图片的说明文字。</p>
<p><img src="https://picsum.photos/id/1018/600/400" alt="示例图片3"></p>
<p>这是第三张示例图片的说明文字。</p>
</div>
<script>
/**
* 图片查看器插件
* 功能:
* 1. 点击图片放大显示
* 2. 滚轮缩放
* 3. 放大、缩小、上一张、下一张按钮
* 4. 键盘方向键切换图片
* 5. ESC键、点击关闭按钮或点击非图片区域关闭
*/
class ImageViewer {
constructor(selector) {
// 初始化变量
this.containerSelector = selector;
this.container = document.querySelector(selector);
this.images = this.container.querySelectorAll('img');
this.currentIndex = 0;
this.scale = 1;
this.minScale = 0.5;
this.maxScale = 3;
this.scaleStep = 0.2;
// 创建查看器DOM
this.createViewerDOM();
// 绑定事件
this.bindEvents();
}
createViewerDOM() {
// 创建遮罩层
this.overlay = document.createElement('div');
this.overlay.className = 'img-viewer-overlay';
// 创建图片容器
this.imageContainer = document.createElement('div');
this.imageContainer.className = 'img-viewer-container';
// 创建图片元素
this.viewerImage = document.createElement('img');
this.viewerImage.className = 'img-viewer-img';
// 创建关闭按钮
this.closeBtn = document.createElement('div');
this.closeBtn.className = 'img-viewer-close';
this.closeBtn.innerHTML = '&times;';
// 创建控制按钮
this.controls = document.createElement('div');
this.controls.className = 'img-viewer-controls';
// 创建各个控制按钮
this.prevBtn = this.createButton('上一张', 'prev');
this.zoomOutBtn = this.createButton('缩小', 'zoom-out');
this.zoomInBtn = this.createButton('放大', 'zoom-in');
this.nextBtn = this.createButton('下一张', 'next');
// 组装DOM
this.controls.appendChild(this.prevBtn);
this.controls.appendChild(this.zoomOutBtn);
this.controls.appendChild(this.zoomInBtn);
this.controls.appendChild(this.nextBtn);
this.imageContainer.appendChild(this.viewerImage);
this.overlay.appendChild(this.closeBtn);
this.overlay.appendChild(this.imageContainer);
this.overlay.appendChild(this.controls);
// 添加到body
document.body.appendChild(this.overlay);
}
createButton(text, action) {
const btn = document.createElement('button');
btn.className = 'img-viewer-btn';
btn.dataset.action = action;
btn.textContent = text;
return btn;
}
bindEvents() {
// 图片点击事件
this.images.forEach((img, index) => {
img.addEventListener('click', () => {
this.open(index);
});
});
// 关闭按钮点击事件
this.closeBtn.addEventListener('click', () => {
this.close();
});
// 遮罩层点击事件(点击非图片区域关闭)
this.overlay.addEventListener('click', (e) => {
if (e.target === this.overlay) {
this.close();
}
});
// 阻止图片点击冒泡到遮罩层
this.viewerImage.addEventListener('click', (e) => {
e.stopPropagation();
});
// 控制按钮点击事件 - 使用事件委托优化性能
this.controls.addEventListener('click', (e) => {
const button = e.target.closest('button');
if (!button) return;
const action = button.dataset.action;
switch (action) {
case 'prev':
this.prev();
break;
case 'next':
this.next();
break;
case 'zoom-in':
this.zoomIn();
break;
case 'zoom-out':
this.zoomOut();
break;
}
});
// 使用节流函数优化滚轮事件
const throttledWheel = this.throttle((e) => {
e.preventDefault();
if (e.deltaY < 0) {
this.zoomIn();
} else {
this.zoomOut();
}
}, 100);
this.overlay.addEventListener('wheel', throttledWheel, { passive: false });
// 键盘事件 - 使用防抖函数优化
const debouncedKeydown = this.debounce((e) => {
if (!this.overlay.classList.contains('active')) return;
switch (e.key) {
case 'Escape':
this.close();
break;
case 'ArrowLeft':
this.prev();
break;
case 'ArrowRight':
this.next();
break;
case '+':
this.zoomIn();
break;
case '-':
this.zoomOut();
break;
}
}, 100);
document.addEventListener('keydown', debouncedKeydown);
// 窗口大小变化事件 - 使用防抖函数优化
const debouncedResize = this.debounce(() => {
if (this.overlay.classList.contains('active')) {
this.updateZoom();
}
}, 200);
window.addEventListener('resize', debouncedResize);
}
// 防抖函数 - 优化性能
debounce(func, delay) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), delay);
};
}
// 节流函数 - 优化性能
throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
open(index) {
this.currentIndex = index;
this.scale = 1;
this.updateImage();
this.overlay.classList.add('active');
document.body.style.overflow = 'hidden'; // 防止页面滚动
}
close() {
this.overlay.classList.remove('active');
document.body.style.overflow = ''; // 恢复页面滚动
}
prev() {
this.currentIndex = (this.currentIndex - 1 + this.images.length) % this.images.length;
this.scale = 1;
this.updateImage();
}
next() {
this.currentIndex = (this.currentIndex + 1) % this.images.length;
this.scale = 1;
this.updateImage();
}
zoomIn() {
if (this.scale < this.maxScale) {
this.scale += this.scaleStep;
this.updateZoom();
}
}
zoomOut() {
if (this.scale > this.minScale) {
this.scale -= this.scaleStep;
this.updateZoom();
}
}
updateImage() {
const img = this.images[this.currentIndex];
this.viewerImage.src = img.src;
this.viewerImage.alt = img.alt;
// 等待图片加载完成
this.viewerImage.onload = () => {
this.updateZoom();
};
}
updateZoom() {
this.viewerImage.style.transform = `scale(${this.scale})`;
}
}
// 初始化插件
document.addEventListener('DOMContentLoaded', () => {
new ImageViewer('.post-content');
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment