Pull to refresh

Исследование на тему замены стандартных кнопок

Reading time 8 min
Views 2.7K
В процессе работы над интерфейсом одного продукта, появилась надобность в изготовлении собственного дизайна кнопок. За это время код, который заменяет стандартную кнопку на требуемую несколько раз переписывался и в данный момент тоже далёк от идеала. Учитывая все текущие проблемы кросс-браузерности, за это время выяснились и получилось нижеописанное.

Допустим, что она должна выглядеть примерно так:



Кнопка должна:
  1. быть inline-блоком
  2. иметь возможность наследовать стилизацию родителя (в пределах возможностей)
  3. Наследовать некоторые свойства и методы родителя.

На первом этапе я попробовал максимально минимизировать код, создавая подобную кнопку двумя контейнерами один из которых вложен во второй со смещениями для отображения красивого бэкграунда с закруглениями, в конце концов, этого не достаточно для того что бы в процессе была возможность наследовать свойства и методы самой кнопки, то есть сама кнопка как элемент должна присутствовать внутри контейнеров (<span><div><button/></div></span>).

Для упрощения эксперимента используем jQuery,

Спустя некоторое время выяснилось, что крайне сложно под IE, FF, Opera контролировать размеры и внешний вид всех контейнеров с кнопкой вкупе c помощью обычных CSS. Результат превратился в борьбу хаков над стилями. Тут же я впервые столкнулся с надобностью написания различных стилей для FF2 и FF3.

В конце концов за основу была взята концепция, используемая в ExtJS, а именно — таблица.

[левый край|кнопка|правый край]

В связи с этим, количество вопросов с позиционированием значительно сократилось, и вместе с ним — количество CSS.

Чит-коды



Допустим, что исходные кнопки задается следующим образом
<input type='submit' value='Submit' class='replaceMe w100'>
<input type='button' value='Pushme' class='replaceMe ico ico-image' disabled='disabled'>


В данном случае у нас получается что класс replaceMe будет являтся селектором для замены, класс w100 — вспомогательный класс (допустим, отвечающий за ширину кнопки), который должен будет наследоваться. ico и ico-image — классы отвечающие за иконки.

Картинка-спрайт для прототипа:
image

CSS прототипа:
.ico {
  padding-left: 20px !important;
  padding-bottom: 1px !important;
  background-position: 0px 0px;
  background-repeat: no-repeat;
}

.ico-image {  background-image: url("page_tick.gif") !important;}

/* Перебиваем все настройки которые могли бы помешать дизайну свыше*/
.xBtn tr td {
  border: 0 none !important;
  border-bottom: 0 none !important;
  padding: 0;
  font:normal 11px sans-serif, tahoma, verdana, helvetica ;
  height: 21px;
  min-height: 21px;
}

/* Стиль таблицы */
.xBtn {
  cursor:pointer;
  border-collapse: collapse;
  white-space: nowrap;
  display: inline;
  width: auto;
}

/* Стиль кнопки внутри центральной ячейки. Убираем все возможные отступы, пробелы.
* Имеем ввиду, что у IE есть такая странность: Добавлять лишние пробелы по краям кнопки,
* нечто походящее на padding: 0 1.3em;
* */
.xBtn button {
  border:0 none;
  background:transparent no-repeat;
  font:normal 11px tahoma,verdana,helvetica;
  padding-left:3px;
  padding-right:3px;
  height: 21px;
  cursor:pointer;
  margin:0;
  overflow:visible;
  width:auto;
  -moz-outline:0 none;
  outline:0 none;
}

/* Допольнение для IE */
* html .xBtn button {width: 1px;}
*+html .xBtn button {width: 1px;}
*+html .xBtn button {padding-top:3px;}

/* если кнопка будет с иконкой */
.xBtn .xBtn-text-ico {
  background-position: 0 0px;
  background-repeat: no-repeat;
  height: 16px;
  padding: 0 0 2px 18px;
  margin-top: 1px;
}

