В данной статье предлагаю рассмотреть стилизацию поля ввода для загрузки файлов. Данная тема не раз рассматривалась на Хабре, но, тем не менее, адекватного решения до сих пор не было предложено.

Итак, начнем. Вначале рассмотрим общую идею. Основным камнем предктовения при стилизации данного элемента является определение загружен файл или нет, чтобы менять текст и стили поля в зависимости от загрузки.

На первый взгляд, здесь трудно обойтись без Javascript, однако если внимательно исследовать элемент, то можно обратить внимание на надпись, которая выставляется по умолчанию, когда файл не загружен. Возможно, это многих вводит в заблуждение при решении данной задачи, однако данная надпись на самом деле ничего не значит, то есть пользователь ее видит, но несмотря на это в понимании браузера поле все равно остается пустым. Таким образом, определить является файл выбран или нет проще простого — использовать атрибут required и прилагающийся к нему псевдокласс :invalid.

Теперь подробно разберем стилизацию для основных браузеров. Начнем с самого простого случая — Webkit. Здесь стилизация вообще превращается в сказку, так как браузера на основе webkit для поля input с атрибутом file поддерживают псевдоэлементы before и after и поэтому всю верстку можно сделать всего одним тегом. Стоит также отметить, что для вебкит-браузеров поддерживается вендорный псевдоэлемент ::-webkit-file-upload-button для стилизации кнопки 'Выберите файл', который мы благополучно убьем, так как при самой детальной кастомизации элемента толку от него не очень много, потому что в нем нельзя менять текст.

Кнопку для загрузки мы будем имитировать с помощью псевдоэлемента after, а само поле с помощью псевдоэлемента before. Ну а дальше дело техники. В нашем примере в зависимости от того загружен файл или нет меняется цвет рамки, текст в кнопке и поле для ввода. Нативную кнопку мы скрываем (::-webkit-file-upload-button) с помощью visibility: hidden, на ее место ставим псевдоэлемент after, а псевдоэлемент before растягиваем на всю оставшуюся ширину, этот псевдоэлемент исчезает, если файл загружен.

<input type="file" required>


