English
🏷️

Удобный кросс-браузерный placeholder с помощью jQuery  

Это статья из старого первого блога, который сохранился только на archive.org. Все старые статьи помечены тегом 🚨 старое

Реализация подсказки в виде <label>, спозиционированная над полем ввода, исчезающая при заполнении, плюс «умная очищалка» содержимого поля, появляющаяся/исчезающая при вводе/удалении содержимого поля, демо-страничка с тем, что должно в итоге получиться.

Есть в html5 такой полезный и удобный атрибут для полей ввода — placeholder.

Он позволяет без лишних усилий отобразить в поле ввода надпись-подсказку, в помощь пользователю. Но адекватно его поддерживают сейчас только некоторые браузеры, FireFox 4, Opera 11 и WebKit-браузеры.

Также, с точки зрения юзабельности, полезен и атрибут search, добавляющий к полю ввода при его заполнении, небольшой контрол в виде крестика для быстрой очистки введенной информации. Этот атрибут поддерживается еще меньшим количеством браузеров, на сегодняшний момент его поддерживают только WebKit-браузеры.

К тому же изменение внешнего вида (рамки, цвета, замена/добавление изображений) полей с этими атрибутами пока представляет собой адскую задачу с использованием селекторов атрибутов, к сожалению до конца не кросс-браузерную и выглядящую, мягко говоря, нелицеприятно :)

Самый простой, далеко не семантичный и самый распространенный способ задать плейсхолдер текстовому полю – добаввить инлайновые JavaScript-события прямо в теге поля ввода:

HTML

    <input 
      type="text" 
      onblur="if (this.value==''){this.value='Поиск...'}" 
      onfocus="if (this.value=='Поиск...') this.value='';" 
      value="Поиск..." >
    

Семантикой здесь как видно и не пахнет, т.к. подсказки временно являются содержимым поля ввода. Куда более лучше было бы реализовать их при помощи <label> как это сделано, к примеру, на страничке помощи компании Яндекс, <label> здесь позиционируется абсолютно над текстовым полем и с помощью JS, при соответствующих действиях, убирается. Об этом способе подробно на одном из Яндекс-субботников рассказывал великий и ужасный :) Виталий Харисов. В своей лекции, приводя примеры с инлайновыми JS-событиями, он справедливо заметил, что для реального применения логику следует оформлять с помощью jQuery или на чистом JS.

Попытаемся соорудить подобное поле ввода с помощью jQuery. Для начала, аккуратно сверстаем соответствующие элементы.

HTML-код будет выглядеть следующим образом:

HTML

    <form class="placeholder-form" action="#" method="post">
      <div class="placeholder">
          <label for="input-name" class="input-hint">
            А как <span class="marked-text-false">вы</span> 
            относитесь к 
            <span class="marked-text-true">семантике</span>?
          </label>
          <input class="input-text" type="text" id="input-name" value="" >
      </div>
    </form>
    

CSS офомление:

CSS

    .placeholder-form {
      min-width:1000px;
      padding:5em;
      position:relative;
      overflow:hidden;
      }
      .placeholder {
          width:90%;
          position:relative;
          _zoom:1;
          }
          .placeholder label {display:block;}
              .marked-text-true {color:#009933;}
              .marked-text-false {color:#ff0000;}
          .input-text {
              display:block;
              margin:.2em 0 .5em 0;
              width:90%;
              border:1px solid #ccc;
              position:relative;
              padding:.1em 60px .1em .3em;
              }
          .placeholder label,
          .input-text {
              font-size:3em;
              line-height:1em;
              color:#5e5e5e;
              }
          #js .placeholder label {
              position:absolute;
              top:.2em;
              _top:.45em;
              left:.3em;
              cursor:text;
              z-index:1;
              }
    

Специально для разработчиков с параноидальной наклонностью делать все так, чтобы при отключенном JS в браузере ничего никуда не ехало и наша надпись-<label> была, аккурат перед полем ввода, а не спозиционирована над ним, добавим небольшую строчку JS-кода:

JavaScript

    document.documentElement.id = "js";
    

Этим незамысловатым буквосочетанием мы присваиваем тегу <html>, айди js. Таким образом, все селекторы, написанные после #js, будут срабатывать только при включенном JS в браузере.

Вот так будет выглядеть наше поле ввода.

Теперь займемся оживлением подсказки.

С помощью событий jQuery .blur() и .focus(), срабатывающими когда элемент теряет фокус или приобретает фокус соответственно, опишем логику исчезания-появления подсказки, добавляя или удаляя у <label> класс hide, который будет скрывать подсказку заданием огромного отрицательного отступа слева: left:-9999em !important;.

Для большей универсальности, если предположить, что таких полей с подсказками на странице может быть больше одного, будем обращаться к подсказке не непосредственно указывая ее класс, а с помощью метода .prev(), к элементу в DOM-дереве,который предшествует полю ввода, то есть, к нашей подсказке. Причем по событию .blur() мы будем убирать подсказку, только если метод .val() ничего не возвращает, то есть в поле ввода пусто:

JavaScript

    $('.input-text').blur(function() {
      if ($(this).val() == '') $(this).prev().removeClass('hide');
    });
    
    $('.input-text').focus(function() {
        $(this).prev().addClass('hide');
    });
    

Также, учитывая перфекционистские наклонности автора :) и возможности браузеров (кроме Opera) драг-энд-дропить текст в поля ввода, продумаем и эту деталь.

