使用JavaScript实现前端图片预览与压缩上传
引言
在现代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);
});
});
