22 декабря 2015
1 августа 2008
62
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 программеров, позволяющий легко добавлять такие стилизованные инпуты на страницу.
Если такая возможность есть, было бы СУПЕР!
Но потом я одумался и стал увлекаться вышиванием крестиком и стилизацией файл-инпутов.
Последняя: обзор файловых загрузчиков.
Рекомендую AjaxUpload.
А насчет курсора, то его стиль невозможно изменить в FF.
решение: добавить к условию выбора иконки if(ext){...} условие else fileName.style.display = 'none';
А я утверждаю, что ширину тоже можно изменить.
Я такого не видел, и сам не додумался.
var RegExExt =/.*.(.*)/; //так правильней (косая перед точкой)
function select_ext() {
здесь код функции
}
p.s. к предыдущему моему комментарию - перед второй точкой нужно ставить обратный слэш, чтобы функция работала
to Саша:
input type="file" onchange="функция()"
Если есть какое либо решение, прокоментируйте плиз...
Если кому интересно, оформил все что тут написано в виде плагина jQuery. Посмотреть можно здесь http://savvateev.org/blog/38/
А можно где-то посмотреть, как это сделать? Ширина-то уменьшается, но при клике справа от кнопки все равно срабатывает инпут.
cursor:pointer не работает в FF
"Инпуту" задаём большой размер шрифта, тем самым увеличивая его ширину до необходимой, и прозрачность. "Филдсету" и "лэйбэлу" в котором лежит "Инпут" задаём "скрыть переполнение", а "лэйбэлу" задаём нужную высоту и вешаем на него картинку с кнопкой бэкграундом. Вот и всё.
Предлагаю ...пятое, более точное:
- Создаём 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-а.