原始代码(IE 时代)
1. DOM 结构
<a href="javascript:;" class="a-upload">
<input type="file" name="" id="">点击这里上传文件
</a>
<a href="javascript:;" class="file">选择文件
<input type="file" name="" id="">
</a>
原理:用 <a> 作为"按钮视觉",把真正的 <input> 用 position absolute 全覆盖在上层并设为透明。
2. CSS 样式 1(灰底)
.a-upload {
padding: 4px 10px;
height: 20px;
line-height: 20px;
position: relative;
cursor: pointer;
color: #888;
background: #fafafa;
border: 1px solid #ddd;
border-radius: 4px;
overflow: hidden;
display: inline-block;
*display: inline; /* IE 7 hack */
*zoom: 1 /* IE 7 hack */
}
.a-upload input {
position: absolute;
font-size: 100px;
/* 设置了 input 宽度后,cursor 属性将不会起作用,需要 font-size:0 */
font-size: 0;
right: 0;
top: 0;
opacity: 0;
filter: alpha(opacity=0); /* IE 8 hack */
cursor: pointer;
width: 100%;
height: 100%;
}
.a-upload:hover {
color: #444;
background: #eee;
border-color: #ccc;
text-decoration: none;
}
3. CSS 样式 2(蓝底)
.file {
position: relative;
display: inline-block;
background: #D0EEFF;
border: 1px solid #99D3F5;
border-radius: 4px;
padding: 4px 12px;
overflow: hidden;
color: #1E88C7;
text-decoration: none;
text-indent: 0;
line-height: 20px;
}
.file input {
position: absolute;
font-size: 100px;
right: 0;
top: 0;
opacity: 0;
}
.file:hover {
background: #AADFFD;
border-color: #78C3F3;
color: #004974;
}
4. jQuery 显示文件名
$(".a-upload").on("change", "input[type='file']", function () {
var filePath = $(this).val();
if (filePath.indexOf("jpg") !== -1 || filePath.indexOf("png") !== -1) {
$(".fileerrorTip").html("").hide();
var arr = filePath.split('\\');
var fileName = arr[arr.length - 1];
$(".showFileName").html(fileName);
} else {
$(".showFileName").html("");
$(".fileerrorTip").html("您未上传文件,或者您上传文件类型有误!").show();
return false;
}
});
三个 IE hack 已不再需要:
*display: inline; *zoom: 1——IE 7 专属,IE 11 已退役,Edge / Chrome / Firefox 全部原生支持display: inline-block。filter: alpha(opacity=0)——IE 8 专属;现代opacity: 0已统一。font-size: 100px技巧——是为了触发 IE 点击区域,现代浏览器不需要,opacity: 0即可保持点击。
方案 1:纯 CSS(<label> 包裹)
最简单、最现代的方式——用 <label> 包裹,把样式写在 label 上:
<label class="upload-btn">
<input type="file" hidden>选择文件
</label>
<label class="upload-btn primary">
<input type="file" hidden multiple>批量上传
</label>
.upload-btn {
display: inline-flex;
align-items: center;
padding: 8px 16px;
border-radius: 4px;
background: #fafafa;
border: 1px solid #ddd;
color: #444;
cursor: pointer;
transition: all 0.2s;
}
.upload-btn:hover {
background: #eee;
border-color: #ccc;
}
.upload-btn.primary {
background: #1e88c7;
border-color: #78c3f3;
color: white;
}
.upload-btn.primary:hover {
background: #006cb0;
color: white;
}
/* 自定义 input 选中后的文件名显示 */
.upload-btn::after {
content: attr(data-filename);
margin-left: 8px;
color: #888;
font-style: italic;
}
文件名显示(不用 jQuery)
document.querySelectorAll('input[type=file]').forEach(input => {
input.addEventListener('change', () => {
const label = input.closest('label');
const fileName = input.files[0]?.name ?? '';
label.setAttribute('data-filename', fileName);
label.classList.toggle('has-file', !!fileName);
});
});
方案 2:JS 完全自定义按钮
需要文件类型图标、缩略图预览、进度条等高级 UI:
<div class="file-picker" data-picker>
<button type="button" class="picker-trigger">
<span class="picker-icon">选择文件</span>
<span class="picker-filename">未选择任何文件
class FilePicker extends HTMLElement {
connectedCallback() {
this.trigger = this.querySelector('.picker-trigger');
this.filename = this.querySelector('.picker-filename');
this.fileInput = document.createElement('input');
this.fileInput.type = 'file';
this.fileInput.hidden = true;
this.appendChild(this.fileInput);
this.trigger.addEventListener('click', () => this.fileInput.click());
this.fileInput.addEventListener('change', () => {
const file = this.fileInput.files[0];
if (file) {
this.filename.textContent = `${file.name} (${this.formatSize(file.size)})`;
this.dispatchEvent(new CustomEvent('file-selected', {
detail: { file },
bubbles: true
}));
}
});
}
formatSize(bytes) {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 ** 2) return `${(bytes / 1024).toFixed(1)} KB`;
return `${(bytes / 1024 ** 2).toFixed(1)} MB`;
}
}
customElements.define('file-picker', FilePicker);
方案 3:现代浏览器原生 accent-color
现代浏览器支持 accent-color,可以一键统一表单元素的颜色:
:root {
accent-color: #1e88c7;
}
适用元素:
<input type="checkbox">、<input type="radio"><input type="range">(滑块)<progress>(进度条)
注意:<input type="file"> 的"选择文件"按钮仍然受浏览器 / 操作系统主题控制,无法用 accent-color 改色。
拖拽上传(现代标配)
无论用哪种 UI,文件上传都应支持拖拽:
<div class="dropzone" data-dropzone>
<p>拖拽文件到这里 / 点击选择</p>
<input type="file" hidden>
</div>
const dz = document.querySelector('[data-dropzone]');
const input = dz.querySelector('input');
dz.addEventListener('click', () => input.click());
dz.addEventListener('dragover', e => {
e.preventDefault();
dz.classList.add('dragover');
});
dz.addEventListener('dragleave', () => dz.classList.remove('dragover'));
dz.addEventListener('drop', e => {
e.preventDefault();
dz.classList.remove('dragover');
input.files = e.dataTransfer.files;
input.dispatchEvent(new Event('change'));
});
.dropzone {
border: 2px dashed #ccc;
border-radius: 8px;
padding: 40px;
text-align: center;
cursor: pointer;
transition: all 0.2s;
}
.dropzone.dragover {
border-color: #1e88c7;
background: #f0f9ff;
}
方案对比
| 方案 | 复杂度 | IE 兼容 | 可访问性 |
|---|---|---|---|
| 原 IE hack(label + 透明 input) | 中等 | IE 6-11 | 良(点击区是 input) |
| 纯 CSS(label 包 input) | 低 | IE 10+ | 优 |
| Web Component <file-picker> | 中 | 不兼容 IE | 优(自定义 ARIA) |
| Dropzone 拖拽 | 中 | IE 10+(drop) | 优 |
使用准则
- 优先
<label>包裹方案——最少代码、最多兼容性(IE 10+ / 现代浏览器)。 - 永远不要用
filter: alpha(opacity=0)这种 IE hack。 - 文件名显示一定要做——否则用户不知道上传成功没有。
- 拖拽是体验加分项——但要 fallback 到点击选择。
- 用
accept属性限制文件类型(如accept="image/*"),浏览器会按类型筛选。 - 大文件上传永远要
multiple+ 分片,否则用户手机端永远失败。