/* А возможно это будет просто текст */
.xBtn .xBtn-text {
  background-position: 0 0px;
  background-repeat: no-repeat;
  padding-top:0px;
  padding-bottom:2px;
  padding-right:0;
  margin-top: 1px;
  height: 16px;
}

/* борьба с IE */
*+html .xBtn .xBtn-text {  padding-top:1px;  margin-top: 2px;}

.xBtn-Left, .xBtn-Right {
  font-size:1px;
  line-height:1px;
  width:3px;
  height:21px;
}

.xBtn-Left   {  background: url(btn-sprite.png) no-repeat 0 0;}
.xBtn-Right {  background: url(btn-sprite.png) no-repeat 0 -21px;}

.xBtn .xBtn-Left i, .xBtn .xBtn-Right i {
  display:block;
  width:3px;
  overflow:hidden;
  font-size:1px;
  line-height:1px;
}

.xBtn .xBtn-Center {
  background:url(btn-sprite.png) repeat-x 0 -42px;
  vertical-align: middle;
  text-align:center;
  cursor:pointer;
  white-space:nowrap;
}

.xBtn-over .xBtn-Left{    background: url(btn-sprite.png) repeat-x 0 -63px !important; }
.xBtn-over .xBtn-Right{   background: url(btn-sprite.png) repeat-x 0 -84px !important; }
.xBtn-over .xBtn-Center {  background: url(btn-sprite.png) repeat-x 0 -105px !important;}
.xBtn-click .xBtn-Left {  background: url(btn-sprite.png) repeat-x 0 -126px !important;}
.xBtn-click .xBtn-Right {  background: url(btn-sprite.png) repeat-x 0 -147px !important; }
.xBtn-click .xBtn-Center {  background: url(btn-sprite.png) repeat-x 0 -168px !important;  }

.xBtn em {
  font-style:normal;
  font-weight:normal;
  height: 16px;
}


* This source code was highlighted with Source Code Highlighter.


