在 H5 移动端实现图片上传功能,核心在于利用 HTML 的 <input type="file"> 标签,并结合 JavaScript 处理文件对象、预览以及通过 FormData 或 Base64 发送到后端。
以下是完整的实现方案,包含 HTML 结构、CSS 样式、JavaScript 逻辑 以及 移动端适配注意事项。
完整代码示例
你可以直接复制以下代码到一个 .html 文件中并在手机浏览器或 Chrome 开发者工具(移动端模式)中测试。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">H5 图片上传示例</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
.upload-container {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
/ 隐藏原始的 input file /
#fileInput {
display: none;
}
/ 自定义上传按钮/区域 /
.upload-btn {
width: 80px;
height: 80px;
border: 1px dashed #ccc;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
background-color: #fff;
color: #999;
font-size: 24px;
cursor: pointer;
}
.upload-btn:active {
background-color: #eee;
}
/ 图片预览区域 /
.preview-item {
position: relative;
width: 80px;
height: 80px;
border-radius: 8px;
overflow: hidden;
}
.preview-item img {
width: 100%;
height: 100%;
object-fit: cover;
}
/ 删除按钮 /
.delete-btn {
position: absolute;
top: -5px;
right: -5px;
width: 20px;
height: 20px;
background-color: rgba(0,0,0,0.6);
color: white;
border-radius: 50%;
text-align: center;
line-height: 20px;
font-size: 12px;
cursor: pointer;
}
.submit-btn {
margin-top: 20px;
width: 100%;
padding: 12px;
background-color: #007AFF;
color: white;
border: none;
border-radius: 8px;
font-size: 16px;
cursor: pointer;
}
.submit-btn:disabled {
background-color: #ccc;
}
</style>
</head>
<body>
<h3>图片上传演示</h3>
<div class="upload-container" id="previewContainer">
<!-- 上传触发按钮 -->
<div class="upload-btn" id="uploadBtn">
+
</div>
</div>

