Cтилизация файл-инпутов

Cтилизация файл-инпутов

Привет. Сегодня я хочу вам рассказать о том, как можно изменить внешний вид файлового инпута, как стилизовать файл-инпут под свой дизайн, как стилизовать <input type="file">.

Хватит ключевых слов =). Суть я думаю вы поняли.

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

Как добиться того внешнего вида файл-инпута, который вам нужен и расскажет эта статья.

Для начала давайте посмотрим на то, как отображаются файл-инпуты без применения каких-либо стилей в разных браузерах.

Пустые файл-инпуты

Как видим, во всех браузерах кроме сафари имеется текстовое поле, а справа от него кнопка. Расскажу о различиях. В Опере и IE мы можем написать адрес файла вручную (сомневаюсь, что кто-либо когда-либо пользовался этой возможностью). Файерфокс, несмотря на то, что у него есть текстовое поле, не дает нам вписать туда адрес файла. Вместо этого появляется окно выбора файла как при нажатии на кнопку «Обзор».
Давайте теперь выберем в этих полях какой-либо файл.

заполненные файл-инпуты

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

Но cначала ознакомимся с проблематикой.
1. Средствами JS мы не можем сымитировать клик на такой инпут. Вот что говорится об этом в святом писании спецификации DOM:

click
Simulate a mouse-click. For INPUT elements whose type attribute has one of the following values: “button”, “checkbox”, “radio”, “reset”, or “submit”.
No Parameters
No Return Value
No Exceptions

То есть методом click мы можем сымитировать клик почти на всех типах инпутов, но не на файл-инпуте. Это сделано чтобы обезопасить компьютер клиента: в противном случае хозяин сайта мог бы без проблем получать любые файлы с компьютера клиента. Хотя с другой стороны, по клику вызывается только окно выбора файла. Так или иначе, в девелопер-центре файерфокса этот факт обозначен как баг.

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

Основная трудность в следующей проблеме.

2. Мы не можем свободно влиять на размеры кнопок «обзор», чтобы подогнать инпут под размер перекрываемой картинки. В файерфоксе мы вообще не можем изменить внешний вид файл-инпута средствами css (кроме высоты). То есть задача заключается определении оптимального размера перекрываемой картинки, чтобы минимальное количество пикселей было некликабельно, а пустые области не реагировали на клик.

Посмотрим на кликабельные области и их размеры в разных браузерах.

Рабочие области в разных браузерах

Все что внутри голубой обводки - кликабельная область. Замечу, что высоту кнопок мы можем увеличить, а вот ширину — нет.


Проблемы ясны. Переходим к делу.

Для начала нарисуем кнопку для выбора файла. Вот какая получилась у меня.

В обычном состоянии:

при наведении:

Теперь для разных типов файлов подготовим иконки и объединим их в один файл.

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

Итак, сверстаем шаблон инпута:

HTML:
<div class="blocker"> <!--блок, перекрывающий поле ввода слева от кнопки-->
<input type="file" id="File1" class="customFile" />
<div id="BrowseButton" title="Выбрать файл" class="fakeButton">
<div id="FileName"> <!--сюда мы будем вставлять имя файла и иконку-->

CSS:
<style type="text/css">
#File1 {
position: absolute;
}
.customFile {
width: 219px;
margin-left: -140px;
cursor: default;
height: 21px;
z-index: 2;
filter: alpha(opacity: 0);
opacity: 0;
}
.fakeButton {
position: absolute;
z-index: 1;
width: 85px;
height: 21px;
background: url(images/button.jpg) no-repeat left top;
float: left;
}

.blocker {
position: absolute;
z-index: 3;
width: 150px;
height: 21px;
background: url(images/transparent.gif);
margin-left: -155px;
}
#FileName {
position: absolute;
height: 15px;
margin-left: 90px;
font-family: Verdana;
font-size: 8pt;
color: Gray;
margin-top: 2px;
padding-top: 1px;
padding-left: 19px;
}
#activeBrowseButton {
background: url(images/button_active.jpg) no-repeat left top;
display: none;
}
</style>

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

Обведенное красным — blocker, файл-инпут показан с половинной прозрачностью.


Теперь при выборе файла (событие onchange) мы должны вырезать название файла из value нашего файл-инпута, определить тип файла по расширению и сместить фон, чтобы показывалась нужная иконка. Обратите внимание, что в разных операционных системах путь к файлу может быть через прямой слэш (в Mac OS, например), или через обратный (Виндуз), поэтому чтобы вырезание названия файла работало везде необходимо два регулярных выражения.

