从零开始:用JavaScript打造简洁高效的HTML图片查看器

简介

先上效果图:

可以看到整体布局简洁清爽,功能设计直观易用。界面支持左右翻页,底部显示页码,右上角展示缩略图,而中间则呈现大图。用户可以通过双击放大图片,长按鼠标则可进行拖动操作,这些简单的功能已经能够满足基本需求。

实现

首先,我们来写一下HTML界面。

HTML

<div id="preview-modal" class="modal">
    <span class="close">&times;</span>
    <button class="nav-btn prev-btn">&lt;</button>
    <button class="nav-btn next-btn">&gt;</button>
    <div class="modal-content">
        <img id="preview-image" src="" alt="Preview Image">
    </div>
    <div id="image-count" class="image-count">1 / {{ all_images }}</div>
</div>

短短几行足以。需要注意的是all_images表示总图片数,请自行定义。

CSS

.modal {
    display: none;                /* 默认隐藏 */
    position: fixed;
    justify-content: center;      /* 水平居中 */
    align-items: center;          /* 垂直居中 */
    z-index: 1001;
    left: 0;
    top: 0;
    width: 100%;
    height: 100vh;
    background-color: rgba(0, 0, 0, 0.9);
}

.modal-content {
    display: flex;
    justify-content: center;
    align-items: center;
    position: relative;
    margin: auto;
    max-width: 100%;
    max-height: 100%;
}

#preview-image {
    position: relative; /* 确保能够自由拖动 */
    max-width: 100%;
    max-height: 80vh;
    transition: transform 0.25s ease;
}

.nav-btn {
    position: absolute;
    top: 50%;
    background-color: rgba(0, 0, 0, 0.5);
    color: white;
    border: none;
    /*padding: 10px;*/
    cursor: pointer;
    font-size: 24px;
}

.prev-btn {
    left: 10px;
    z-index: 1005;
}

.next-btn {
    right: 10px;
    z-index: 1005;
}

.close {
    position: absolute;
    top: 20px;
    right: 35px;
    color: white;
    font-size: 40px;
    font-weight: bold;
    cursor: pointer;
    z-index: 1002;
}

/* 样式化图片序号显示 */
.image-count {
    position: absolute;
    left: 50%;     /* 水平居中 */
    top: 95%;
    transform: translateX(-50%);
    color: white;
    font-size: 18px;
    font-weight: bold;
    background-color: rgba(0, 0, 0, 0.5);  /* 背景色透明 */
    padding: 5px;
    border-radius: 5px;
}

@media (max-width: 630px) {
    .nav-btn{
        top: 90%;
    }
    .image-count{
        top: 90%;
    }

    .modal-content {
        max-width: 100%;
        max-height: 80%
    }

    .close {
        top: 10px;
        right: 10px;
    }
}

基本的样式,根据需要自行调整。

JS

预览界面入口

这部分是进入预览界面以及实现平移缩放功能的关键。我们先实现预览界面的函数调用。

{% for url links in all_urls %}
  <div class="grid-item">
    <a onclick="openPreview({{ loop.index0 }})">
      <img src="{{ url }}" alt="图片"/>
    </a>
  </div>
{% endfor %}

用<a>标签包裹img缩略图,并添加函数openPreview,这样我们就可以在点击缩略图时打开图片预览界面。openPreview函数需要传入当前图片的index用于获取图片url,这里我传了index。

接下来实现openPreview入口函数。

let currentImageIndex = 0;
let scale = 1;
const previewImage = document.getElementById('preview-image');
const modal = document.getElementById('preview-modal');
const imageCount = document.getElementById('image-count');

function openPreview(index0){
    currentImageIndex = index0;
    previewImage.src = links[currentImageIndex];
    modal.style.display = 'flex';  // 显示控件
    scale = 1;
    previewImage.style.transform = `scale(${scale})`;
    imageCount.textContent = `${currentImageIndex + 1} / ${links.length}`;
}

注意,变量links为大图地址的列表,请自行定义。

控件功能实现

