本文介绍如何通过 JavaScript 和 CSS 技巧解决浏览器默认文件上传按钮样式难以定制的问题。通过隐藏真实上传框并叠加自定义 UI 元素,结合 DOM 操作同步文件路径,实现美观且功能完整的上传组件。
问题
在网页开发中,常见的输入框样式如下所示:
设计师可能希望上传区域也采用相同的设计风格,并额外添加一个选择按钮。然而,当尝试将普通输入框替换为文件上传控件时,往往无法达到预期效果。由于各浏览器对默认按钮的渲染存在显著差异,直接为默认按钮添加样式几乎是不可能的任务。

尽管这并非完美设计的上传界面,但这已是当前技术条件下能实现的最好效果。
值得注意的是,Safari 浏览器的设计风格略有不同。Safari 团队可能出于安全或溢出考虑,关闭了手动输入文件路径的功能。这种设计的缺点在于,用户一旦选择了文件,便无法取消选择。
解决办法
读者 Michael McGrady 提出了一种巧妙的解决方案,用于解决文件上传按钮的样式定制问题。本文中的所有解决方案均源自他的创意,我在此基础上增加了 position:relative 属性、添加注释并进行测试,最终将其转化为 JavaScript 实现。
未使用该技巧时的效果:
使用后的预期效果:
现在看起来是否好多了?
McGrady 的方法简洁而优雅:
1、创建一个普通的<input type="file">,并将其放置在具有 position:relative 属性的父元素中。
2、在同一个父元素中,添加一个普通的<input>和一个图片,并对其进行样式设置。通过设置绝对定位,使这个普通 input 与<input type="file">重叠。
3、将<input type="file">的 z-index 设置为 2,使其显示在普通 input 的上方。
4、最后,将<input type="file">的 opacity 设置为 0。这样,<input type="file">变得不可见,而下层的 input/image 得以显示,但用户仍然可以点击“浏览”按钮。如果按钮位于图片上方,视觉上看起来就像点击了图片一样。
注意,不能使用 visibility:hidden,因为完全不可见的元素是无法被点击的,我们需要的是一个“可见但不可见”的可点击元素。
至此,该效果可以通过纯 CSS 实现,但还差一点。
5、当用户选择文件后,可见的假输入框应显示所选文件的路径,就像正常的<input type="file">一样。虽然只需简单地将<input type="file">的内容复制过来,但这需要 JavaScript 的支持。
因此,如果没有 JavaScript,这项技术可能无法完全实现。稍后我会解释原因。我决定将整个想法封装为 JavaScript 代码。如果你不需要显示文件名,也可以使用纯 CSS 方式,尽管这并非最佳方案。
HTML/CSS 结构 我打算采用以下 HTML/CSS 结构:
代码如下:
div.fileinputs {
position: relative;
}
div.fakefile {
position: absolute;
top: 0px;
left: 0px;
z-index: 1;
}
input.file {
position: relative;
text-align: right;
-moz-opacity:0 ;
filter:alpha(opacity: 0);
opacity: 0;
z-index: 2;
}
<div class="fileinputs">
<input type="file" class="file" />
<div class="fakefile">
<input />
<img src="search.gif" />
</div>
</div> <div class="fileinputs">的位置设置为 relative,这样我们就能在其中放置一个绝对定位的层:假输入框。
<div class="fakefile">包含一个假输入框和一个按钮,其位置为绝对定位,z-index 值为 1,使其显示在真实上传框的下方。
真实的上传框也设置了 position 属性为 relative,以便设置其 z-index 值。总之,需要确保上传框显示在假输入框之上。然后我们将其透明度设置为 0,使其不可见。
还需要注意 text-align:right:因为 Mozilla 无法直接设置上传框的宽度,所以我们要确保浏览按钮位于 DIV 的右边缘,假按钮也位于右侧,且应在真按钮下方。
你还需要一些 CSS 代码来设置宽度、高度、边框等,在此示例中未详细列出。
为什么是 JavaScript?
使用 JavaScript 的第一个原因是要将文件路径复制到假的文本框里。
第二,JavaScript 会忽略掉没有意义的 HTML 代码:<div class="fakefile">,让代码保持干净。
最后,对于一些旧浏览器不能处理 CSS,在 Netscape 和 IE4 里面文件输入就不可访问。对于那些没有 CSS 的浏览器,用户会看到两个输入框,而且不能理解第二个是干嘛的。
Netscape 4 的问题
在 Netscape 4 里面用户只能看见按钮。可能是因为 position:absolute 的原因。
IE4 的问题
在 IE4 里面会有一个诡异的原来的“浏览”按钮的影子,而且不能点击。没有解决办法
Netscape 3 的问题
对于那些没有 CSS 功能的浏览器。虽然可以使用,但是两个输入框会让用户郁闷。
解决办法-JavaScript
这些问题的解决办法就是 JavaScript:通过 JavaScript 生成输入框和按钮。现在最坏的情况就是 JavaScript 不能执行,即使如此,用户也能上传文件。虽然不那么好看,但是还是能工作的。
所以原来复杂的 HTML 变成了:
代码如下:
<div class="fileinputs">
<input type="file" class="file">
</div> 我们通过 JavaScript 来添加其他元素。
代码
代码如下:
var W3CDOM = (document.createElement && document.getElementsByTagName);
function initFileUploads() {
if (!W3CDOM) return;
var fakeFileUpload = document.createElement('div');
fakeFileUpload.className = 'fakefile';
fakeFileUpload.appendChild(document.createElement('input'));
var image = document.createElement('img');
image.src='pix/button_select.gif';
fakeFileUpload.appendChild(image);
var x = document.getElementsByTagName('input');
for (var i=0;i<x.length;i++) {
if (x[i].type != 'file') continue;
if (x[i].parentNode.className != 'fileinputs') continue;
x[i].className = 'file hidden';
var clone = fakeFileUpload.cloneNode(true);
x[i].parentNode.appendChild(clone);
x[i].relatedElement = clone.getElementsByTagName('input')[0];
x[i].onchange = x[i].onmouseout = function () {
this.relatedElement.value = this.value;
}
}
} 解释
如果浏览器不支持 W3C DOM,那么什么也不做。
代码如下:
var W3CDOM = (document.createElement && document.getElementsByTagName);
function initFileUploads() {
if (!W3CDOM) return; 创建<div class="fakefile">和他的内容。需要的时候我们会复制它。
代码如下:
var fakeFileUpload = document.createElement('div');
fakeFileUpload.className = 'fakefile';
fakeFileUpload.appendChild(document.createElement('input'));
var image = document.createElement('img');
image.src='pix/button_select.gif';
fakeFileUpload.appendChild(image); 然后遍历页面上的所有 input,如果不是<input type="file">则忽略。
代码如下:
var x = document.getElementsByTagName('input');
for (var i=0;i<x.length;i++) {
if (x[i].type != 'file') continue; 再做一次检测:如果<input type="file">的父元素没有 fileinputs 的 class,则忽略。
代码如下:
if (x[i].parentNode.className != 'fileinputs') continue; 现在我们就找到了需要添加样式的上传框。首先我们添加一个 hidden 的类名。
代码如下:
x[i].className = 'file hidden'; 复制假的输入框然后添加在<input type="file">的父元素上。
代码如下:
var clone = fakeFileUpload.cloneNode(true);
x[i].parentNode.appendChild(clone); 现在我们就成功的添加了样式。但是还没有结束,我们希望用户在输入框内看到文件路径。
首先我们给<input type="file">创建一个属性,指向假的输入框:
代码如下:
x[i].relatedElement = clone.getElementsByTagName('input')[0]; 这样当用户改变了上传文件的时候我们就能很轻松及时的访问到假的输入框,然后复制路径。
在这有个问题,我们使用什么 event 呢?通常使用 change 事件,当上传文件改变的时候,假的输入框的值也随之改变。
但是 Mozilla 1.6 在上传框上不支持这个事件(Firefox 支持)。所以我在这里添加一个 onmouseout 的事件。(IE 下同样可以运行,Safari 不行)
代码如下:
x[i].onchange = x[i].onmouseout = function () {
this.relatedElement.value = this.value; 3 } 问题和扩展
还有一个问题,用户在选择了一个文件之后就不能取消了。
假设用户选择了一个文件之后,突然不想上传了。通常只需要删除文件路径就可以了。但是在我们的例子里却很难,试一试,可以删除但是通常与感觉相反。
所以我们希望用户通过修改假的输入框也能修改真正的上传路径。
允许选择是可能的。当用户选择了上传文件的任何部分,我们就选择整个假输入框的全部内容。
[code] x[i].onselect = function () { 2 this.relatedElement.select(); 3 }
但是 JavaScript 的安全性不允许程序修改上传路径,所以我们不能通过让用户修改输入框的内容来修改真正的上传路径。所以我决定放弃 onselect 事件。
一种可行的办法是:给假的输入框添加一个清楚按钮,当用户点击之后就删除掉原来的上传框然后重新创建一个。这虽然笨重,但是能确实删除用户不想上传的文件路径。我不觉得这个一定可以工作,我也没有写这部分代码。
点击事件的路径
有读者建议,去掉那些繁杂的 CSS,彻底隐藏上传框,然后把假的输入框的 click 事件绑定在真正的上传框上。非常棒的想法,而且比上面的简单的多。
[code] fakeField.onclick = function () { 2 realField.click() 3 }
这个 click() 方法允许你仿真一个表单项。复选框的点击,单选框被选中等等。然而 Mozilla 和 Opera 不支持。我想知道为什么,因为添加这个方法最大的不安全性就在于弹出一个选择文件的对话框而已。
所以我们也不能用这个简单的办法。
翻译地址:http://www.quirksmode.org/dom/inputfile.html
转载请保留以下信息
作者:北玉(tw:@rehawk)