记录技术成长,奔向未来!

前台页面图片点击放大缩小功能的实现(使用模态框lightbox)

我们理解需求:在前台文章详情页中,点击图片可以弹出一个模态框(lightbox),能放大查看、缩小、关闭等。需要为 Ryblog 添加一个轻量级图片查看器。点击图片后出现的模态框需要有“放大/缩小”按钮,方便用户调整查看尺寸。我们需要在模态框上添加两个按钮:放大(Zoom In)和缩小(Zoom Out),控制图片的 transform: scale()。

我们可以在 article.php 中引入一个简单的 lightbox 库,比如 lightbox2 或 fancybox,或者自己实现一个简单的模态框。为了轻量化,我们可以使用纯 CSS + JavaScript 实现一个简单的图片点击放大功能。

方案:监听 .article-content img 的点击事件,创建模态框显示该图片,并支持关闭。

无需引入外部库,直接修改 assets/js/main.js 添加代码。

给出实现代码。

请在 assets/js/main.js 末尾粘贴以下完整代码:

javascript
// ========== 图片点击放大(支持拖拽和滚轮缩放) ==========
(function() {
    let modal = null;
    let img = null;
    let transform = { scale: 1, translateX: 0, translateY: 0 };
    let isDragging = false;
    let startX = 0, startY = 0;
    let lastTranslateX = 0, lastTranslateY = 0;

    function createModal() {
        if (modal) return;

        // 创建模态框背景
        modal = document.createElement('div');
        modal.id = 'imageModal';
        modal.style.cssText = `
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0,0,0,0.9);
            z-index: 9999;
            overflow: hidden;
        `;

        // 图片容器(用于拖拽)
        const container = document.createElement('div');
        container.style.cssText = `
            width: 100%;
            height: 100%;
            display: flex;
            align-items: center;
            justify-content: center;
            overflow: hidden;
            cursor: grab;
        `;
        container.id = 'imageModalContainer';

        // 图片元素
        img = document.createElement('img');
        img.style.cssText = `
            max-width: none;
            max-height: none;
            user-select: none;
            pointer-events: none;
            transition: transform 0.05s linear;
        `;

        // 工具栏
        const toolbar = document.createElement('div');
        toolbar.style.cssText = `
            position: absolute;
            bottom: 20px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(0,0,0,0.6);
            color: white;
            padding: 8px 16px;
            border-radius: 30px;
            font-size: 14px;
            display: flex;
            gap: 12px;
            z-index: 10001;
            backdrop-filter: blur(5px);
        `;
        const zoomInBtn = document.createElement('button');
        zoomInBtn.textContent = '+';
        const zoomOutBtn = document.createElement('button');
        zoomOutBtn.textContent = '-';
        const resetBtn = document.createElement('button');
        resetBtn.textContent = '重置';
        const scaleText = document.createElement('span');
        scaleText.textContent = '100%';
        [zoomInBtn, zoomOutBtn, resetBtn].forEach(btn => {
            btn.style.cssText = 'background:transparent; border:none; color:white; font-size:18px; cursor:pointer; padding:0 8px;';
        });
        toolbar.appendChild(zoomOutBtn);
        toolbar.appendChild(scaleText);
        toolbar.appendChild(zoomInBtn);
        toolbar.appendChild(resetBtn);

        // 关闭按钮
        const closeBtn = document.createElement('span');
        closeBtn.innerHTML = '×';
        closeBtn.style.cssText = `
            position: absolute;
            top: 20px;
            right: 30px;
            font-size: 40px;
            color: white;
            cursor: pointer;
            z-index: 10001;
            font-weight: bold;
        `;

        container.appendChild(img);
        modal.appendChild(container);
        modal.appendChild(toolbar);
        modal.appendChild(closeBtn);
        document.body.appendChild(modal);

        // 事件绑定
        closeBtn.onclick = closeModal;
        modal.onclick = (e) => { if (e.target === modal) closeModal(); };

        // 滚轮缩放
        container.addEventListener('wheel', (e) => {
            e.preventDefault();
            const delta = e.deltaY > 0 ? -0.1 : 0.1;
            let newScale = transform.scale + delta;
            if (newScale < 0.5) newScale = 0.5;
            if (newScale > 3) newScale = 3;
            // 获取鼠标相对于图片的位置(简化:以图片中心为中心缩放)
            // 为了更好的体验,我们以鼠标位置为中心,但为简化,先以图片中心为中心
            const rect = img.getBoundingClientRect();
            const mouseX = e.clientX - rect.left;
            const mouseY = e.clientY - rect.top;
            const scaleRatio = newScale / transform.scale;
            transform.translateX = mouseX - (mouseX - transform.translateX) * scaleRatio;
            transform.translateY = mouseY - (mouseY - transform.translateY) * scaleRatio;
            transform.scale = newScale;
            applyTransform();
            updateScaleText(scaleText);
        });

        // 拖拽
        container.addEventListener('mousedown', (e) => {
            if (e.button !== 0) return;
            isDragging = true;
            startX = e.clientX;
            startY = e.clientY;
            lastTranslateX = transform.translateX;
            lastTranslateY = transform.translateY;
            container.style.cursor = 'grabbing';
            e.preventDefault();
        });
        window.addEventListener('mousemove', (e) => {
            if (!isDragging) return;
            const dx = e.clientX - startX;
            const dy = e.clientY - startY;
            transform.translateX = lastTranslateX + dx;
            transform.translateY = lastTranslateY + dy;
            applyTransform();
        });
        window.addEventListener('mouseup', () => {
            isDragging = false;
            container.style.cursor = 'grab';
        });

        // 按钮点击
        zoomInBtn.onclick = () => {
            transform.scale = Math.min(3, transform.scale + 0.2);
            applyTransform();
            updateScaleText(scaleText);
        };
        zoomOutBtn.onclick = () => {
            transform.scale = Math.max(0.5, transform.scale - 0.2);
            applyTransform();
            updateScaleText(scaleText);
        };
        resetBtn.onclick = () => {
            transform = { scale: 1, translateX: 0, translateY: 0 };
            applyTransform();
            updateScaleText(scaleText);
        };
        container.ondblclick = () => {
            transform = { scale: 1, translateX: 0, translateY: 0 };
            applyTransform();
            updateScaleText(scaleText);
        };

        function applyTransform() {
            img.style.transform = `translate(${transform.translateX}px, ${transform.translateY}px) scale(${transform.scale})`;
        }
        function updateScaleText(span) {
            span.textContent = Math.round(transform.scale * 100) + '%';
        }
    }

    function openModal(imageSrc) {
        createModal(); // 确保模态框存在
        if (!img) return;
        img.src = imageSrc;
        transform = { scale: 1, translateX: 0, translateY: 0 };
        applyTransform();
        const scaleSpan = document.querySelector('#imageModal .toolbar-scale') || (() => {
            const span = document.createElement('span');
            span.className = 'toolbar-scale';
            return span;
        })();
        updateScaleText(scaleSpan);
        modal.style.display = 'block';
        document.body.style.overflow = 'hidden';
    }

    function closeModal() {
        if (modal) modal.style.display = 'none';
        if (img) img.src = '';
        document.body.style.overflow = '';
    }

    // 辅助函数
    function applyTransform() {
        if (!img) return;
        img.style.transform = `translate(${transform.translateX}px, ${transform.translateY}px) scale(${transform.scale})`;
    }
    function updateScaleText(span) {
        if (span) span.textContent = Math.round(transform.scale * 100) + '%';
    }

    // 绑定所有 .article-content 内的图片
    function bindImageClick() {
        document.querySelectorAll('.article-content img').forEach(img => {
            if (img.hasAttribute('data-zoom-bound')) return;
            img.style.cursor = 'pointer';
            img.addEventListener('click', (e) => {
                e.stopPropagation();
                openModal(img.src);
            });
            img.setAttribute('data-zoom-bound', 'true');
        });
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', bindImageClick);
    } else {
        bindImageClick();
    }

    // 监听动态内容
    const observer = new MutationObserver(() => bindImageClick());
    const targetNode = document.querySelector('.article-content');
    if (targetNode) observer.observe(targetNode, { childList: true, subtree: true });
})();