const closeBtn = document.querySelector('.close');
const prevBtn = document.querySelector('.prev-btn');
const nextBtn = document.querySelector('.next-btn');

closeBtn.addEventListener('click', () => {
    modal.style.display = 'none';
    img_offsetX = 0;
    img_offsetY = 0;
    scale = 1;
});

// 上一张图片
prevBtn.addEventListener('click', () => {
    currentImageIndex = (currentImageIndex - 1 + links.length) % links.length;
    previewImage.src = links[currentImageIndex];
    scale = 1;
    previewImage.style.transform = `scale(${scale})`;
    imageCount.textContent = `${currentImageIndex + 1} / ${links.length}`;
});

// 下一张图片
nextBtn.addEventListener('click', () => {
    currentImageIndex = (currentImageIndex + 1) % links.length;
    previewImage.src = links[currentImageIndex];
    scale = 1;
    previewImage.style.transform = `scale(${scale})`;
    imageCount.textContent = `${currentImageIndex + 1} / ${links.length}`;
});

这里实现了关闭、上一页和下一页按钮的功能。其中img_offsetX、img_offsetY是后面平移功能使用到的变量,这里是已经完成好的代码。

平移功能实现

我们希望实现的平移效果是,用户点击previewImage并长按后,图片能够跟随鼠标滑动。

首先定义变量;

let scale = 1;
let isLongPress = false; // 长按标志位
let pressTimer; // 长按计时器
let startX, startY; // 鼠标的起始位置
let offsetX = 0, offsetY = 0; // 鼠标的偏移量
let img_offsetX = 0, img_offsetY = 0; // 图片的偏移量
let lastTouchTime = 0;

接下来实现具体逻辑;

previewImage.addEventListener('mousedown', (e) => {
    // 防止图片的默认拖动行为
    e.preventDefault();

    startX = e.clientX; // 记录鼠标单击时的初始位置
    startY = e.clientY;

    // 设置一个定时器判断是否为长按
    pressTimer = setTimeout(() => {
        isLongPress = true;
    }, 200);  // 200毫秒判断为长按,可以调整这个值

});

// 鼠标移动时拖动图片
previewImage.addEventListener('mousemove', (e) => {
    if (isLongPress) {
        // 计算鼠标的相对移动
        let deltaX = e.clientX - startX;
        let deltaY = e.clientY - startY;
        // 将鼠标移动量累加到图片的偏移量中
        img_offsetX += deltaX / scale;
        img_offsetY += deltaY / scale;
        // 刷新鼠标初始位置
        startX = e.clientX; 
        startY = e.clientY;
        previewImage.style.transition = 'none'; // 禁用动画
        previewImage.style.transform = `scale(${scale}) translate(${img_offsetX}px, ${img_offsetY}px)`; // 平移图片

    }
});

// 鼠标点击结束时
previewImage.addEventListener('mouseup', () => {
    // 清除定时器
    clearTimeout(pressTimer);
    isLongPress = false;
});

// 鼠标离开控件时
previewImage.addEventListener('mouseleave', () => {
    // 清除定时器
    clearTimeout(pressTimer);
    isLongPress = false;
});

在适配移动端时,需要响应触摸事件;

previewImage.addEventListener('touchstart', function(e) {
    // 记录触摸起始位置
    if (e.touches.length === 1) {
      startX = e.touches[0].clientX;
      startY = e.touches[0].clientY;
    }
});

previewImage.addEventListener('touchmove', function(e) {
    if (e.touches.length === 1) {
        // 获取滑动的距离
        const moveX = e.touches[0].clientX - startX;
        const moveY = e.touches[0].clientY - startY;

        // 更新偏移量
        img_offsetX += moveX / scale;
        img_offsetY += moveY / scale;

        startX = e.touches[0].clientX
        startY = e.touches[0].clientY

        // 更新图片位置
        previewImage.style.transition = 'none';
        previewImage.style.transform = `scale(${scale}) translate(${img_offsetX}px, ${img_offsetY}px)`;

        // 防止默认的触摸滚动行为
        e.preventDefault();
    }
});