И собственно, сам код:
$('.replaceMe').each(function(){
  // у исходника тут же убираем уже ненужный класс
  $(this).removeClass('replaceMe');

  // Создаем таблицу
  var BtnTable = document.createElement('table');
  var BtnTableRow = BtnTable.insertRow(0);
  var LeftBtnCell = BtnTableRow.insertCell(0);
  var CenterBtnCell = BtnTableRow.insertCell(1);
  var RightBtnCell = BtnTableRow.insertCell(2);
  
  // Что бы ячейки таблицы небыли пустыми,
  // создаем для них какие-нибуть контролируемые элементы DOM
  var newBtnContainer = document.createElement('em');
  var newBtnSideLContainer = document.createElement('i');
  var newBtnSideRContainer = document.createElement('i');

  // Назначаем классы, вставляем в контейнеры
  $(LeftBtnCell).addClass('xBtn-Left').append(newBtnSideLContainer);
  $(RightBtnCell).addClass('xBtn-Right').append(newBtnSideRContainer);

  // Замечательный атрибут,
  // предотвращающий выделение текстовой информации внутри блока
  newBtnContainer.setAttribute('uselectable', 'on');

  $(BtnTable)

    // Назначаем класс для самой таблицы-кнопки
    .addClass('xBtn')

    // Переносим из исходной кнопки ее значение в атрибут Title, для красоты эксперимента
    .attr('title', $(this).attr('value') || '')

    // Изменение классов при наведении и клике на таблице-кнопке
    .hover(
      function(){
        if ($('button:enabled', $(BtnTable)).length) $(this).addClass('xBtn-over');
      },
      function(){
        $(this).removeClass('xBtn-over');
        $(this).removeClass('xBtn-click');
      }
    )
    .mousedown(function(){
      //$(newBtn).focus();
      $(this).addClass('xBtn-click');
    })
    .mouseup(function(){
      $(this).removeClass('xBtn-click');
    });

  // Будем считать, что иконки на кнопках будут задаваться двумя классами
  // первый из которых будет отвечать за место под кнопку, второй - за само изображение.
  // ico ico-image

  // Определяем, есть ли у нас иконка, и что за...
  var xBtnClasses = this.className.split(' ');
  var hasIco = $(this).hasClass('ico');
  var icoClassName = '';
  for (var i = 0; i < xBtnClasses.length; i++ ) {
    if (xBtnClasses[i].toString().match(/ico-\w+/)) icoClassName = xBtnClasses[i].toString();
  }

  // Убираем из исходника классы отвечающие за иконки.
  if (hasIco && icoClassName) {
    $(this).removeClass('ico').removeClass(icoClassName)
  }

  // Копируем в центральную ячейку с будущей кнопкой классы из исходника (оставшиеся классы)
  // Обозначаем ее собственным классом и вставляем в нее контейнер для будущей кнопки
  $(CenterBtnCell).append(newBtnContainer).addClass(this.className).addClass('xBtn-Center');;
  
  // Копируем событие onclick исходника
  var onClickEv = $(this).attr('onclick');

  // Если есть какое событие, то устанавливаем его на всю таблицу-кнопку
  if (jQuery.isFunction(onClickEv)) {
    $(BtnTable).bind('click', function(e){
      onClickEv();
    });

  // Если нет события, то может быть наш родитель был submit-ом?
  // Нажимаем на кнопку и ждем спецэффектов
  } else if ( this.type == 'submit' ) {
    $(BtnTable).bind('click', function(e){
      if ($(this).find('button').length) {
        var f = $(this).find('button')[0];
        f.click();
      }
    });
  }
  
  // Скрываем нашего прородителя и вставляем перед ним прототип нашей кнопки
  $(this).hide().before(BtnTable);

  // Вместо кода написанного ниже сначала была попытка создать кнопку методами JavaScript.
  // Но в связи с существующей проблемой под IE, которая запрещает изменять атрибут type
  // кнопку создаем обычным способом:
  
  var Btn = '<button ' +
    // Трансплантируем тип кнопки
    'type="' +  ($(this).attr('type') || 'button') +  '" ' +
    
    // Назначаем ID исходника
    'id="' +  this.id +  '" ' +
    
    // Передаем классы: иконок, если есть,
    'class="' +  ((hasIco && icoClassName) ? 'xBtn-text-ico ico ' + icoClassName : 'xBtn-text') + (($(this).attr('disabled')) ? ' disabled"' : '') + '" ' +
    
    // Передаем остальные аттрибуты
    (($(this).attr('disabled')) ? 'disabled="disabled"' : '') +
    'name="' +  $(this).attr('name')  +  '" ' +
    'title="' +  $(this).attr('title')  +  '" ' +
    'style="' +  (($(this).attr('value') == '') ? 'width:16px;' : '')  +  '" ' +
    '>' + $(this).attr('value') +
    '</button>';

  // Tadaaaa!
  newBtnContainer.innerHTML = Btn;

  // Небольшие жонглирования с шириной под IE, котрый не совсем понимает, что такое ширина, когда нет содержимого текста
  if ($.browser.msie && $(this).attr('value') != '') {
    if ($(CenterBtnCell).width()) {
      $(CenterBtnCell).find('button').css('width', $(CenterBtnCell).width() + 'px');
    } else {
      $(CenterBtnCell).find('button').css('width', TextMetrixWidth(this) + 18 + 'px');
    }
  }

  $(this).remove();
});

* This source code was highlighted with Source Code Highlighter.


Большое спасибо за внимание. Надеюсь что знания почерпнутые мной в процессе работы пригодились.

update: Демонстрация работы, по просьбам трудящихся.
Tags:
Hubs:
+39
Comments 113
Comments Comments 113

Articles