function HandleChanges() {
file = fileInput.value;
reWin = /.*(.*)/;
var fileTitle = file.replace(reWin, "$1"); //выдираем название файла для windows
reUnix = /.*/(.*)/;
fileTitle = fileTitle.replace(reUnix, "$1"); //выдираем название файла для unix-систем
fileName.innerHTML = fileTitle;

var RegExExt =/.*.(.*)/;
var ext = fileTitle.replace(RegExExt, "$1");//и его расширение

var pos;
if (ext) {
switch (ext.toLowerCase()) {
case 'doc': pos = '0'; break;
case 'bmp': pos = '16'; break;
case 'jpg': pos = '32'; break;
case 'jpeg': pos = '32'; break;
case 'png': pos = '48'; break;
case 'gif': pos = '64'; break;
case 'psd': pos = '80'; break;
case 'mp3': pos = '96'; break;
case 'wav': pos = '96'; break;
case 'ogg': pos = '96'; break;
case 'avi': pos = '112'; break;
case 'wmv': pos = '112'; break;
case 'flv': pos = '112'; break;
case 'pdf': pos = '128'; break;
case 'exe': pos = '144'; break;
case 'txt': pos = '160'; break;
default: pos = '176'; break;
};
fileName.style.display = 'block';
fileName.style.background = 'url(images/icons.png) no-repeat 0 -'+pos+'px';
};

};
function MakeActive() {
activeButton.style.display = 'block';
};
function UnMakeActive() {
activeButton.style.display = 'none';
};

Последние две функции MakeActive и UnMakeActive добавляют hover эффект к нашей кнопочке.

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

На странице у нас останется только два элемента:

<div id="wrapper">
<input id="File1" type="file"/>
</div>

Если у пользователя будет отключен JavaScript, он увидит обычный файл-инпут. В противном случае произойдет следующее:

 window.onload = WindowOnLoad;
var fileInput = document.getElementById('File1');
var fileName = document.createElement('div');
fileName.style.display = 'none';
fileName.style.background = 'url(images/icons.png)';
var activeButton = document.createElement('div');
var bb = document.createElement('div');
var bl = document.createElement('div');
function WindowOnLoad() {
var wrap = document.getElementById('wrapper');
fileName.setAttribute('id','FileName');
activeButton.setAttribute('id','activeBrowseButton');
fileInput.value = '';
fileInput.onchange = HandleChanges;
fileInput.onmouseover = MakeActive;
fileInput.onmouseout = UnMakeActive;
fileInput.className = 'customFile';
bl.className = 'blocker';
bb.className = 'fakeButton';
activeButton.className = 'fakeButton';
wrap.appendChild(bb);
wrap.appendChild(bl);

wrap.appendChild(activeButton);

wrap.appendChild(fileName);
};

 

Теперь посмотрим как это выглядит.

В следующих пятничных сниппетах будет скрипт, позволяющий добавлять несколько файл-инпутов.

Если пожелают читатели, то в добавок к скрипту мы сделаем специализированный контрол для asp.net программеров, позволяющий легко добавлять такие стилизованные инпуты на страницу.

Расскажите друзьям

Оцените статью:
  • 1
  • 2
  • 3
  • 4
  • 5

Комментарии — 62