移动端和PC端的处理有所不同。对于PC端,若不复位长按标志位,鼠标继续移动时,图片会继续跟随移动;而在移动端,触摸结束后,触摸点的位置不再变化,因此图片也不会继续移动。

缩放功能实现

首先来实现Ctrl+鼠标滚轮缩放;

// 监听鼠标滚轮缩放
previewImage.addEventListener('wheel', (e) => {
    // 如果没有按住 Ctrl 键,则不进行缩放
    if (!e.ctrlKey) {
        return;
    }

    e.preventDefault(); // 防止页面滚动

    if (e.deltaY < 0) {
        // 向上滚动:放大
        scale *= 1.1;
    } else {
        // 向下滚动:缩小
        scale /= 1.1;
    }

    // 限制缩放范围
    scale = Math.max(1, Math.min(scale, 5)); // 设定最小缩放为1,最大为3

    // 更新图片的transform属性
    previewImage.style.transition = 'transform 0.3s ease'; // 缩放加一点过渡效果
    previewImage.style.transform = `scale(${scale})`;
});

接下来实现双击缩放,我们要实现的效果是:放大并居中用户双击的位置,一共可以放大2次,每次放大3倍,图片放缩至最大时再次双击复原。

previewImage.addEventListener('dblclick', (e) => {
    // 获取屏幕中央位置
    const centerX = window.innerWidth / 2;
    const centerY = window.innerHeight / 2;
    if (scale < 10) {
        // 获取鼠标点击位置与屏幕中央的偏移
        offsetX = centerX - e.clientX;
        offsetY = centerY - e.clientY;
        scale *= 3;
        // 应用偏移
        img_offsetX += offsetX / scale;
        img_offsetY += offsetY / scale;
        previewImage.style.transition = 'transform 0.3s ease';
        previewImage.style.transform = `scale(${scale}) translate(${img_offsetX}px, ${img_offsetY}px)`;
    } else {
        // 复原
        scale = 1
        img_offsetX = 0
        img_offsetY = 0
        previewImage.style.transition = 'transform 0.3s ease';
        previewImage.style.transform = `scale(${scale}) translate(${img_offsetX}px, ${img_offsetY}px)`;
    }
});

最后适配移动端的缩放功能;

previewImage.addEventListener('touchend', function(e) {
    const currentTime = new Date().getTime();
    const timeDifference = currentTime - lastTouchTime; // 计算两次触摸的间隔时间

    if (timeDifference < 500 && timeDifference > 0) {
        if(e.touches && e.touches.length > 0){
            // 双击事件发生,时间间隔小于500ms视为双击
            const centerX = window.innerWidth / 2;
            const centerY = window.innerHeight / 2;
            if (scale < 10) {
                offsetX = centerX - e.touches[0].clientX;
                offsetY = centerY - e.touches[0].clientY;
                scale *= 3;

                img_offsetX += offsetX / scale;
                img_offsetY += offsetY / scale;
                previewImage.style.transition = 'transform 0.3s ease';
                previewImage.style.transform = `scale(${scale}) translate(${img_offsetX}px, ${img_offsetY}px)`;
            } else {
                scale = 1
                img_offsetX = 0
                img_offsetY = 0
                previewImage.style.transition = 'transform 0.3s ease';
                previewImage.style.transform = `scale(${scale}) translate(${img_offsetX}px, ${img_offsetY}px)`;
            }
        }
    }
    lastTouchTime = currentTime; // 更新上次触摸时间
});

到这里我们的简易图片预览界面就做好啦!

附件

为方便使用,这里免费提供js和css代码给大家~

preview-modal.js

preview-modal.css


从零开始:用JavaScript打造简洁高效的HTML图片查看器
https://blog.nasxyz.top/archives/37b5f9c4-8e37-4132-a64d-b3bcbcd4252a
作者
kanochi
发布于
2025年02月08日
更新于
2025年02月09日
许可协议