<button class="submit-btn" id="submitBtn" disabled>提交图片</button>
<!-- 核心 input 标签 -->
<!-- accept="image/" 限制只能选图片 -->
<!-- multiple 允许选多张 -->
<input type="file" id="fileInput" accept="image/" multiple>
<script>
const fileInput = document.getElementById('fileInput');
const uploadBtn = document.getElementById('uploadBtn');
const previewContainer = document.getElementById('previewContainer');
const submitBtn = document.getElementById('submitBtn');
// 存储文件对象的数组
let files = [];
// 1. 点击自定义按钮触发原生 input 点击
uploadBtn.addEventListener('click', () => {
fileInput.click();
});
// 2. 监听文件选择变化
fileInput.addEventListener('change', (e) => {
const selectedFiles = Array.from(e.target.files);
if (selectedFiles.length === 0) return;
// 将新选择的文件添加到数组中
files = [...files, ...selectedFiles];
// 重新渲染预览
renderPreviews();
// 清空 input 的 value,这样选择相同的文件也能触发 change 事件
fileInput.value = '';
});
// 3. 渲染预览图
function renderPreviews() {
// 保留上传按钮,移除之前的预览项
const existingPreviews = previewContainer.querySelectorAll('.preview-item');
existingPreviews.forEach(item => item.remove());
// 如果文件太多,可以选择只显示前几张,这里演示全部显示
files.forEach((file, index) => {
const reader = new FileReader();
reader.onload = (e) => {
const div = document.createElement('div');
div.className = 'preview-item';
div.innerHTML = `
<img src="${e.target.result}" alt="preview">
<div class="delete-btn" onclick="removeFile(${index})">×</div>
`;
// 在上传按钮之前插入
previewContainer.insertBefore(div, uploadBtn);
};
reader.readAsDataURL(file);
});
// 更新提交按钮状态
submitBtn.disabled = files.length === 0;
submitBtn.innerText = `提交 (${files.length})`;
}
// 4. 删除图片
window.removeFile = function(index) {
files.splice(index, 1);
renderPreviews();
}
// 5. 提交上传逻辑
submitBtn.addEventListener('click', async () => {
if (files.length === 0) return;
const formData = new FormData();
// 将文件添加到 FormData
// 注意:'images' 是后端接收的字段名
files.forEach(file => {
formData.append('images', file);
});
try {
// 模拟发送请求 (实际项目中替换为 axios 或 fetch)

console.log('正在上传...', files.length, '张图片');
// 这里使用 fetch 示例
/
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
const result = await response.json();
/
// 模拟成功
setTimeout(() => {
alert('上传成功!(模拟)');
// 上传成功后清空
files = [];
renderPreviews();
}, 1000);
} catch (error) {
alert('上传失败');
console.error(error);
}
});
</script>
</body>
</html>
关键属性解析
在 <input type="file"> 中,以下属性对移动端体验至关重要:
| 属性 | 值 | 说明 |
|---|---|---|
type |
file |
指定为文件上传控件。 |
accept |
image/ |
关键:限制用户只能选择图片文件,在移动端点击时会直接弹出“拍照”或“从相册选择”的对话框,而不是文件管理器。 |
multiple |
multiple |
允许一次选择多张图片,如果不加此属性,iOS 和 Android 的行为可能不同(iOS 通常默认多选,Android 可能需要此属性或特定配置)。 |
capture |
camera |
(可选) 如果只想调用摄像头,可以加此属性,但通常建议用 accept="image/" 让用户自己选择拍照或相册。 |
移动端特殊注意事项
A. 摄像头调用问题 (iOS vs Android)
- iOS (Safari/微信):
- 使用
accept="image/"时,通常会弹出“拍照”或“照片库”选项。 - 如果希望强制调用摄像头,可以使用
capture="camera",但这会跳过相册选择。 - 坑点:在 iOS 14+ 中,如果用户选择“部分照片”,浏览器可能无法获取完整的
File对象,只能获取到缩略图或受限路径,这是系统安全限制,前端无法完全绕过,需后端配合处理或提示用户。
- 使用
- Android:
行为相对统一,通常直接弹出文件选择器(包含相机选项)。
B. 图片压缩 (非常重要)
移动端拍照的照片通常非常大(几 MB 到十几 MB),直接上传会导致:
- 上传速度慢。
- 消耗用户流量。
- 服务器存储压力大。
建议在前端进行压缩,可以使用以下方案:
- Canvas 压缩:将图片绘制到 Canvas 上,
toDataURL或toBlob输出压缩后的图片。 - 第三方库:推荐使用 compressorjs 或 blueimp-load-image。
简易 Canvas 压缩示例:
function compressImage(file, quality = 0.7, maxWidth = 1080) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = (e) => {
const img = new Image();
img.src = e.target.result;
img.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 计算缩放比例
let width = img.width;
let height = img.height;
if (width > maxWidth) {
height = maxWidth / width;
width = maxWidth;
}
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, width, height);
// 转换为 Blob 对象,方便上传
canvas.toBlob((blob) => {
resolve(new File([blob], file.name, {
type: 'image/jpeg',
lastModified: Date.now()
}));
}, 'image/jpeg', quality);
};
};
});
}
C. 上传进度条
对于大图上传,用户需要反馈。
- 如果使用
XMLHttpRequest或fetch(配合ReadableStream较复杂),可以使用axios的onUploadProgress属性来显示进度条。
D. 微信环境
如果在微信内打开:
- 可以使用微信 JSSDK 的
chooseImage和uploadImage接口,这样可以利用微信的 CDN 加速上传,速度更快,且能获取微信服务器返回的media_id。 - 但通用方案(如上面的代码)在微信中也能正常工作。
后端接收建议
- 字段名:确保
formData.append('key', file)中的'key'与后端接口定义的参数名一致。 - MIME 类型:前端发送的
FormData会自动处理Content-Type: multipart/form-data,后端无需手动设置。 - 文件大小限制:前端可以检查
file.size,如果超过限制(如 5MB),则提示用户或触发压缩。
- 使用
<input type="file" accept="image/" multiple>作为核心控件。 - 隐藏原生控件,用自定义 UI 触发点击。
- 使用
FileReader进行本地预览。 - 务必在前端进行图片压缩,优化用户体验。
- 使用
FormData+fetch/axios发送请求。
首发原创文章,作者:世雄 - 原生数据库架构专家,如若转载,请注明出处:https://idctop.com/article/479865.html