cmepthuk
Отличная работа! Спасибо!
Mikhail
Решение интересное и часто нужное. Но вот изначально скрывать поле ввода я бы не стал. Всё-таки для win- и *nix-пользователей такой вариант непривычен и сбивает с толку.
#
Mikhail
adw0rd
Очень интересно, спасибо за статью!
#
adw0rd
sergy
Отличная статья, спасибо
#
sergy
Impala
хм... интересно, надо будет применить на практике :)
#
Impala
Monte
Замечательная, полезная и приятно оформленная статья. Спасибо огромное!
#
Monte
Сергей
Спасибо, реализовали в одном из наших проектов! :)
drw
это 5!
#
drw
dmitry39
отличная статья, причем давно на эту тему не встречал чего-либо столь грамотного. Постараюсь ASP.NET control сделать :)
Andron
Спасибо, думаю пригодится!
#
Andron
Жека
ага. Пожалуйста. К пятнице улучшу код и упрощу процесс интеграции на страницы.
porcelanosa
изменять надо конечно все инпуты такого типа на сайте - если уж заниматься этим. название файла я бы помещала слева и обводила бы рамочкой - имитируя поле ввода. - Надо бы добавить возможость изменять текст кнопки - т.е. - не всегда файл надо находить. Может иногда его надо просто выбрать
Жека
Эта статья и расписана для того, чтобы было понятно как это можно сделать. Расположение файл-инпута, названия файла и всего прочего можно задать вручную. А вот для того, чтобы изменять текст кнопки, надо будет наверх выводить не рисунок, а кнопку со своим текстом.
Александр
К сожалению, если у пользователя отключено отображение графики, то весь этот труд насмарку - пользователь вообще ничего не увидит.
#
Александр
Жека
Опять таки, можно выводить не рисунок, а кнопку. Тогда ее можно будет всегда увидеть. А еще можно сначала кнопку, а наверх наложить нужные изображения.
Ренат
Клёво! Пригодится :)
#
Ренат
cr_
а почему в ff3 правая половина кнопки не активна?
#
cr_
Жека
Я проверял на разных машинах и разных операционных системах: в Firefox 3 (Mac, Win), Firefox 2 (Mac, Win), Opera 9.25, Opera 9.51, IE 6, IE 7, Safari (Mac, Win) неактивен только самый край кнопки, вся остальная часть активна.
Piom
Недавно столкнулся с аналогичной задачей, но там была еще проблема стилизовать рамку вокруг blocker, решил очень коряво, ваши статьи мне подсказали как сделать правильно. Большое спасибо за статью.
#
Piom
Guf
классно, одна из немногих нормальных статей по теме стилей.:))
#
Guf
Максим
Большое спасибо, использую наряду с такими полезными вещами от автора как NicEdit, иконки документов и многими другими интересными решениями! Респект!
#
Максим
Алексей
наглядно и красиво
Амаль
респект великолепная и очень нужная статья
Jabe
отличная статья ..завтра же попробую реализовать в проекте:)..а то стандартное поле загрузки вообще не нравится
#
Jabe
Миша Татхагата
Женя, а почему бы не отрыть народу страшную тайну: существует, по крайней мере, пара плагинов к JQuery, которые позволяют сделать все это действо немного проще. Надеюсь, ты не заподозришь меня в том, что я не слишком хорошо знаком с объектной моделью документа. Но, как человек продающий за возобновляемые деньги невосполнимое время я все чащи и чаще думаю о том, что стодвенадцатьтысячтристатридцатьчетвертое изобретение велосипеда, возможно, не является самой удачной идеей в наше нелегкое время )))
#
Миша Татхагата
Жека
Ну вот. Взял и все рассказал :(
Миша Татхагата
Браво, Женя, адекватно реагируешь на комплименты )))
#
Миша Татхагата
Сергей DS
Если пожелают читатели, то в добавок к скрипту мы сделаем специализированный контрол для asp.net программеров, позволяющий легко добавлять такие стилизованные инпуты на страницу.
Если такая возможность есть, было бы СУПЕР!
#
Сергей DS
Евгений
Ок. До пятницы сделаю.
Сергей
Друзья, у меня он не работает, все конечно красиво но файлы на сервер не загружаются
#
Сергей
Евгений
Они и не должны загружаться на сервер. Я рассматривал здесь только клиентскую часть.
Stefana
Stefana http://freelance4all.ru/ Мне очень полезна эта статья оказалась. Давно задавался целью сделать что-либо подобное. Ты давно всем этим увлекаешься??
Evgenij
Стилизацей файл-инпутов? Да, давольно давно. В детстве меня больше склоняло, как и всех нас, молодых и незрелых, к стилизации чекбоксов.
Но потом я одумался и стал увлекаться вышиванием крестиком и стилизацией файл-инпутов.
Константин
Спасибо! Просто супер реализация. Можно Ваш пример использовать у себя на сайте?
Adward
Спасибо =)
LamerStudio
А как можно сделать 2 кнопки выбора на одной странице? Например, нужно указать, что нужно выгрузить фотографию и резюме. Как со строны css - понятно, две разные кнопки, в разных состояних, для них разные стили. Но, видимо что-то надо менять js. И курсор, наверное, надо менять со стандартного.
#
LamerStudio
Евгений
ЛамерСтудио, после этой статьи вышло еще несколько про стилзацию файл-инпутов.
Последняя: обзор файловых загрузчиков.
Рекомендую AjaxUpload.

А насчет курсора, то его стиль невозможно изменить в FF.
LamerStudio
Спасибо, Женя! Но мне понравился именно этот вариант. Все устраивает, но два на странице не становятся, а надо два именно таких на одной странице.
#
LamerStudio
LamerStudio
cursor: pointer исправляет ситуацию с курсором, но не до конца, мешает опять же js? который делает псевдо :hover.
#
LamerStudio
LamerStudio
Евгений, никак не решим эту проблему?
#
LamerStudio
dot
заметил небольшой косяк: если сначала выбрать какой-нибудь файл, а при следующей попытке выбора нажать кнопку Отмена, то в поле с именем файла сохранится иконка соответствующая типу предыдущего выбранного файла.
решение: добавить к условию выбора иконки if(ext){...} условие else fileName.style.display = 'none';
#
dot
Евгений
Бармалей, браво :)
Я такого не видел, и сам не додумался.
Саша
а как можно запустить функцию на javascript сразу после того как будет выбран файла?
Денисов Вячеслав
var RegExExt =/.*.(.*)/; //здесь ошибка
var RegExExt =/.*.(.*)/; //так правильней (косая перед точкой)
Денисов Вячеслав
>>а как можно запустить функцию на javascript сразу после того как будет выбран файла?