input {position: relative; width: 100%; height: 36px; padding-top: 14px; border: 2px solid #ccc; outline: 0; color: blue; background: yellow;}
input:invalid {border: 2px solid red;}
input::-webkit-file-upload-button {visibility: hidden; width: 160px;}
input::before, input::after {position: absolute;}
input::after {content: 'Загрузите еще'; left: 0; top: 0; background: #ccc; height: 50px; line-height: 50px; width: 150px; text-align: center; color: magenta;}
input:invalid::after {content: 'Загрузите файл';}
input::before {display: none;}
input:invalid::before {content: 'Файл не загружен'; display: inline-block; background: yellow; left: 150px; top: 0; height: 50px; line-height: 50px; width: calc(100% - 160px); padding-left: 10px; color: blue;}

Рассмотрим вариант, когда кнопка находится справа. Здесь мы просто нативной кнопке (::-webkit-file-upload-button) задаем нулевую ширину, оставляя ее спрятанной с помощью visibility: hidden, а фиктивную кнопку (псевдоэлемент after) просто прижимаем к правому краю.

input {position: relative; width: 100%; height: 36px; padding-top: 14px; border: 2px solid #ccc; outline: 0; color: blue; background: yellow;}
input:invalid {border: 2px solid red;}
input::-webkit-file-upload-button {visibility: hidden; width: 0;}
input::before, input::after {position: absolute;}
input::after {content: 'Загрузите еще'; right: 0; top: 0; background: #ccc; height: 50px; line-height: 50px; width: 150px; text-align: center; color: magenta;}
input:invalid::after {content: 'Загрузите файл';}
input::before {display: none;}
input:invalid::before {content: 'Файл не загружен'; display: inline-block; background: yellow; left: 10px; top: 0; height: 50px; line-height: 50px; width: calc(100% - 160px); padding-left: 10px; color: blue;}

Теперь перейдем к Firefox. Этот браузер не поддерживает псевдоэлементы к инпутам вообще, поэтому здесь придется вместо псевдоэлементов использовать две метки, которые ссылаются на поле для загрузки файла.

Рассмотрим вариант с кнопкой слева. Сделаем врапер, цвет которого будет соответствовать цвету поля. Здесь мы закроем нативную кнопку фаерфокса одной меткой. А с помощью второй метки будем имитировать поле, в которое можно засунуть любой текст. Это фиктивное поле будет исчезать после того, как файл будет выбран. По этой причине необходимый цвет поля будет виден тот, который указан во врапере.

<div class="wrap">
  <input type="file" required id="f">
  <label for="f" class="button"></label>
  <label for="f" class="field">‘айл не загружен</label>
</div>


.wrap {position: relative; background: yellow;}
input {width: calc(100% - 70px); height: 50px; padding-left: 70px; border: 2px solid #ccc; color: blue; font: 16px/50px Arial;}
input:invalid {border: 2px solid red; padding-left: 70px; width: calc(100% - 70px);}
label {position: absolute;}
.button {left: 2px; top: 2px; width: 150px; height: 50px; font: 16px/50px Arial; text-align: center; background: #ccc; color: magenta;}
.field {display: none;}
.button::after {content: 'Загрузите еще';}
input:invalid ~ .button::after {content: 'Загрузите файл';}
input:invalid ~ .field {display: inline-block; width: calc(100% - 160px); right: 0; top: 2px; height: 50px; padding-left: 10px; background: yellow; color: blue; font: 16px/50px Arial;}

Рассмотрим вариант фаерфокса, где кнопка расположена справа. Здесь чуть сложнее, но тем не менее задача решаема. Так как нативная кнопка в фаервоксе расположена слева, то мы все поле подгоним под врапер на ширину кнопки, для этого враперу зададим overflow: hidden. Дальше как и в предыдущих примерах двигаем кнопку вправо, которой является label, а второй label используем в качестве поля. В общем разберетесь.

<div class="wrap">
	<input type="file" required id="f">
	<label for="f" class="button"></label>
	<label for="f" class="field">Файл не загружен</label>
	<span class="left-border"></span>
</div>


.wrap {position: relative; background: yellow; overflow: hidden;}
input {width: 100%; height: 50px; border: 2px solid #ccc; color: blue; font: 16px/50px Arial; margin-left: -82px;}
input:invalid {border: 2px solid red; padding-left: 150px; width: calc(100% - 150px);}
label {position: absolute;}
.button {z-index: 1; right: 0; top: 0; width: 150px; height: 54px; font: 16px/50px Arial; text-align: center; background: #ccc; color: magenta; border-top: 2px solid #ccc; border-bottom: 2px solid #ccc; border-right: 2px solid #ccc;}
.field {display: none;}
.button::after {content: 'Загрузите еще';}
input:invalid ~ .button {border-top: 2px solid red; border-bottom: 2px solid red; border-right: 2px solid red; height: 50px; top: 0;}
input:invalid ~ .button::after {content: 'Загрузите файл';}
input:invalid ~ .field {display: inline-block; width: calc(100% - 150px); left: 2px; top: 2px; height: 50px; padding-left: 5px; background: yellow; color: blue; font: 16px/50px Arial;}
.left-border {width: 2px; height: 50px; position: absolute; top: 2px; left: 0; background: #ccc;}
input:invalid ~ .left-border {background: red;}

Отдельно стоит сказать несколько слов про Internet Explorer. В нем данный способ поддерживается частично, так как поле для ввода там прописано очень жестко, точнее само поле стилизуется, но строка с текстом внутри поля стилизуется на крестах в самом браузере, поэтому ключи к нему я подобрать не смог (во всяком случае пока), возможно это и к лучшему. Плюс ко всему после загрузки файла в эксплорере фиктивное поле так и остается висеть пока на него не наведешь мышкой (во всяком случае у меня так получается на win7 ie11). Стоит также отметить, что в эксплорере есть нативный севдоэлемент для работы с данным элементом ::-ms-browse, предназначенный для стилизации кнопки для загрузки (ie10+).

Подводя итоги можно выделить достоинства и недостатки данного решения.
Достоинства:
— полноценная стилизация элемента загрузки файлов на чистом CSS
— на Webkit (который должен стать стандартом) стилизация делается одним файлом
Недостатки:
— не поддерживается IE
Пример можно посмотреть и скачать здесь.

В дополнение к статье хотелось бы сказать, что данный способ пока что лучше применять только для вебкит-браузеров (по вышеприведенной ссылке примеры обновлены), а для остальных браузеров пользоваться методами с яваскриптом.

Комментарии (13)


  1. jMas
    16.04.2015 16:07
    +3

    Если пробовали такой вариант —

    <label>Upload file text clickable - <input type="file" /></label>
    

    подскажите, в каких браузерах работает / не работает? Спасибо.


    1. grifangel Автор
      16.04.2015 16:38
      -7

      Такой вариант не пробовал так как для данного примера данная конструкция тегов неуместна. Согласно статье label должен идти после input-а чтобы меняться в зависимости от input:invalid. Я рассматривал другие варианты стилизации (при другой очередности и вложенности тегов), но там ситуация осложняется тем, что при внешнем label его нельзя стилизовать в зависимости от логики input:invalid.


      1. SelenIT2
        16.04.2015 17:04

        А так —

        <label>Upload file text clickable - <input type="file" /> <span></span></label>
        

        и менять span?


        1. grifangel Автор
          16.04.2015 17:16

          Такой вариант тоже не пробовал, при таком варианте текст, который идет перед input не поменяется в зависимости от того выбран файл или нет, в данном случае можно конечно работать с самим span, но я не вижу особой разницы, в любом случае на поддержке браузерами это не отразится (также как и в предыдущем посте Upload file text clickable — />), в ИЕ поле ввода очень жестко прошито и его стили у меня убить никак не получилось.


          1. jMas
            16.04.2015 18:32

            Разница есть:
            — не нужно на каждое поле придумывать ID
            — поле можно вообще унести абсолютным позицированием за пределы слоя, что сделает его невидимым


            1. grifangel Автор
              16.04.2015 21:48

              А зачем придумывать ID? Может я неправильно понял Вашу мысль? Поле в любом случае придется делать видимым хотя бы после выбора файла, так как путь к файлу прописывается только в поле.


              1. jMas
                16.04.2015 23:23

                Как насчет? jsbin.com/fehadi
                Просто если делать label с for, тогда каждый input должен иметь ID на который ссылается for. Таким образом код становится менее гибче, то есть я не смогу уже просто взять и скопировать кусочек кода отвечающий за стилизованный input, мне прийдется менять ID, если полей на одной странице будет много.


                1. grifangel Автор
                  17.04.2015 01:03

                  Ваше предложение мне интересно. Спасибо. В любом случае если вернуться к Вашему первому вопросу, то поддержка браузеров по сравнению с моим изначальным вариантом не меняется (все кроме ИЕ).


                  1. jMas
                    17.04.2015 09:21

                    Спасибо за дополнительную информацию. Если выясните что то интересное по стилизации элементов — пишите статьи. У меня, например, есть эксперимент с дропдауном на основе checkbox-ов jsbin.com/sodiwi (реагирует на Tab и выбор можно осуществлять кнопками управления, но совершенно не подходит для мобильных устройств). Мне всегда интересно разбирать подобные решения.


                    1. grifangel Автор
                      17.04.2015 12:03

                      У меня много интересного по стилизации элементов форм, селект у меня тоже есть причем у меня получилось сделать только на CSS, правда работает только в хроме и фаере. Он есть по ссылке, которая приведена внизу этой статьи. Статья на эту тему у меня уже готова, только модерация не пропустила, я ее подправлю и еще раз попробую залить. И еще, возвращаясь к дискуссии, на один инпат можно вешать несколько label, это валидно, а стили на цсс легко копировать, там достаточно через запятую перед фигурными скобками перечислить элементы, для которых нужно применить стили, хотя я может неправильно Вас понял.


  1. xGromMx
    16.04.2015 18:13
    +7

    Цвета выедают мне глаза…


  1. piliff
    16.04.2015 19:31
    +7

    Результаты в картинках добавили бы наглядности


  1. romy4
    19.04.2015 09:16

    FF 36.0.1 linux вылазит кнопка [1] [2]