当前位置:首页 > 工具 > 正文内容

使用JavaScript实现前端图片预览与压缩上传

访客 工具 2026年11月5日 1

引言

在现代Web应用中,用户上传图片是一个常见的功能。为了提升用户体验并优化服务器资源,通常需要在图片上传到服务器之前进行客户端处理,包括实时预览、格式校验以及必要的尺寸和质量压缩。本文将详细介绍如何使用纯JavaScript实现这些功能。

基本图片预览功能

首先,我们来实现一个基础的图片预览功能。当用户选择图片文件后,我们立即在页面上显示该图片的缩略图。

HTML 结构

一个简单的文件输入框和一个用于显示预览图的<img>标签。

<input type="file" id="imageFileInput" accept="image/*">
<div id="previewContainer" style="margin-top: 10px; border: 1px solid #eee; padding: 10px; min-height: 100px; display: flex; align-items: center; justify-content: center;">
    <img id="previewImage" src="" alt="图片预览" style="max-width: 200px; max-height: 200px; display: none;">
    <p id="previewPlaceholder">请选择图片进行预览</p>
</div>
<p id="errorMessage" style="color: red; display: none;"></p>

JavaScript 逻辑

我们监听文件输入框的change事件。当文件被选中时,使用FileReader对象读取文件内容并将其显示为Base64编码的数据URL。

document.addEventListener('DOMContentLoaded', () => {
    const fileInput = document.getElementById('imageFileInput');
    const previewImage = document.getElementById('previewImage');
    const previewPlaceholder = document.getElementById('previewPlaceholder');
    const errorMessage = document.getElementById('errorMessage');

    fileInput.addEventListener('change', (event) => {
        const selectedFile = event.target.files[0];

        // 清除之前的错误信息和预览状态
        errorMessage.style.display = 'none';
        previewImage.style.display = 'none';
        previewImage.src = '';
        previewPlaceholder.style.display = 'block';
        previewPlaceholder.textContent = '请选择图片进行预览';


        if (!selectedFile) {
            errorMessage.textContent = '错误:未选择任何文件。';
            errorMessage.style.display = 'block';
            return;
        }

        // 校验文件类型是否为图片
        if (!selectedFile.type.startsWith('image/')) {
            errorMessage.textContent = '错误:请选择图片文件。';
            errorMessage.style.display = 'block';
            return;
        }

        const fileReader = new FileReader();

        fileReader.onloadstart = () => {
            previewPlaceholder.textContent = '正在读取文件...';
        };

        fileReader.onprogress = (e) => {
            if (e.lengthComputable) {
                const percent = Math.round((e.loaded / e.total) * 100);
                previewPlaceholder.textContent = `读取进度:${percent}%`;
            }
        };

        fileReader.onload = (e) => {
            previewImage.src = e.target.result; // Data URL (Base64编码)
            previewImage.style.display = 'block';
            previewPlaceholder.style.display = 'none';
            errorMessage.style.display = 'none';
            // console.log('图片读取成功并显示预览');
        };

        fileReader.onerror = () => {
            errorMessage.textContent = '错误:文件读取失败。';
            errorMessage.style.display = 'block';
            previewPlaceholder.style.display = 'block';
            previewPlaceholder.textContent = '请选择图片进行预览';
            // console.error('文件读取出错');
        };

        // 读取文件内容为Data URL
        fileReader.readAsDataURL(selectedFile);
    });
});

客户端图片压缩处理

为了节省带宽和存储空间,在上传大尺寸图片前,通常需要对其进行客户端压缩。我们可以利用HTML5的Canvas API实现图片尺寸调整和质量压缩。

压缩函数实现

下面的compressAndEncodeImage函数接受一个File对象和压缩选项(如目标宽度、高度、输出质量),然后通过回调函数返回Base64编码的压缩图片数据。

/**
 * 客户端图片压缩和Base64编码
 * @param {File} imageFile - 用户选择的图片文件对象
 * @param {Object} options - 压缩选项
 *   @param {number} [options.maxWidth] - 图像最大宽度,保持宽高比
 *   @param {number} [options.maxHeight] - 图像最大高度,保持宽高比
 *   @param {number} [options.quality=0.8] - JPEG图片质量 (0-1), 默认为0.8
 *   @param {string} [options.outputFormat='image/jpeg'] - 输出图片格式, 默认为'image/jpeg'
 * @param {function(string | null, Error | null)} callback - 回调函数, 成功时返回Base64字符串,失败时返回Error
 */
