跨浏览器自定义文件上传按钮样式

117

我想将文件上传按钮样式设置为个人喜好,但是我找不到任何真正可靠的方法来实现这一点而不使用JS。我在此主题上发现了两个其他问题,但那里的答案要么涉及JavaScript,要么建议使用Quirksmode的方法

我的主要问题是使用Quirksmode的方法后,文件按钮仍将具有浏览器定义的尺寸,因此它不会自动调整为放置在其下方作为按钮使用的任何尺寸。我基于它编写了一些代码,但它只会占用文件按钮通常占用的空间,因此它不会像我希望的那样填充父div。

HTML:

<div class="myLabel">
    <input type="file"/>
    <span>My Label</span>
</div>

CSS:

.myLabel {
    position: relative;
}
.myLabel input {
    position: absolute;
    z-index: 2;
    opacity: 0;
    width: 100%;
    height: 100%;
}
This fiddle演示了这种方法是相当有缺陷的。在Chrome浏览器中,单击第二个演示按钮下面的!!仍会打开文件对话框,但在所有其他浏览器中,文件按钮没有占据正确的按钮区域。
有没有更可靠的方法来样式化文件上传按钮,不使用任何JavaScript,并且最好尽可能少使用“hacky”的代码(因为黑客通常会带来其他问题,例如在演示中出现的问题)?

使用quirsmode,但字体大小要大一些。请查看我的答案。 - regisbsb
1
Tympanus/codrops 提供了一份优秀的教程,介绍如何样式化文件输入框并保持其在键盘上可访问和可导航。 - Dan Dascalescu
7个回答

302

我发布这篇文章是因为(令我惊讶的是)没有其他地方推荐过这个方法。

有一种非常简单的方法可以做到这一点,而不限制您使用浏览器定义的输入维度。只需在隐藏的文件上传按钮周围使用 <label> 标签即可。这比通过 webkit 内置样式[1]允许的样式更自由。

标签标记是为将任何点击事件指向其子输入[2]而设计的,因此使用它,您不再需要使用 JavaScript 将点击事件指向输入按钮。您应该使用以下代码:

label.myLabel input[type="file"] {
    position:absolute;
    top: -1000px;
}

/***** Example custom styling *****/
.myLabel {
    border: 2px solid #AAA;
    border-radius: 4px;
    padding: 2px 5px;
    margin: 2px;
    background: #DDD;
    display: inline-block;
}
.myLabel:hover {
    background: #CCC;
}
.myLabel:active {
    background: #CCF;
}
.myLabel :invalid + span {
    color: #A44;
}
.myLabel :valid + span {
    color: #4A4;
}
<label class="myLabel">
    <input type="file" required/>
    <span>My Label</span>
</label>

我使用了固定位置来隐藏输入框,以便即使在旧版本的Internet Explorer中也能正常工作(模拟IE8无法在visibility:hiddendisplay:none文件输入上工作)。我已经在模拟的IE7及以上版本进行了测试,它完美地工作。


  1. 不幸的是,您不能在<label>标签内使用<button>,因此您必须自己定义按钮的样式。对我来说,这是这种方法唯一的缺点。
  2. 如果定义了for属性,则使用其值来触发与<label>for属性相同的ID的输入框。

8
太棒了!至于label元素内部的按钮,这是完全可以的。只要这些按钮不被其他标签标记或本身就是另一个标签,它们可以包含任何短语内容(包括inputbutton)。来源:https://html.spec.whatwg.org/multipage/forms.html#the-label-element - Derek Johnson
1
@DerekJohnson 是的,这是确实可能和允许的,但它不会产生期望的效果(至少不是在每个浏览器中)。例如:这个演示(至少在Chrome中)当你点击按钮时不会聚焦输入,但如果你点击纯文本则会聚焦。由于您想要标签打开文件弹出窗口,您需要使用除<button>之外的其他东西。 - Joeytje50
2
@regisbsb 我刚刚又在IE7和IE8中测试了这个jsfiddle,确实IE7似乎不再工作了,但是IE8完全正常。你遇到的无法运行的代码是什么?PS:目前只有0.08%的人使用IE7,所以我不会担心它。无论如何,IE8应该仍然可以正常工作,所以你能否给我发送一个在IE8中无法工作的代码的jsfiddle/jsbin等链接? - Joeytje50
3
这对我非常有用,唯一的问题是,在选择完文件后,它没有实际显示已选择文件。是否有任何方法可以显示已选择文件的指示器? - Dylan Vester
2
这就是在CSS中使用:valid:invalid代码所完成的工作。或者,您可以使用类似于这个的东西来更改文本。但是,您无法以这种方式精确显示所选文件。 - Joeytje50
显示剩余11条评论

