美化文件上传控件
原文地址:http://www.quirksmode.org/dom/inputfile.html
版权原作者所有 copyright: www.quirksmode.org translation date: 2007-02-04
使用CSS来改变所有的表单元素的样式时,文件上传控件的表现到目前为止是最差的。IE中仅提供一点点样式可以应用,Mozilla中支持的更少,而其他浏览器几乎不支持。特别是“浏览”按钮,完全不能使用CSS来操作。
问题
在建立站点时,我可以创建像下面的我需要的表单输入项:
设计师需要为上传控件使用相同的样式,为其加上一个"Select"图象。当我将那些可以使用在其他表单控件上的CSS规则应用到上传控件上时,它并没有像想象的那样起作用,并且在不同的浏览器里显示的样式也完全不一样,至于那个默认的按钮,几乎不可能被应用样式
考虑不同之处

这很难让任何人说这是个好的表单项设计,但是直到现在,这已经我们能做到的最好的了。
Also note Safari's fundamentally different approach. The Safari team has probably decided on this approach to disallow the manual entering of a file name and this avoid exploits like this one. The drawback is that the user can't decide not to upload a file after having selected one.
解决方法
幸运的,Michael McGrady发明了一种优雅的欺骗方法,让我们能或多或少的对上传控件使用样式。该页所写的解决方法是属于他的,我只是加了代码position: relative
、一些笔记、测试和完全使用JavaScript来完成。
没有经过处理的上传控件在你的浏览器里大体是下面的样子:
现在看来好了很多,是么?(前提是你的浏览器支持opacity
)
McGrady的方法优雅而且简单:
- 将一个上传控件
<input type="file">
放置到一个具有position: relative
样式的元素中。 - 同样,在该元素中放入一个应用又恰当样式的文本控件
<input>
和一张图片。将这些元素的Position样式设置为absolutely,这样他们可以出现在和上传控件<input type="file">
相同的位置。 - 设置
<input type="file">
的z-index
样式为2
,使它显示在文本控件和图片的上方。 - 最后,设置
<input type="file">
的opacity
样式为0
.现在,文件上传控件将不可见且文本控件和图片则可以看到,但是你仍然可以点击“浏览”按钮。如果上传控件的按钮在图片的正上方,那么用户看起来像是点击了图片并弹出了文件选择的窗口。
(注意:你不能使用visibility: hidden
,因为一个隐藏的元素是不可点击的,但我们希望<input type="file">
保留可点击的属性。
到这里,效果是通过纯CSS来完成的,但是还缺乏一个功能。
- 当用户选择了一个文件,正常情况下,文本表单应该像
<input type="file">
一样能显示文件的正确路径。我们可以通过使用JavaScrip来复制<input type="file">
的新值到文本控件.
因此该方法需要使用到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">
的Positiond样式为relative,这样我们可以在其里面创建一个position为absolute的层:用来放置伪造的文件上传控件元素。
<div class="fakefile">
这个层里包含了伪造的文本控件和按钮,其position样式为absolute,并且其z-index
为1,这样它将出现在文件上传控件的下面。
真实的文件上传控件同样也使用position: relative
,这样才能为其指派z-index
,毕竟它要出现在伪造控件的上面。接着我们设置他的opacity
值为0,使其就像不可见一样。
注意设置text-align: right
:Mozilla不接受文件上传控件的width
申明,我们必须确定“浏览”按钮在<div>
的右边。同时伪造的“Search”按钮的位置也在右边,正好咱真实按钮的下面。
你需要使用精细的CSS样式来设置所有的宽度、高度、边框等等,但是我没有将其包含在整个样例里面。
为什么使用JavaScript?
但是,我决定使用纯JavaScrip的解决方法。首先我们总需要JavaScript来将文件上传控件的文件路径值复制到文本框中。
其次,纯JavaScript解决方法可以消除无意义的HTML:<div class="fakefile">
。它使我的代码更简洁。
最后,比较老的浏览器不能处理CSS,在IE4和Netscape4中文件上传控件将是不可点击的。对那些使用no-CSS浏览器的用户来说,他们将看到两个文本输入控件并不能理解为什么要第二个。
下面是纯CSS的解决方法
一些浏览器截图更能说明这个问题。
问题 - Netscape 4
首先,Netscape 4中用户只能看到如下所示的那个按钮。整个可能是因为position: absolute
的原因,Netscape 4不能处理这个。
更坏的是用户不能点击那个按钮。大量的工作只使这个问题得到了部分解决。Netscape 4的用户必须能访问这个表单项。

问题 - IE 4
IE4中:一个不固定的阴影显示原始的“浏览”按钮,并且它是不可点击的。该解决方法在IE4中行不通。

问题 - Netscape 3
最后,使用Netscape 3或者其他non-CSS浏览器的用户,虽然文件上传控件是可访问的,但是用户可能会为看到额外的文本输入控件而感到困惑。

JavaScript解决方法
上面的解决方法是:使用JavaScript来添加伪造的文件上传控件。最坏的情况是脚本不能工作,这样用户将只能看到真实的<input type="file">
。虽然不能美化,但是仍然能访问上传控件。
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; } } }
说明
If the browser doesn't support the W3C DOM, don't do anything.
如果浏览器不支持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);
Then go through all inputs on the page and ignore the ones that aren't <input type="file">
.
遍历页面内所有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">
的元素的class属性不包含fileinputs
时则忽略它。
if (x[i].parentNode.className != 'fileinputs') continue;
现在我们对<input type="file">
进行优化。首先我们添加名为"hidden"的className。在这个新class中我添加了样式opacity 和 position。
x[i].className = 'file hidden';
克隆伪造的表单并且将其追加到<input type="file">
的父级节点中。
var clone = fakeFileUpload.cloneNode(true); x[i].parentNode.appendChild(clone);
现在我们成功的美化了<input type="file">
。但是并没有完全完成,我们需要让用户能看到他们想上传的文件名称和路径。
首先我们为<input type="file">
创建新的属性指向伪造的文本控件:
x[i].relatedElement = clone.getElementsByTagName('input')[0];
使用它,当用户改变真实<input type="file">
的文件时,很容易访问伪造的项。
现在有一个问题:我们使用哪个事件?正常我们将使用控件的change
事件:如果其值发生改变,文本跟着发生改变
可惜Mozilla 1.6不支持上传控件的change
事件(Firefox 0.9.3支持)。因为Mozilla的缘故我使用mouseout
事件,该事件只在用户选择文件之后发生(IE中支持,但是在Safari中不受支持)。
x[i].onchange = x[i].onmouseout = function () { this.relatedElement.value = this.value; }
问题和扩展
一个问题仍然存在:用户不能选择不上传文件。
假设用户选择了一个文件,过一会后又决定不上传了,在默认的<input type="file">
中可以移除里面的路径来不上传,但是在我们的例子中,这将变得非常困难。试一下,可以完成但是绝对是违反视觉的。
因此我们可能让用户能选择和修改伪造的上传控件的值并且将修改应用到真实的上传控件。
Allowing selection is somewhat possible. When the user selects any part of the real file upload, we select the entire value of the fake file upload.
允许选择有点可能,当用户选择部分真实上传控件的值时,我们选择整个伪造控件的值。
x[i].onselect = function () { this.relatedElement.select(); }
不幸运的,JavaScript安全限制了不允许我们改变<input type="file">
的值。因此我们不能让用户手工修改伪造控件的值。因而我决定不考虑onselect
事件。
一个可能的解决方法是给伪造控件添加一个“清除”按钮,按钮上绑定如下事件:删除原来对应的<input type="file">
并且重新创建一个新的。虽然有点麻烦,但是我们可以删除我们不需要上传的文件。我没有写那部分脚本并且我也不知道它是否能正常工作。
XeonWell注:不知道是否可以通过在提交表单时,对对应的文件上传控件和其伪造的控件的值进行比较,如果不想的的话,再执行作者所说的方法(删然后建,或者将该上传控件属性disable设置为true),这个笔者目前还没又做个测试,又测试的朋友希望能告知笔者。
发送click事件
一个读者提议移除所有复杂的CSS样式,完全隐藏上传控件,然后通过发送所有在伪造控件上的click事件到真实的控件上。一个不错的主意,并且比上面介绍的简单。
fakeField.onclick = function () { realField.click() }
click()
方法允许模拟一个点击到表单控件上。复选框选中、单选选中等等。但是不幸运的是Mozilla和Opera不支持将此方法添加到上传控件上。我一直想不通为什么:添加这个不会又安全危险,因为它最坏也只能打开一个文件选择的弹出窗口。
So unfortunately we cannot use this simple solution.
因此我们不能使用该方法。
XeonWell注:www.box.net里面有用到该方法,大家可以注册之后看看它如何应用的样式,位置为登录之后选择upload即刻看到。 IE和Firefox的一些插件可以很方便的及时查看页面内元素的属性,具体介绍大家可以访问这篇文章ie与FireFox下扩展开发插件收集
本文版权属于原作者所有,本人翻译只为爱好和学习知识,翻译的比较烂,建议有兴趣的朋友直接看原文。如有其他问题,可以与我联系(XeonWell#591wap.cn).replace("#","@")