function compressAndEncodeImage(imageFile, options = {}, callback) {
    if (!imageFile || !imageFile.type.startsWith('image/')) {
        return callback(null, new Error('无效的图片文件。'));
    }

    const fileReader = new FileReader();
    fileReader.onload = (fileReaderEvent) => {
        const img = new Image();
        img.src = fileReaderEvent.target.result;

        img.onload = () => {
            const originalWidth = img.width;
            const originalHeight = img.height;

            let processingWidth = originalWidth;
            let processingHeight = originalHeight;

            // 根据maxWidth和maxHeight调整尺寸,保持宽高比
            if (options.maxWidth && originalWidth > options.maxWidth) {
                processingWidth = options.maxWidth;
                processingHeight = originalHeight * (processingWidth / originalWidth);
            }
            if (options.maxHeight && processingHeight > options.maxHeight) {
                processingHeight = options.maxHeight;
                processingWidth = originalWidth * (processingHeight / originalHeight);
            }

            // 创建Canvas元素并设置尺寸
            const canvas = document.createElement('canvas');
            const ctx = canvas.getContext('2d');

            canvas.width = processingWidth;
            canvas.height = processingHeight;

            // 在Canvas上绘制缩放后的图片
            ctx.drawImage(img, 0, 0, processingWidth, processingHeight);

            // 获取Base64编码的图片数据
            const outputQuality = Math.min(Math.max(options.quality || 0.8, 0), 1); // 确保质量在0到1之间
            const outputFormat = options.outputFormat || 'image/jpeg';
            const compressedBase64 = canvas.toDataURL(outputFormat, outputQuality);

            callback(compressedBase64, null); // 成功回调
        };

        img.onerror = () => {
            callback(null, new Error('图片加载失败,无法进行压缩。'));
        };
    };

    fileReader.onerror = () => {
        callback(null, new Error('文件读取失败。'));
    };

    fileReader.readAsDataURL(imageFile);
}

如何结合预览和压缩

现在,我们可以将预览功能与压缩功能结合起来。在文件选择后,首先进行预览,然后调用压缩函数,并将压缩后的Base64数据用于实际上传(这里只演示获取压缩数据)。

document.addEventListener('DOMContentLoaded', () => {
    const fileInput = document.getElementById('imageFileInput');
    const previewImage = document.getElementById('previewImage');
    const previewPlaceholder = document.getElementById('previewPlaceholder');
    const errorMessage = document.getElementById('errorMessage');
    
    // 创建一个用于显示压缩信息的P标签并插入到DOM中
    const compressedInfo = document.createElement('p');
    const previewContainer = document.getElementById('previewContainer');
    if (previewContainer) {
        previewContainer.parentNode.insertBefore(compressedInfo, previewContainer.nextSibling);
    }

    fileInput.addEventListener('change', (event) => {
        const selectedFile = event.target.files[0];

        // 清除之前的状态信息
        errorMessage.style.display = 'none';
        compressedInfo.textContent = '';
        previewImage.style.display = 'none';
        previewImage.src = '';
        previewPlaceholder.style.display = 'block';
        previewPlaceholder.textContent = '请选择图片进行预览';


        if (!selectedFile) {
            errorMessage.textContent = '未选择任何文件。';
            errorMessage.style.display = 'block';
            return;
        }

        if (!selectedFile.type.startsWith('image/')) {
            errorMessage.textContent = '错误:请选择图片文件。';
            errorMessage.style.display = 'block';
            return;
        }

        // --- 1. 执行图片预览 ---
        const previewFileReader = new FileReader();
        previewFileReader.onloadstart = () => {
            previewPlaceholder.textContent = '正在读取预览图片...';
        };
        previewFileReader.onload = (e) => {
            previewImage.src = e.target.result; // 显示原始图片预览
            previewImage.style.display = 'block';
            previewPlaceholder.style.display = 'none';
            // console.log('原始图片预览成功');

            // --- 2. 预览成功后,执行图片压缩 ---
            compressAndEncodeImage(selectedFile, { maxWidth: 800, quality: 0.6 }, (compressedBase64, error) => {
                if (error) {
                    console.error('图片压缩失败:', error);
                    compressedInfo.textContent = '图片压缩失败:' + error.message;
                    compressedInfo.style.color = 'red';
                } else {
                    console.log('原始图片大小:', (selectedFile.size / 1024).toFixed(2), 'KB');
                    console.log('压缩后图片大小:', (compressedBase64.length / 1024).toFixed(2), 'KB');
                    compressedInfo.textContent = `压缩成功! 原始: ${(selectedFile.size / 1024).toFixed(2)} KB, 压缩后: ${(compressedBase64.length / 1024).toFixed(2)} KB`;
                    compressedInfo.style.color = 'green';

                    // 此时 compressedBase64 包含了压缩后的图片数据,可以将其用于上传到服务器
                    // 例如: uploadToServer(compressedBase64, selectedFile.name);
                }
            });
        };
        previewFileReader.onerror = () => {
            errorMessage.textContent = '错误:文件读取失败,无法预览。';
            errorMessage.style.display = 'block';
            previewPlaceholder.textContent = '请选择图片进行预览';
        };
        previewFileReader.readAsDataURL(selectedFile);
    });
});
标签: JavaScript
返回列表