功能说明


点击文章图片 → 模态框弹出,显示图片。
标滚轮缩放:以光标位置为中心放大/缩小,范围 50% ~ 300%。 
 鼠标拖拽平移:按住图片区域拖动即可移动视图(缩放后尤其有用)。 
 工具栏:显示当前缩放比例,提供 +/- 按钮和重置按钮。 
 双击图片:快速重置为原始大小和位置。 
 关闭:点击背景或 ❌ 按钮关闭。

保存后刷新前台文章页即可体验

该功能能否通用?

回答:是的,该函数独立,只要页面有 .article-content img 且引入了 main.js 即可。对于 Ryweb1.0,直接复制到 main.js 末尾即可,无需额外修改。


这个图片点击放大功能是完全独立、零依赖的,不依赖于任何框架或特定的类名(除了 .article-content img 这个选择器)。因此,它可以应用到任何结构类似的网站中,只要将代码复制到您的 main.js 文件末尾即可。

对于 Ryweb 1.0 或者别的系统,操作步骤如下:

  1. 打开 /assets/js/main.js 文件。

  2. 将函数代码追加到文件末尾即可。

  3. 保存文件,清除浏览器缓存,刷新前台文章页。

为什么直接复制就能用?

  • 代码自动查找页面中 .article-content img 元素,这正是 Ryweb 1.0 文章内容区包裹图片的容器类。

  • 无需修改 HTML 或 PHP,完全由 JavaScript 驱动。

  • 样式、事件、模态框全部在代码中动态创建,不会与现有样式冲突。

如果需要应用到其他网站,只需确保选择器匹配您网站文章内容区的图片容器。例如:

  • 若您的文章内容区类名为 .post-content,则将代码中的 '.article-content img' 改为 '.post-content img' 即可。

现在您已经拥有了一个轻量、实用的图片放大预览功能,可以直接集成到您的系统中。


发表评论 (0)

留下你的足迹

验证码 点击图片刷新