function select_ext() {
здесь код функции
}


p.s. к предыдущему моему комментарию - перед второй точкой нужно ставить обратный слэш, чтобы функция работала
Денисов Вячеслав
господи. администрация, разрешите в комментариях html-теги (htmlspecialchars() которые) а то бардак получается.

to Саша:
input type="file" onchange="функция()"
Евген
Неее, статейка весьма сомнительной недобности. Все это, конечно, фишки, но никакой реальной выгоды не имеют.
#
Евген
LoveIs
пришлось сначала повозиться, но потом все заработало так как надо, спасибо :)
Voya
Фала ти многу пријателе, си ме спасил :D
#
Voya
Kostya
Кажется с cursor:pointer; большой косяк. Везде прописываю, но он не работает в наглую. Причем у вас видимо такая же проблема была и вы просто её не упомянули в статье.
Если есть какое либо решение, прокоментируйте плиз...
#
Kostya
Evgenij
Нету решения. cursor:pointer не работает в FF насколько я помню.
britva
Замечательная статья! Большое спасибо!
Если кому интересно, оформил все что тут написано в виде плагина jQuery. Посмотреть можно здесь http://savvateev.org/blog/38/
Eugene
>>А я утверждаю, что ширину тоже можно изменить.
А можно где-то посмотреть, как это сделать? Ширина-то уменьшается, но при клике справа от кнопки все равно срабатывает инпут.
#
Eugene
Ара
Отличное решение!
#
Ара
Koko
A где cursor: pointer; ?!
#
Koko
Evgenij
Насчет курсора уже отвечал выше:

cursor:pointer не работает в FF
Ростислав
Можно вообще без JS обойтись:
"Инпуту" задаём большой размер шрифта, тем самым увеличивая его ширину до необходимой, и прозрачность. "Филдсету" и "лэйбэлу" в котором лежит "Инпут" задаём "скрыть переполнение", а "лэйбэлу" задаём нужную высоту и вешаем на него картинку с кнопкой бэкграундом. Вот и всё.
#
Ростислав
Anatoli
>>стодвенадцатьтысячтристатридцатьчетвертое изобретение велосипеда>> делать нужно, ибо не хлебом единным ...
Предлагаю ...пятое, более точное:
- Создаём Input заведомо много больше Button-a, скажем
style=position:absolute; visibility:hidden; width:300px; height:100px; padding:30px;
font-size:30px; filter: alpha(opacity: 0); opacity: 0;
- Абсолютно грубо с перехлёстом накрываем Button Input-ом;
- B Button.onmouseover Input.visibility=visible;
- B Input.onmouseout Input.visibility=hidden;
- B Input.onmousemove, если вывалился за пределы Button-a, to Input.visibility=hidden;

В итоге: Input перекрывает ( с точностью до px ) только Button;
Click работает ( с точностью до px ) только в рамках Button-а.
#
Anatoli
Владимир
Если такую кнопку ставить на форму заявки - файл загружается на сервер? Или сразу со страницы отправляется на е-майл?
#
Владимир
Антон
А как сделать множественные инпуты на странице?
#
Антон

Новый комментарий

как выглядит какой тег
жирный текст <b>жирный текст</b>
курсивный тект <i>курсивный тект</i>
зачеркнутый текст <s>зачеркнутый текст</s>
подчеркнутый текст <u>подчеркнутый текст</u>
ссылка <a href="адрес">ссылка</a>
function foo() { ... }
<pre><code>function foo() { ... } </code></pre>
разрешенные теги или посмотреть как будет выглядеть
как выглядит какой тег
жирный текст <b>жирный текст</b>
курсивный тект <i>курсивный тект</i>
зачеркнутый текст <s>зачеркнутый текст</s>
подчеркнутый текст <u>подчеркнутый текст</u>
ссылка <a href="адрес">ссылка</a>
function foo() { ... }
<pre><code>function foo() { ... } </code></pre>
разрешенные теги или посмотреть как будет выглядеть

metin2 pvp metin2 pvp serverler pvp serverler