- PVSM.RU - https://www.pvsm.ru -
Уже немало копий front-end разработчиков было сломано об проблему стилизации поля ввода input[type=file]. Суть проблемы заключается в том, что в спецификации HTML [1] нет строгих правил, устанавливающих, как же должен отображаться браузером этот элемент. Более того, для input[type=file] не предусмотрено атрибутов, которые позволили бы изменить его внешний вид, с помощью стилей CSS можно изменить лишь вид его границы и шрифт, а средствами JavaScript, из соображений безопасности, нельзя сымитировать клик по этому элементу, который вызвал бы системное окно для выбора файла. Но что же делать, когда заказчик хочет адаптивный сайт с красивыми стилизованными формами, в которых нельзя обойтись без этого поля ввода?
За то время, сколько существует эта проблема (а существует она очень долго), было найдено несколько способов ее решения. Всего их существует три:
Убедить заказчика, что можно жить и со стандартным input[type=file].
Написать/использовать готовый загрузчик файлов на Flash/Java-апплете. Используется, например, на habrastorage.org/ [2]
Средствами CSS «замаскировать» стандартный input[type=file], сделать его полностью прозрачным и поместить на месте стилизованного фейкового поля, чтобы клик по последнему вызывал клик по стандартному, и, как следствие, открывал системное окно выбора файла.
И у второго, и у третьего способа, разумеется, есть свои минусы. Существенный недостаток Flash/Java-решения в том, что для его работы нужны соответствующие плагины, которых в браузере пользователя может не оказаться. Недостаток «маскировочного» решения же заключается в том, что для его реализации необходимо использовать хаки (про это речь пойдет ниже), а также потому, что оно бессмысленно без использования JavaScript (ведь нужно же как-то различать состояния «файл не выбран» и «файл выбран» для стилизованного фейкового поля, что на одном CSS сделать невозможно).
Посмотрев несколько рабочих реализаций способа №3, мне захотелось изобрести свой велосипед, но не простой, а золотой адаптивный! Во всех решениях (как, например, здесь [3] и здесь [4]), которые мне встречались, стилизованное поле для выбора файла имеет фиксированную ширину, либо имеет вид простой одинарной кнопки (тоже с фиксированной шириной). Задание ширины же фейкового поля в процентах либо невозможно в принципе, либо может привести к тому, что данное поле будет работать в некоторых браузерах не совсем так, как хотелось бы.
Поэтому ключевой задачей было поставлено создание «резинового» input[type=file], который на экранах мобильных устройств представлял бы из себя простую кнопку для выбора файла (имя выбранного файла выводится на ней же), а на широких экранах выглядел бы как привычное для всех текстовое поле + кнопка, которое может тянуться на всю ширину окна:
Схематический вид элемента на мобильных устройствах
Схематический вид элемента на десктопных устройствах
Дабы упростить себе жизнь, было решено отказаться от поддержки «любимца» всех верстальщиков — браузера IE 8. Да, сейчас многие меня упрекнут за это, но во-первых, этот браузер сейчас используется всего 4,8% пользователей (с тенденцией на понижение), что видно из статистики [5], а во-вторых, у меня не было и нет большой уверенности, что реализовать подобный input[type=file] вообще возможно для этого браузера.
Таким образом, с учетом оговоренной выше схемы, исходная верстка для нашего стилизованного поля выбора файла может иметь следующий вид:
<div class="file_upload">
<div>Файл не выбран</div>
<button>Выбрать</button>
<input type="file">
</div>
Перейдем к стилям. Чтобы у читателя не сложилось неверное впечатление, что каждое используемое в статье значение свойств CSS имеет огромную важность (так называемые «магические числа»), договоримся помечать те из них, которые можно смело изменять под свои нужды, комментарием
/* example */
Договорились? Отлично! Начнем стилизовать наше фейковое поле выбора файла с его «обертки» — div.file_upload:
.file_upload{
position: relative;
overflow: hidden;
font-size: 1em; /* example */
height: 2em; /* example */
line-height: 2em /* the same as height */
}
— свойство position задается для того, чтобы относительно div.file_upload можно было абсолютно позиционировать его дочерние элементы, а свойство overflow — для того, чтобы скрывать все то, что по каким-то причинам не влезет в нашу обертку (а такое найдется, но об этом позже). На широких экранах наши красивые поле и кнопка должны отображаться в одну строку, поэтому зададим для обоих float:
.file_upload > div, .file_upload > button{
float: left;
height: 100%;
margin: 0
}
Зададим также какую-нибудь базовую ширину для обоих элементов в процентах:
.file_upload > div{
width: 80%; /* example */
padding-left: 1em; /* example */
margin-right: -1em; /* the same as padding-left, but negative */
line-height: inherit
}
.file_upload > button{
width: 20% /* example */
}
Поскольку мы хотим, чтобы на мобильных устройствах текстовое поле скрывалось, и оставалась одна кнопка выбора файла, необходимо задать media query:
@media only screen and ( max-width: 500px ){ /* example */
.file_upload > div{
display: none
}
.file_upload > button{
width: 100%
}
}
Ну а теперь — самое веселое! Необходимо сделать стандартный input[type=file] полностью прозрачным, и растаращить растянуть его до размеров «обертки» div.file_upload. Для реализации последнего применим хак в виде абсолютного позиционирования и свойства CSS 3 transform, с помощью которого увеличим элемент, например, в 20 раз (да, это самое обычное «магическое число»):
.file_upload input[type=file]{
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
transform: scale(20);
letter-spacing: 10em; /* IE 9 fix */
-ms-transform: scale(20); /* IE 9 fix */
opacity: 0;
cursor: pointer
}
Как видно из приведенного выше фрагмента CSS, для IE 9 потребовались дополнительные костыли. Это связано с тем, что данный браузер при клике на текстовое поле не вызывает системное окно выбора файла, а любезно предлагает «стереть» имя уже выбранного, что символизируется мигающим текстовым курсором. Поэтому для него дополнительно задается огромный интервал между буквами, что увеличивает кнопку элемента до размеров div.file_upload. Отмечу также, что z-index в данном случае не указывается, т.к. элемент идет последним «потомком» в выбранной с самого начала разметке.
На примере десктопного браузера FireFox, сейчас наше кастомизированное поле выбора файла для разных размеров окна выглядит так:
Разумеется, в таком примитивном виде поле выбора файла вряд ли кого-то устроит, поэтому добавим дополнительные стили, которые сделают кнопку выбора файла, скажем, фиолетовой, добавят тени и т.д. Не забудем также добавить свой стиль для кнопки, когда на нее наведен курсор, стиль для нажатой кнопки, а еще добавим стиль для всего элемента, когда на нем находится фокус (будет применяться при помощи JavaScript):
/* Making it beautiful */
.file_upload{
border: 1px solid #ccc;
border-radius: 3px;
box-shadow: 0 0 5px rgba(0,0,0,0.1);
transition: box-shadow 0.1s linear
}
.file_upload.focus{
box-shadow: 0 0 5px rgba(0,30,255,0.4)
}
.file_upload > button{
background: #7300df;
transition: background 0.2s;
border: 1px solid rgba(0,0,0,0.1);
border-color: rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);
border-radius: 2px;
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.2) inset, 0 1px 2px rgba(0, 0, 0, 0.05);
color: #fff;
text-shadow: #6200bd 0 -1px 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis
}
.file_upload:hover > button{
background: #6200bd;
text-shadow: #5d00b3 0 -1px 0
}
.file_upload:active > button{
background: #5d00b3;
box-shadow: 0 0 3px rgba(0,0,0,0.3) inset
}
Теперь наше поле выбора файла выглядит так:
Поскольку мы делаем полноценное поле для выбора файла, то нужно позаботиться о том, чтобы его можно было комфортно заполнять и с клавиатуры (сейчас же фокус вначале устанавливается на стилизованную кнопку, а затем — на скрытый input[type=file], что никак визуально не проявляется). Для этого, разумеется, используем JavaScript. Чтобы не писать много кода, я позволю себе использовать популярную библиотеку jQuery:
var wrapper = $( ".file_upload" ),
inp = wrapper.find( "input" ),
btn = wrapper.find( "button" ),
lbl = wrapper.find( "div" );
btn.focus(function(){
inp.focus()
});
// Crutches for the :focus style:
inp.focus(function(){
wrapper.addClass( "focus" );
}).blur(function(){
wrapper.removeClass( "focus" );
});
Поле ввода до сих пор оставалось «мертвым» — при выборе файла имя последнего нигде не отображалось. Пришло время исправить и это:
var file_api = ( window.File && window.FileReader && window.FileList && window.Blob ) ? true : false;
inp.change(function(){
var file_name = file_api ? inp[ 0 ].files[ 0 ].name : inp.val().replace( "C:\fakepath\", '' );
if( ! file_name.length )
return;
if( lbl.is( ":visible" ) ){
lbl.text( file_name );
btn.text( "Выбрать" );
}else
btn.text( file_name );
}).change();
— если браузер поддерживает File API, то имя файла определяется с помощью него, в противном случае оно вырезается из значения скрытого input[type=file]. Для мобильных устройств, когда элемент представляет из себя одну кнопку, имя выбранного файла выводится на ней же.
Казалось бы, все, что требуется, уже написано. А вот фигушки! Если выбрать файл, используя «мобильное» поле, а затем увеличить размер окна и перевести элемент в «десктопный», то в текстовом поле так и останется «Файл не выбран» — нужно каждый раз обновлять элемент при изменении размеров окна:
$( window ).resize(function(){
$( ".file_upload input" ).triggerHandler( "change" );
});
Полученное стилизованное поле выбора файла было успешно протестировано в следующих браузерах:
Из плюсов рассмотренного в статье подхода можно выделить следующие основные:
Из основных минусов:
Рабочий пример можно посмотреть на CodePen [6].
Автор: dif
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/news/40596
Ссылки в тексте:
[1] спецификации HTML: http://www.w3.org/html/wg/drafts/html/master/forms.html#file-upload-state-%28type=file%29
[2] habrastorage.org/: http://habrastorage.org/
[3] здесь: http://habrahabr.ru/post/30560/
[4] здесь: http://habrahabr.ru/post/171743/
[5] статистики: http://www.w3schools.com/browsers/browsers_explorer.asp
[6] CodePen: http://codepen.io/dif/pen/izwfh
[7] Источник: http://habrahabr.ru/post/189570/
Нажмите здесь для печати.