Итак, если мы перетащим текст в какой-либо версии эксплорера или WebKit-браузере в текстовое поле – оно получит фокус и сработает событие .focus(), поведение при котором уже было описано и подсказка исчезнет. Но в Mozilla FireFox этого почему-то не происходит и .focus() не срабатывает. Мне в голову не пришло ничего более умного, чем проверять методом .mouseover(), находится ли курсор с перетаскиваемым текстом над полем ввода и если поле ввода было не пустым, принудительно прятать подсказку:

JavaScript

    $('.input-text').mouseover(function() {
      if ($(this).val() != '') $(this).prev().addClass('hide');
    });
    

Некоторые браузеры отображают ранее заполненную информацию при простой перезагрузке страницы (не по ctrl+F5). В частности это делает Mozilla FireFox и интернет эксплореры. Поэтому нужно проверить, не пустое ли поле после перезагрузки страницы. В этом случае, тоже исходя из предположения, что полей ввода может быть больше чем одно, дабы обращаться к каждому полю ввода при помощи указателя this, воспользуемся методом .each(), который будет матчить все поля ввода с соответствующим классом:

JavaScript

    $('.input-text').each(function(){
        if($(this).val() != '') $(this).prev().addClass('hide');
    });
    

Теперь вся jQuery-конструкция выглядит следующим образом:

JavaScript

    $(document).ready(function() {

      $('.input-text').each(function(){
          if($(this).val() != '') $(this).prev().addClass('hide');
      });

      $('.input-text').blur(function() {
          if ($(this).val() == '') $(this).prev().removeClass('hide');
      });

      $('.input-text').focus(function() {
          $(this).prev().addClass('hide');
      });

      $('.input-text').mouseover(function() {
          if ($(this).val() != '') $(this).prev().addClass('hide');
      });

    });
    

В итоге должно получиться нечто подобное.

Теперь доработаем немного удобность пользования полем с помощью CSS.

Как видно на тестовой страничке предыдущего примера, при перетаскивании текста (напомню, в браузере Opera такой возможности нет) подсказка загораживает основную часть поля ввода и курсор принимает соответствующий угрожающий значек :).

Приходится водить мышью и искать, где бы все-таки можно отпустить текст (К примеру в поле поиска Яндекс-почты так и сделано).

Казалось бы, все просто: убираем z-index:1; у подсказки #js .placeholder label, делаем прозрачным поле ввода, отменяя у него бэкграунд — background:none;, но предательский курсор над подсказкой в эксплорерах прекрасно продолжает мешать драг-энд-дропу.

Но решение, как обычно странное, все же есть ;)

Специально для эксплореров задаем бэкграундом изображение — //background:url(‘bg.gif’);, причем экслорерам все равно, есть оно на сервере или нет, подсказка уйдет под поле ввода в обоих случаях.

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

Дополненный CSS-код:

CSS

    .placeholder-form {
      min-width:1000px;
      padding:5em;
      position:relative;
      overflow:hidden;
      }
      .placeholder {
          width:90%;
          position:relative;
          _zoom:1;
          }
          .placeholder label {display:block;}
              .marked-text-true {color:#009933;}
              .marked-text-false {color:#ff0000;}
          .input-text {
              display:block;
              margin:.2em 0 .5em 0;
              width:90%;
              border:1px solid #ccc;
              position:relative;
              padding:.1em 60px .1em .3em;
              background:none;
              //background:url('bg.gif');
              }
          .placeholder label,
          .input-text {
              font-size:3em;
              line-height:1em;
              color:#5e5e5e;
              }
          #js .placeholder label {
              position:absolute;
              top:.2em;
              _top:.45em;
              left:.3em;
              cursor:text;
              }
              .hide {left:-9999em !important;}
    

Итак, окончательный вариант подсказки.

Теперь займемся реализацией нашей «очищалки» содержимого поля.

Роль «очищалки» у нас будет выполнять <span> с классом clear-text. Разместим его сразу после текстового поля:

HTML

    <form class="placeholder-form" action="#" method="post">
        <div class="placeholder">
            <label for="input-name" class="input-hint">
              А как 
              <span class="marked-text-false">вы</span> 
              относитесь к 
              <span class="marked-text-true">семантике</span>?
            </label>
            <input class="input-text" type="text" id="input-name" value="" >
            <span class="clear-text"></span>
        </div>
    </form>

    

И назначим ему соответствующие стили:

CSS

    .clear-text {display:none;}
    #js .clear-text {
        position:absolute;
        top:19px;
        _top:29px;
        right:42px;
        width:30px;
        height:30px;
        background:url("images/clear-cross.png") no-repeat;
        cursor:pointer;
        z-index:1000;
        display:none;
        }
        #js .clear-text:hover {background-position:0 -30px;}
        #js .ct-show {display:block;}
    