14

请查看以下适用于所有浏览器的方法。基本上,我将输入放在图像顶部。我使用font-size使其变得非常大,这样用户就始终可以单击上传按钮。

.myFile {
  position: relative;
  overflow: hidden;
  float: left;
  clear: left;
}
.myFile input[type="file"] {
  display: block;
  position: absolute;
  top: 0;
  right: 0;
  opacity: 0;
  font-size: 100px;
  filter: alpha(opacity=0);
  cursor: pointer;
}
<label class="myFile">
  <img src="http://wscont1.apps.microsoft.com/winstore/1x/c37a9d99-6698-4339-acf3-c01daa75fb65/Icon.13385.png" alt="" />
  <input type="file" />
</label>


1
我必须说,这不是对原问题的回答,原问题明确提到了Quirksmode的这种方法,该方法使用与您的答案相同的基本原理。但是,如果您想使用它,我当然不会阻止您,但我建议根本不要使用<label>标签(而是使用<div>或其他东西),因为它对此情况没有任何帮助。 - Joeytje50
他抱怨说它不会调整大小,但是现在通过设置字体大小,它可以实现了。 引用:“我对Quirksmode的方法主要的问题是文件按钮仍然会保持浏览器定义的尺寸,所以它不会自动调整到放置在其下方的任何按钮所使用的尺寸。我根据它编写了一些代码,但它只会占用文件按钮通常占用的空间,所以它无法像我想要的那样完全填充父级div。” - regisbsb
1
这种使用大字体的解决方案有一个缺点,就是如果您在文件上传按钮附近有可点击的元素,您无法单击它们,因为可点击区域覆盖了下面的元素... 对我来说不是一个解决方案! - basZero
i) 为什么同时使用 opacity:0filter: alpha(opacity=0)?似乎只有 opacity:0 就足以使文件元素不可见。ii) 其次,使用 font-size:1px 而不是 100px 怎么样?标签内的 img 实际上充当了输入元素(因此可点击区域将由其决定),那么将输入元素变得非常小且不可见怎么样? - Hassan Baig
这是为IE7和IE8设计的。 - regisbsb

9
最好的例子就是这个,没有隐藏,没有使用jQuery,完全是纯CSS。 http://css-tricks.com/snippets/css/custom-file-input-styling-webkitblink/

.custom-file-input::-webkit-file-upload-button {
    visibility: hidden;
}