上一篇:将Android库发布到GitHub,实现便捷引用

没有最新的文章了...

相关文章

Trojan服务器搭建与配置

一、整体架构(先对齐认知)Clash Meta (PC / iOS / Android)        ↓ TLS   Trojan Server (443)        ↓     InternetTrojan 的核心是: TLS + HTTPS 流量伪装 看起来像正常网站 非常适合...

Tailscale 的详细用法

Tailscale 是一种基于 WireGuard 协议 的 零配置 VPN(虚拟私有网络)服务,让设备之间能够 安全、加密地直接连接,就像它们在同一个本地网络一样。它的核心特点是 简单、安全、跨平台。Tailscale 非常适合 没有公网 IP、两台电脑不在同一局域网 的场景。 简单来说,Tailscale 是什么?Tailscale 是一款让你的各种设备(电脑、服务器、手机...

Clash Tun 模式 导致 爱快(iKuai SD-Wan)内网域名无法访问

一、Clash  DNS 配置dns:  enable: true  listen: 0.0.0.0:53  ipv6: true  enhanced-mode: redir-host  nameserver:    - 223.5.5.5    - 223.6.6.6iKuai 内网域名 ...

深入解析Node.js运行环境与异步I/O架构

深入解析Node.js运行环境与异步I/O架构

核心定义与价值Node.js本质上是一个JavaScript运行环境,而非编程语言或应用框架。它赋予了JavaScript脱离浏览器在服务端、命令行工具及网络应用中执行的能力。其核心意义在于:用单一语言打通前后端开发壁垒。基于事件驱动与非阻塞I/O的架构特性,Node.js在处理API网关、实时通信及微服务等I/O密集型场景时表现卓越,已成为现代后端工程的主流选择。浏览器沙箱限制1995年Java...

ADO.NET SQL参数化查询的最佳实践

在 ADO.NET 中执行 SQL 查询时,参数化查询是一种关键的安全措施和性能优化手段。它通过将 SQL 命令和用户提供的数据分开处理,有效防止了 SQL 注入攻击,并有助于数据库缓存执行计划。下面总结了几种常用的参数化查询方式。 1. 使用 SqlParameter 对象(推荐) 这是最推荐的参数化查询方式。通过显式创建 SqlParameter 对象,您可以精确控制参数的类...

基于ELK的日志集中化分析系统搭建

构建统一日志管理平台的必要性 在分布式架构中,各服务节点独立运行,日志分散存储于不同主机。传统通过命令行工具如grep、awk逐个检索日志的方式,在数据量庞大时效率极低,难以实现快速定位问题。为提升运维效率,需建立集中式日志处理体系,具备日志采集、传输、存储、分析与告警能力。 ELK技术栈核心组件解析 Elasticsearch:分布式搜索引擎,支持全文检索、实时数据分析和高可用集群部署,...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。