С ховером для IE6 возиться не стал, да простят меня суровые его пользователи :)

Для показа/скрытия будем задавать/удалять класс ct-show.

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

Как и в предыдущих вариантах, будем стараться сделать код универсальным, обращаясь к нужному полю посредством указателя this используя методы .parent() — родительский элемент — блок с класом placeholder и метода .find(), с помощью которого мы будем искать в текущем блоке placeholder текущее поле ввода input-text:

JavaScript

    $('.clear-text').click(function() {
        $(this).parent().find('.input-text').val('').focus();
        $(this).removeClass('ct-show');
    });
    

«Очищалка» должна появляться сразу после ввода первого символа, а также исчезать, после удаления последнего. Для этого воспользуемся событием .keyup() — по «отжатию» кнопки клавиатуры. Не .keydown() — потому что, тогда пришлось бы нажимать еще раз клавишу для появления или исчезновения «очищалки»:

JavaScript

    $('.input-text').keyup(function() {
        if($(this).val() != '') {
            $(this).parent().find('.clear-text').addClass('ct-show');
        }
        else {$(this).parent().find('.clear-text').removeClass('ct-show');}
    });
    

Также как и в первом случае с драг-энд-дропом, добавим появление «очищалки» в уже имеющееся условие специально для FF и WebKit:

JavaScript

    $('.input-text').mouseover(function() {
        if ($(this).val() != '') {
            $(this).prev().addClass('hide');
            $(this).parent().find('.clear-text').addClass('ct-show');
        }
    });
    

А также, для тех браузеров, которые после простой перезагрузки запоминают текстовые поля. Добавим в уже имеющуюся логику строчку для появления «очищалки» — если поле не пусто:

JavaScript

    $('.input-text').each(function(){
        if($(this).val() != '') {
            $(this).prev().addClass('hide');
            $(this).parent().find('.clear-text').addClass('ct-show');
        }
    });

    

Теперь вся jQuery-конструкция будет выглядеть так:

JavaScript

    $(document).ready(function() {

      $('.input-text').each(function(){
          if($(this).val() != '') {
              $(this).prev().addClass('hide');
              $(this).parent().find('.clear-text').addClass('ct-show');
          }
      });

      $('.input-text').blur(function() {
          if ($(this).val() == '') $(this).prev().removeClass('hide');
      });

      $('.input-text').focus(function() {
          $(this).prev().addClass('hide');
      });

      $('.input-text').mouseover(function() {
          if ($(this).val() != '') {
              $(this).prev().addClass('hide');
              $(this).parent().find('.clear-text').addClass('ct-show');
          }
      });

      $('.clear-text').click(function() {
          $(this).parent().find('.input-text').val('').focus();
          $(this).removeClass('ct-show');

      });

      $('.input-text').keyup(function() {
          if($(this).val() != '') {
              $(this).parent().find('.clear-text').addClass('ct-show');
          }
          else {$(this).parent().find('.clear-text').removeClass('ct-show');}
      });

    });
    

В итоге, помимо «умной подсказки», к нашему полю добавится еще и «умная очищалка». Страница с примером.

Еще один неприятный и непонятный пока мне момент с IE. При добавлении в JS-код изменений для «очищалок», с интернет эксплорерами начинает происходить странная штука, в любом поле ввода, если его не заполнять, а просто установить курсор, при простой перезагрузке данное поле будет «как-бы в фокусе», но при этом .focus() срабатывать не будет, и подсказка будет отображена всеравно, пока не произойдет нажатие клавиши, или «перефокусировка» (.blur() а потом опять фокус в это поле). Для решения этой проблемы достаточно вызвать .blur() для всех полей при перезагрузке страницы:

JavaScript

    $('.input-text').each(function(){
        $(this).blur(); // чтобы сбросить фокус со всех полей при перезагрузке страницы в ie
        if($(this).val() != '') {
            $(this).prev().addClass('hide');
            $(this).parent().find('.clear-text').addClass('ct-show');
        }
    });
    

Работу нескольких полей, одного только с подсказкой, а двух других — с подсказкой и «очищалками», можно посмотреть на этой демо-странице.

Скачать архив всех тестовых страниц и демо-страницу с комментариями в коде, можно отсюда.