.custom-file-input::before {
    content: 'Select some files';
    display: inline-block;
    background: -webkit-linear-gradient(top, #f9f9f9, #e3e3e3);
    border: 1px solid #999;
    border-radius: 3px;
    padding: 5px 8px;
    outline: none;
    white-space: nowrap;
    -webkit-user-select: none;
    cursor: pointer;
    text-shadow: 1px 1px #fff;
    font-weight: 700;
    font-size: 10pt;
}

.custom-file-input:hover::before {
    border-color: black;
}

.custom-file-input:active::before {
    background: -webkit-linear-gradient(top, #e3e3e3, #f9f9f9);
}
<input type="file" class="custom-file-input">


也是一个非常好的解决方案。我唯一能看到的问题是,从技术上讲,输入元素没有 ::before::after 伪元素,因为它们是空元素(没有内容)。有关更多信息,请参见 此 SO 问题 - Joeytje50
2
这会导致IE和Chrome表现完全不同。难道不是想让它与浏览器无关吗? - kaybee99
6
它在Firefox上根本不起作用,因为Firefox基于Gecko而非webkit。 - Cullub

2

这似乎很好地解决了问题。 这里有一个演示:

HTML

<label for="upload-file">A proper input label</label>

<div class="upload-button">

    <div class="upload-cover">
         Upload text or whatevers
    </div>

    <!-- this is later in the source so it'll be "on top" -->
    <input name="upload-file" type="file" />

</div> <!-- .upload-button -->

CSS

/* first things first - get your box-model straight*/
*, *:before, *:after {
    -moz-box-sizing: border-box;
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
}

label {
    /* just positioning */
    float: left; 
    margin-bottom: .5em;
}

.upload-button {
    /* key */
    position: relative;
    overflow: hidden;

    /* just positioning */
    float: left; 
    clear: left;
}

.upload-cover { 
    /* basically just style this however you want - the overlaying file upload should spread out and fill whatever you turn this into */
    background-color: gray;
    text-align: center;
    padding: .5em 1em;
    border-radius: 2em;
    border: 5px solid rgba(0,0,0,.1);

    cursor: pointer;
}

.upload-button input[type="file"] {
    display: block;
    position: absolute;
    top: 0; left: 0;
    margin-left: -75px; /* gets that button with no-pointer-cursor off to the left and out of the way */
    width: 200%; /* over compensates for the above - I would use calc or sass math if not here*/
    height: 100%;
    opacity: .2; /* left this here so you could see. Make it 0 */
    cursor: pointer;
    border: 1px solid red;
}

.upload-button:hover .upload-cover {
    background-color: #f06;
}

在这里再玩了一下:http://codepen.io/sheriffderek/pen/JqlDB - sheriffderek
不错的想法。我现在在我的项目中不再支持IE 8或以下版本。但是对于某些特定客户端的东西还是有好处的。label解决方案似乎是更好的选择。我以前使用的是display:none;然后让标签与一些单选按钮成为inline-blockblock。看起来很棒 - 我的解决方案并不是超级微小的,但 - 实际上 - 您会有多少个上传按钮等等... 我想你可以在用户重置时结合这2种方法,并覆盖所有可能的情况。 - sheriffderek
2
http://codepen.io/sheriffderek/pen/177e86a195b98a2058921e4ef859cd73 - sheriffderek
@sherriffderek 噢,我也非常喜欢使用无线电进行交互。我甚至基于此制作了一个完整的MediaWiki扩展程序。不幸的是,在Android浏览器上它的更新效果并不好,所以你需要为此构建一个备用方案(如果你想支持额外的四分之一移动用户)。 - Joeytje50
1
@regisbsb - 它也不能在Atari或NES上运行。 - sheriffderek
显示剩余2条评论

1
任何一种简单的覆盖所有文件输入的方法就是只需样式化你的 input[type=button],并将其全局应用以将文件输入转换为按钮。
$(document).ready(function() {
    $("input[type=file]").each(function () {
        var thisInput$ = $(this);
        var newElement = $("<input type='button' value='Choose File' />");
        newElement.click(function() {
            thisInput$.click();
        });
        thisInput$.after(newElement);
        thisInput$.hide();
    });
});

这是我从http://cssdeck.com/labs/beautiful-flat-buttons获取的一些示例按钮CSS:

input[type=button] {
  position: relative;
  vertical-align: top;
  width: 100%;
  height: 60px;
  padding: 0;
  font-size: 22px;
  color:white;
  text-align: center;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
  background: #454545;
  border: 0;
  border-bottom: 2px solid #2f2e2e;
  cursor: pointer;
  -webkit-box-shadow: inset 0 -2px #2f2e2e;
  box-shadow: inset 0 -2px #2f2e2e;
}
input[type=button]:active {
  top: 1px;
  outline: none;
  -webkit-box-shadow: none;
  box-shadow: none;
}

0

我刚遇到这个问题,并为那些使用Angular的人编写了一个解决方案。您可以编写一个自定义指令,由一个容器、一个按钮和一个类型为文件的输入元素组成。然后使用CSS将输入框放在自定义按钮上,但透明度为0。您将容器的高度和宽度设置为按钮的偏移宽度和高度,将输入框的高度和宽度设置为容器的100%。

这个指令

angular.module('myCoolApp')
  .directive('fileButton', function () {
    return {
      templateUrl: 'components/directives/fileButton/fileButton.html',
      restrict: 'E',
      link: function (scope, element, attributes) {

        var container = angular.element('.file-upload-container');
        var button = angular.element('.file-upload-button');

        container.css({
            position: 'relative',
            overflow: 'hidden',
            width: button.offsetWidth,
            height: button.offsetHeight
        })

      }

    };
  });

如果你正在使用Jade,那么这是一个Jade模板。
div(class="file-upload-container") 
    button(class="file-upload-button") +
    input#file-upload(class="file-upload-input", type='file', onchange="doSomethingWhenFileIsSelected()")  

如果你在使用HTML,则是相同的HTML模板

<div class="file-upload-container">
   <button class="file-upload-button"></button>
   <input class="file-upload-input" id="file-upload" type="file" onchange="doSomethingWhenFileIsSelected()" /> 
</div>

CSS

.file-upload-button {
    margin-top: 40px;
    padding: 30px;
    border: 1px solid black;
    height: 100px;
    width: 100px;
    background: transparent;
    font-size: 66px;
    padding-top: 0px;
    border-radius: 5px;
    border: 2px solid rgb(255, 228, 0); 
    color: rgb(255, 228, 0);
}

.file-upload-input {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 2;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}

-2

如果你正在使用Bootstrap和LESS,那么样式化标签也很容易:

label {
    .btn();
    .btn-primary();

    > input[type="file"] {
        display: none;
    }
}

display: none 会将输入框从选项卡顺序中移除。使用 <label>(如 Tympanus 所示)是语义化的,但需要一些改进。 - Dan Dascalescu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接