Доброго времени суток, мой дорогой друг. В сети, да и на Хабре, есть множество статей на тему создания своего input type=«file», но все они отличаются большим количеством костылей и большим количеством кода, что, как мне кажется, не есть хорошо. Ибо, как бы это не было парадоксально, меньше — лучше.
Рабочий пример того, что получится:
Сам принцип кастомизированного input file особых отличий не имеет: убираем с экрана input, и всё возлагаем на плечи label, которому мы добавим свои стили. Главное отличие — малое кол-во кода.
Начнём
Мы имеем input и label:
Теперь уберём с экрана input, добавив классу my следующие стили:
А также стилизуем сам label, добавив ему свои стили:
И теперь самое интересное: javascript. Собственно вот он:
Он работает следующим образом: Когда пользователь жмёт на input с классом .my, то js начинает отслеживать его изменение. Дальше в дело вступает if (если). Так вот если у нас этот (this) input не пустой (то есть файл был какой-то выбран), то стоящий по соседству выше элемент (это у нас label) получит текст «Выбрано файлов» + кол-во файлов, которое выбрал пользователь. Ну а если пользователь ничего не выбрал, то label просто получит текст «Выберите файлы».
Всё!
// Дополнительная информация
Может возникнуть проблема с вёрсткой, какая была и у меня. А проблема может заключаться с этим пресловутым .prev(). По факту, есть вероятность того, что невозможно расположить label и input file рядом друг с другом, и текст «Выбрано файлов» будет применяется не к label, а к левому элементу.
Эту проблему можно решить вот так:
Поместите label и input в один div, и дайте этому div'у класс, к примеру «box-form»
И замените в js
на
Вуаля! Теперь, стоявшие далеко друг от друга, label и input способны взаимодействовать друг с другом.
Лучше, конечно, избегать таких случаев, но никто не защищён от фреймворков, где input'ы пишешь не ты, а их генерирует сам фреймворк…
Спасибо за внимание.
Рабочий пример того, что получится:
Сам принцип кастомизированного input file особых отличий не имеет: убираем с экрана input, и всё возлагаем на плечи label, которому мы добавим свои стили. Главное отличие — малое кол-во кода.
Начнём
Мы имеем input и label:
<label for="myfile" class="label">Выберите файлы</label>
<input type="file" class="my" id="myfile" name="myfile" multiple>
Теперь уберём с экрана input, добавив классу my следующие стили:
.my {
width: 0.1px;
height: 0.1px;
opacity: 0;
overflow: hidden;
position: absolute;
z-index: -1;
}
А также стилизуем сам label, добавив ему свои стили:
.label {
width: 180px;
height: 50px;
border-radius: 4px;
text-align: center;
cursor: pointer;
display: block;
font: 14px/50px Tahoma;
transition: all 0.18s ease-in-out;
border: 1px solid #333;
color: #333;
}
.label:hover {
color: white;
background: #333;
}
И теперь самое интересное: javascript. Собственно вот он:
$('.my').change(function() {
if ($(this).val() != '') $(this).prev().text('Выбрано файлов: ' + $(this)[0].files.length);
else $(this).prev().text('Выберите файлы');
});
Он работает следующим образом: Когда пользователь жмёт на input с классом .my, то js начинает отслеживать его изменение. Дальше в дело вступает if (если). Так вот если у нас этот (this) input не пустой (то есть файл был какой-то выбран), то стоящий по соседству выше элемент (это у нас label) получит текст «Выбрано файлов» + кол-во файлов, которое выбрал пользователь. Ну а если пользователь ничего не выбрал, то label просто получит текст «Выберите файлы».
Всё!
// Дополнительная информация
Может возникнуть проблема с вёрсткой, какая была и у меня. А проблема может заключаться с этим пресловутым .prev(). По факту, есть вероятность того, что невозможно расположить label и input file рядом друг с другом, и текст «Выбрано файлов» будет применяется не к label, а к левому элементу.
Эту проблему можно решить вот так:
Поместите label и input в один div, и дайте этому div'у класс, к примеру «box-form»
<div class="box-form">
<p>Текст какой-то</p>
<div>
<label for="myfile" class="label">Выберите файлы</label>
</div>
<div>
<div></div>
<input type="file" class="my" id="myfile" name="myfile" multiple>
</div>
</div>
И замените в js
$(this).prev()
на
$(this).closest('.box-form').children('.label')
Вуаля! Теперь, стоявшие далеко друг от друга, label и input способны взаимодействовать друг с другом.
Лучше, конечно, избегать таких случаев, но никто не защищён от фреймворков, где input'ы пишешь не ты, а их генерирует сам фреймворк…
Спасибо за внимание.
Поделиться с друзьями
Delphinum
А разве зависимость в виде JQuery уже считается минималистичным решением?
Azoh
Конечно же. В хабах же jQuery, а не Javascript. Для настоящих jQuery-программистов.
MrCheater
Не просто jQuery, а jqyery
kirigosh
ахаха))) спасибо, не заметил) новый язык, получается, изобрёл.
А по поводу зависимости. Сейчас на практике 99% сайтов используют jquery. Зачем сопротивляться тенденции?
raveclassic
Видимо, 99% вашей практики? И ради чего, пару раз DOM пощупать?
kirigosh
DOM пощупать, анимацию поцеловать, ajax по попе шлёпнуть и promise подмигнуть.
raveclassic
А что не так со стандартным API? Нет, ну я понимаю, в 2010 jquery из всех щелей лезла. Но ведь 2017 на дворе, все API давно стандартизировано.
Rad1calDreamer
Любой пост, начинающийся с «Пишем самый простой\крутой\быстрый %имяФичи%....» содержит в себе jQuery. И лучше не спрашивать: «А с какой целью вы здесь используете jQuery? »
saluev
Моя любимая часть —
$(this)[0]
.k12th
Если
label
не содержит в себеinput
, то он и не будет работать как лейбл. То есть, нужен id у инпута и for у этого лейбла. И если верстка не совсем кривая, то найти нужный лейбл можно через$('label[for=" + this.id + '"]')
.keslo
Быстро-то, может быстро. Но разбираться с «быстрым» кодом потом дольше приходится.
Как по мне, если вы работаете с несколькими дочерними узлами внутри родительского, то можно использовать всплытие событий. И не использовать parent() и всякую другую вакханалию.
Пример https://jsfiddle.net/keslo/07bupmgp/
Aingis
Кажется, любой здешний обитатель может написать код лучше.
Судя по кусочку
{ width: 0.1px; height: 0.1px; opacity: 0;...}
, вы читали статью Styling & Customizing File Inputs the Smart Way (которая находится по первым же ссылкам в поиске), но не дочитали даже до раздела «Keyboard Navigation». А зря, там учтено много нюансов. Вы бы хоть список требований составили.Вот, например, какие есть серьёзные недостатки:
transition: all
).А какие проблемы вы решали?
dif
Ох. Посмотрел по вашей ссылке свою «статью», которую написал 4 года назад — теперь не могу развидеть этот кривой CSS и говнокод на jQuery :(
Спасибо, что напомнили — обязательно перепишу это безобразие с гордой пометкой «tutorial», как появится время.
kirigosh
…
Поле для drag&drop нужно делать, а не на кнопку кидать. Кидать на кнопку — это уже маразм какой-то…
Речевые?.. Вообще никак не знаком с этим. Как обычный input file с помощью речевых открыть?
Тач? Он не будет работать? Пальцем не попадёшь или что?
Телевизор? Меня интересует сейчас не поддержка телевизоров, а хранилище, с которого будут загружаться файлы. Тебе, как минимум, нужна флешка. Ситуация, наверно, будет такая: Ты идёшь к компу и скидываешь на флешку файлы, достаёшь из компа флешку и идёшь к телевизору, вставляешь её (а usb вход находится сзади, и тебе нужно еще попотеть, чтобы эту флешку вставить) в телевизор, и начинаешь тыкать пультом по форме… Товарищ, я смотрю вы знаете толк в извращениях. Хотя, я наверно не прав. Я подумаю на счёт этого…
Как будто надо. Ты еще попроси превью изображения с функцией перетаскивания… Наша цель: быстро и просто.
Ну это не моя забота. Для проверки формы пишется отдельная функция, которая меня не касается, и разработчик её расширяет так, как ему вздумается. К примеру, у меня валидацию устраивает yii, и все у меня ошибки нормально отображаются.
На другой язык? Трудности… Трудности будут также для людей с ограниченными возможностями, для детей и для стариков. О каком переводе идёт речь? Для разных языков нужно писать разные сайты с нуля, придерживаясь законам основного дизайна. Писать один и тот же код для нескольких языков тоже самое, что делать резиновые сайты.
Размер кнопки надо было в процентах указывать?
Слово Tahoma просто так написано? Для красоты?
Цвет фона указан. Он прозрачный.
Вот блин, цвет не контрастный. Чтож теперь делать? Этож обязательное правильно: использовать зелёный цвет. Иначе же форма работать не будет.
transition: all и? Что не так?
Наличие формы, как бы уже подразумевает наличие jquery. Писать на голом javascript тоже самое, что купить машину и ходить пешком.
Если яваскрипт у юзверя сломан, то он до моего input file даже не дойдёт. так что тут мне не о чем переживать.
Внешний вид. На разных браузерах input кроме того, что страшный, так еще и везде разный. Хотелось бы придерживаться основному стилю/дизайну при разработке.
raveclassic
herr_kaizer
Ну это вообще пушка. Хорошо, таких как вы меньшинство, по крайней мере на крупных ресурсах.
Это даже комментировать не хочется.
Сколько вам лет?
kirigosh
20
jhekasoft
Неадекватно.
Aingis
Я смотрю, вы специалист по говнокоду.
michael_vostrikov
Указанные вами трудности связаны с физическими возможностями и знаниями пользователей. А вам говорят про трудности при разработке и поддержке.
Вы в курсе, что копи-паста это плохой подход к проектированию? Как потом это все поддерживать? Да и зачем тратить столько времени и сил, когда есть способы гораздо проще.
Не надо было его вообще указывать. Чтобы другому программисту не пришлось переопределять его в 10 местах, если понадобилось размер шрифта увеличить или текст подлиннее написать.
На немецком, например, эта надпись в 1.5 раза длиннее. Как вы предлагаете придерживаться законов основного дизайна? Руками размер каждой кнопки задавать, чтобы внутренние отступы нормально выглядели? Или проще все-таки отступы стандартными средствами задать?
Ничего подобного. Это конкретно в Yii работа с полями формы происходит через jQuery.
ilyaplot
jsfiddle за пару минут стало лучше. Но это самый минимум того, что нужно было сделать.
keslo
Ну там много, что можно изменить :-)
Получилась бы хорошая задачка для собеседования, типа «что бы вы изменили в этом коде и почему?».
kirigosh
вторая часть комментария вас тоже касается https://habrahabr.ru/post/321250/#comment_10055500
0Ilya
Почему статью написал он, а стыдно мне?
Какое удобное название классов, какой универсальный обработчик и код!
Вот самое простейшее улучшение данного кода:
kirigosh
Сейчас мне стыдно за ваш код. Я, конечно, профан в разработке, но поясните мне: на кой черт нужен var text_label_id = $(this).attr('id');? Мы все знаем, что var используют для обозначения того, или иного куска кода. Чтобы этот кусок стал короче и более понятен, мы используем var. Особенно он полезен, когда этот кусок кода встречается неоднократно, то объявление переменной — просто спасение. Но здесь… Здесь что-то новенькое. Вы только один раз использовали text_label_id, и сделали это, между прочем, на соседней строчке. И самое смешное, что вы дали ему название, которое по своей длине не особо-то и отличается от первоначального источника… $(this).attr('id'); = 19 символов. text_label_id = 13 символов. Оно того стоило?
А теперь поговорим про скорость, мой всё еще не менее дорогой друг.
Что быстрее .prev() или label[for="(this).attr('id')"]? Ну вот серьёзно, представь, что проще сделать?
Давай я для тебя аналогию проведу, а то, мне кажется, ты сразу не поймёшь мою мысль.
Вот смотри: допустим ты живёшь на 50-ом этаже многоэтажного дома. В квартире, к примеру, 297. И вот тебе по срочной необходимости понадобилась соль. Да, банальная такая соль. Ты знаешь, что соль может дать тебе твой сосед Вася, который живёт в соседней квартире на площадке твоего этажа, но номер квартиры забыл и посчитать не можешь. Но так как ты у нас очень педантичный человек, тебе обязательно нужно знать номер квартиры. И что делаешь ты? Ты выходишь из своей квартиры, спускаешься на первый этаж и начинаешь заходить во все квартиры, проверяя, живёт ли в этой квартире Вася. «Вася тут?» — обращаешься ты к жильцу квартиры номер 1. И так далее. Каждую, мать её, квартиру проверяешь. И потом уже, под вечер, когда ты дошел до 298 квартиры, где живёт наш Вася, ты просишь у него соль. Красаучик, ничего не сказать.
Вот и label[for="(this).attr('id')"] тоже самое. Начинаешь перебирать весь документ, в надежде найти label, у которого id равен id input. Ключевое слово, если ты не понял, здесь «весь документ».
raveclassic
Переменная нужна для простоты поддержки, а не для экономии на
спичкахсимволах.С чего вы взяли, что поиск по селектору осуществляется перебором?
Что будет, если между инпутом и лэйблом добавится элемент?
Можете переписать это на DOM API? Вы удивитесь, насколько бесполезен тут jquery.
kirigosh
так я описал случай, когда между ними появляется еще один элемент. Мы обращаемся к родителю и у родителя, в дочках, ищем label. Всё просто.
А с чего вы взяли что это не перебор файла? Компьютер сразу знает, где у него находится label for=«id=input»? Ну ка, вспомни, как у тебя на компьютере работает поиск? Так я тебе напомню. Берётся название файла и проверяется это название с файлами на компьютере. Так и тут. Берётся документ (html) и проверяются все строчки на совпадение. Ты ради интереса создай web файл, который будет весить 1гб. И сделай потом label[for="(this).attr('id')"]. Зависло? Странно… Не должно…
Ты можешь сказать, что я утрирую, и что по факту у нас не будет документа в 1гб, и что разницы мы не ощутим между prev() и label for=«id=input», но хочу тебе сказать, а ты это тоже забыл. 100 рублей = 100 раз по 1 рублю. Так вот задержка в 1/100, которую мы, якобы, не ощутим, добавится к другим задержкам, что приведёт потом к тому, что мы тупо сидим и ждём, пока у нас комп сообразит, ибо таких обращений, типа abel for=«id=input», у нас немерено. А потом мы виним железо в зависаниях, виним хром, который много памяти жрёт, виним то, чего винить не должны. Ты не говнокодь, тогда нормально всё будет! Глюков не будет. Или другое сравнение: игры требовательны к железу. Они не требовательны, они просто не оптимизированы.Я на 1000% уверен, что можно оптимизировать любую игру и играть в неё на максимальных настройках графики на старом ПК 2004 года. А из-за таких, как ты, у нас всё на говне и держится. Всё лагает, зависает и требует ОЗУ.
На моём старом пк full HD видео на youtube лагает, просто fps падает. Видимо youtube писали такие, как ты… Спасибо тебе за web 2.0
Aingis
Это просто шедевральный комментарий!
raveclassic
Потрясающе! Жаль вашего работодателя.
melnik909
Вы все же перегибаете палку. Мальчишка еще учится. Да, его решение далеко от совершенства, но и говорить так, тоже не стоит. Вспомните себя в 20. Нужно меньше негатива и больше конструктивной критики.
raveclassic
Понятное дело, перегибаю. Это тоже часть процесса обучения, так сказать, получить по шапке, чтобы впредь думать, что писать.
Серьезно, без обид, все мы шли этим путем, но не все позволяли себе что-то из разряда вышеизложенного.
yarosroman
В 20 лет то, уже пора перестать обижаться на конструктивную критику и начать анализировать, что вам говорят.
DjOnline
Позвольте присоедениться к срачу! :)
Веб-видео (Youtube и другие) действительно тормозит и через html5, и через flash на старом железе, которое тем не менее способно тянуть это видео через MPC и LavFilters/CoreAvc, в виду того что браузер всё таки не полноценный оптимизированный видеоплеер, и не знает таких штук как внешние быстрые кодеки, decode frame in advance и прочей специфики. Внешние декодеры на старом железе — это не пустое слово, некоторые из них заточены под старые intel адаптеры и умеют частично использовать аппаратное ускорение, которое недоступно через html5/flash, потому что системный декодер (либо встроенный в браузер) не поддерживает это железо и считает устаревшим.
Пример перед глазами — двухядерный ноутбук с Intel 3100MHD, который без проблем показывает в MPC или Kodi 1080/24p, но в ютубе даже 720p притормаживает что в Flash что в html5. Также это заметно на старых атомных планшетах, для которых не всегда есть поддержка аппаратного декодера в браузере/системе.
Помню были такие плагины, которые меняют содержимое flash/html5 плеера на embed vlc/wmp и видео начинает идти стабильнее на старых машинах. Пример — https://chrome.google.com/webstore/detail/vlc-4-youtube-beta/jldiailifbdkepgpcojllmkbakleicab?hl=en, или https://addons.mozilla.org/en-US/firefox/addon/mpc-yt/ для MPC.
raveclassic
Ну как же, проблема же не в кодеках, железе, прожорливой технологии, оптимизации или в чем-то там еще. Проблема в том, что мы с вами тут только и делаем, что говнокодим и, вообще, ломаем веб, негодяи. Все же просто.
Aingis
Ну, на Ютюбе 1080p60fps тормозит даже на довольно мощных системах. А причина в том, что Ютюб очень любит использовать кодек VP9, который сжимает немного лучше, но ценой того, что аппаратно его никто не умеет декодировать. В таком случае очень помогает расширение вроде h264fy, благодаря переключению на кодек h264.
DjOnline
Описываемые события были задолго до появления 1080/60p и vp9 на youtube, и наблюдаются по сей день на 1080/24p или даже 720/24p. Не хочу повторяться, многие старые массовые видеоадаптеры intel умеют аппаратное декодирование через стороннии кодеки, но не умеют через системные и через браузер. На ещё более старых системах без аппаратного декодирования проблема в том же, в браузер/flash встроен ffmpeg, который не является образцом скорости, в отличи например от старых добрых CoreAvc/LavFilters. Опять же видеоплеер умеет выводить видео через EVR и прочее, а браузер должен делать куча других вещей, composition и прочее, предусмотреть отображение html элементов поверх видео, что тоже не прибавляет скорости. Поэтому даже массовый intel 4500Mhd 2008 года, который без проблем тянет в MPC 1080/24p, неспособен в youtube воспроизвести 1080/24p.
michael_vostrikov
Это для вас новенькое. Переменные вводятся для понятности и упрощения поддержки.
А вот поговорим. Что быстрее, обращение к элементу напрямую средствами браузера или через дополнительную библиотеку, которая обращается к нему средствами браузера? Ну вот серьёзно, представьте, что проще сделать? Давайте я для вас аналогию проведу, а то, мне кажется, вы сразу не поймёте мою мысль.
Допустим, вам по срочной необходимости понадобилась соль. Да, банальная такая соль. Вы знаете, что соль может дать ваш сосед Вася, который живёт в соседней квартире на площадке вашего этажа. Но вы не хотите просто зайти к нему и попросить. Вы звоните своему юристу, просите его «найди Васю и спроси у него соль», он ищет адрес Васи в городской базе данных, приезжает к нему, составляет договор, берет соль, заворачивает ее в пакет, передает вам, вы разворачиваете пакет и берете соль. Зато вы точно знаете, что юрист решит все вопросы сам — и присутствие/отсутствие Васи и наличие у него соли.
К чему это я? К тому, что если вы используете jQuery, то нет смысла экономить на селекторах, тем более для одного нажатия, тем более в таком эмоциональном ключе. Все равно это будет медленнее варианта без jQuery. Хотите скорости — пишите на чистом javascript.
Я, конечно, не специалист в тестировании, но вот вам небольшой тест производительности.
Aingis
100000 повторов с рэндомом — не очень хорошая идея. Думаю, реально jQuery отстаёт ещё больше, так как элементов всего 1000 и результат кешируется. Из-за этого на повторах смазываются затраты времени парсинга селектора. 1000 повторов, кстати, вполне хватит. performance.now() меряет с точностью до 1/1000 мс.
rayz_razko
Вам будет полезно почитать это: Clean Code by Robert C. Martin
AiZen_13
Несочтите за критику, но зачем Вам jQuery для получения id и files?Намного ж изящнее и проще использовать this.id и this.files
библиотека отличная конечно, но порой ее суют куда не попадя — какое-то #жиквериголовногомозга прям
di-strange
Не так красиво, но все таки без jQuery
vlfesko
А как же быть с фокусом? Я, конечно, все понимаю, но TAB никто не отменял — иногда проще и быстрее доклацать до инпута табом, если постоянно пользуешься страницей.
AiZen_13
сдается мне, можно выкинуть стили height, width, overflow для класса .my, а то что-то кода многовато
Ivanq
А без jQuery слабо? Было бы прекрасное решение, если бы использовать Vanilla JS.
herr_kaizer
Ну что за жесть, могли бы хоть как плагин оформить. Будто на 5 лет назад во времени переместился.
Мне пару часов назад хабр показывал рекламу «Стань программистом за 2 часа», видимо примерно этого уровня оттуда выходят.
iwonz
…