原始代码(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 已不再需要:
  1. *display: inline; *zoom: 1——IE 7 专属,IE 11 已退役,Edge / Chrome / Firefox 全部原生支持 display: inline-block。
  2. filter: alpha(opacity=0)——IE 8 专属;现代 opacity: 0 已统一。
  3. 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)优

使用准则

  1. 优先 <label> 包裹方案——最少代码、最多兼容性(IE 10+ / 现代浏览器)。
  2. 永远不要用 filter: alpha(opacity=0) 这种 IE hack。
  3. 文件名显示一定要做——否则用户不知道上传成功没有。
  4. 拖拽是体验加分项——但要 fallback 到点击选择。
  5. 用 accept 属性限制文件类型(如 accept="image/*"),浏览器会按类型筛选。
  6. 大文件上传永远要 multiple + 分片,否则用户手机端永远失败。
参考资料