Когда <button> не лучший выбор (она медленнее создается, отображается и с трудом стилизуется)

Перевод статьи When <button> isn’t the best choice (it’s slow to create and render and problematic to style) с сайта benfrain.com, автор — Бен Фрейн.

Элемент <button> значительно медленнее при создании в JavaScript, он медленнее отображается в браузере и крайне трудно стилизуется кроссбраузерно. Несмотря на то, что <button>, это «правильный» выбор для задач кнопки, это не обязательно должен быть «лучший» выбор.

Представьте картину. Я пытаюсь убедить главного инженера программного обеспечения использовать более семантические элементы в разметке, а не только общие элементы, типа div  и span. Такие семантические элементы, как sectionheaderfooter и nav. Разговор выглядел примерно следующим образом. «Нам нужно проверить, что они не медленнее при создании в JavaScript», — говорит он. «Конечно, нет проблем», — отвечаю я, с полной уверенностью.

Тогда-то я и понял, что для кнопки это добром не кончится. Видите ли, button немного особенный. Он как радиокнопки или группы полей: он делает «нечто».

У вас есть несколько минут? Я хочу рассказать, почему ратовать за использование элемента button  может быть гиблым делом для больших веб-проектов.

Тестирование производительности кнопок при помощи createElement в JavaScript

Прежде всего мне нужны были данные, чтобы гарантировать, что создание семантических элементов (sectionnavabutton и т.д.) в JavaScript не медленнее чем создание div или span.

Я не пишу умные тесты. Я не достаточно умён для этого. Я определённо не прошёл бы тест FizzBuzz. Поэтому я создал что-то более простое, но ,надеюсь, достаточно наглядное. Я сделал почти одинаковую страницу для каждого рассматриваемого элемента. Кроме тега h1 для заголовка, больше разметки не было. Вместо этого сниппет на JavaScript внизу каждой страницы создавал 200000 рассматриваемых элемента и добавлял их в тело страницы. Для каждой страницы эта функция выглядела так:

;(function TimeThisMother() {

 window.onload = function(){

  setTimeout(function(){

  var t = performance.timing;

   for (var index = 0; index < 200000; index++) {

 var makeDiv = document.createElement("button");

 makeDiv.classList.add('a-button');

 makeDiv.textContent = 'это число кнопок ' + index;

 document.body.appendChild(makeDiv);

   }

   console.log("JS took: " + ( (t.loadEventEnd - t.responseEnd) / 1000) + " секунд, требующихся на создание элемента");

 }, 0);

 };

})();

Ясно, что добавлять столько элементов на страницу нелепо. Однако, это позволяет нам чётко видеть (вы сможете увидеть разницу своими глазами) разницу в производительности между этими элементами.

Время загрузки создания элементов при помощи JavaScript

Я также использовал «тайминги навигации», чтобы измерить, сколько времени потребовалось JavaScript для создания элементов. Это значение записывается в консоли в инструментах разработчика. Однако, я не думаю, что это о многом говорит (помимо того, что Safari ЧЕРТОВСКИ быстрее по сравнению с Chrome/Firefox). Намного показательнее то, что после того, как элементы созданы, они должны отобразиться на странице. Вот где открывается настоящая пропасть между button и, всем остальным.

Тесты с createElement для каждого элемента

Нажмите любую из следующих ссылок, и как только вы это сделаете, запустите секундомер. Остановите его, когда увидите контент, который появится под тегом h1. Повторите этот текст и я думаю, вы получите такой же результат, что и я: button тормозит. Почти всегда в два раза медленнее чем любой другой элемент. В Chrome у меня на локальной машине любые из структурных элементов (divsectionnav и т.д.) занимают около 7 секунд. button занимает около 14 секунд.

Кроме того, если вам угодно взглянуть на результаты, которые я запустил через Web Page Test, то вот сравнение div и button:

  • div отображается за 9.6 секунд (среднее по 3 запускам)
  • button отображается за 15.38 секунд (среднее по 3 запускам)

Чёёё? Вот, попробуйте сами. Попробуйте сейчас же:

Если вы желаете посмотреть эти примеры на GitHub, то вы можете сделать это здесь: https://github.com/benfrain/css-performance-tests

Кнопка, чё за фигня? Это из-за твоих стилей?

Моей первой мыслью было, что это должно быть из-за стилей. Во всех браузерах на <button> навешивается столько визуального багажа из браузерных стилей, что это наверняка сказывается на времени отрисовки? В попытке нейтрализовать браузерную таблицу стилей (на данный момент я проверял в основном в Chrome), я добавил следующие переопределения:

button.a-button {

 height: 30px;

 width: 100%;

 display: block;

 font: inherit;

 text-align: initial;

 padding: 0;

 box-sizing: content-box;

 border: 0;

 outline: 0;

 background-color: wheat;

 border-bottom: 1px solid #f90;

 text-transform: inherit;

 text-rendering: inherit;

 letter-spacing: inherit;

 text-indent: inherit;

 -webkit-appearance: none;

 -moz-appearance: none;

 appearance: none;

}

Кроме этого, я ещё до сих пор не могу понять, как добиться, чтобы текст начинался вертикально в том же самом месте, где и у других элементов (просто привести к единому виду раскладку между протестированными элементами)!

Кстати, если вы вдруг не знали, appearance: none; возвращается в стандарты. Оно определено в CSS-модуле базового пользовательского интерфейса 4 уровня. Однако, ни сейчас, а может, и никогда в будущем он не даст нам полного визуального контроля над кнопками.

Это палка о двух концах браузерных таблиц стилей для элемента button. Все браузеры не дают некоторым элементам полностью управляться через CSS. Тем не менее, вот к какой двойственной ситуации это приводит:

Браузер хочет, чтобы button всегда выглядела как button, так что люди могут всегда видеть, что это button (доступность — это хорошо, окей).

Разработчики хотят, чтобы button-ы выглядели так, как им хочется, а не так, как явные button-ы с дефолтными браузерными стилями (эстетика важна, окей)

Кроме того, если я не ошибаюсь, Firefox даже не позволяет button быть flex-контейнером (и это, наверное, правильно, согласно спецификации)

Мы можем плясать вокруг этого целый день, но я всё-таки осмелюсь предположить, почему люди не используют элементы button; они выглядят ужасно и никакие эпизодические CSS-кодеры не знают, как заставить их вести себя как надо.

Изначально я был убеждён, что браузерные стили были единственным важнейшим источником проблемы со скоростью, но когда я связался с ребятами из команды разработчиков Chrome, они добавили еще версии:

Ben Frain @benfrain

Тест показывает, что элемент button в два раза медленнее создавать + отображать, чем div при помощи createElement(). Это нормально?.

@benfrain @paul_irish

Можно мне увидеть тест? Я могу представить, что теневая DOM для button гораздо сложнее, чем для простого div, так что да.

Говоря о button в связи с Flex, Даниел Холберт высказал интересную догадку в этом комментарии на bugzilla.

<button> не реализуется (браузерами) на чистом CSS, поэтому с точки зрения CSS они немного «чёрные ящики». Это означает, что они не обязаны вести себя так же, как, например, и <div>.

Хм, больше информации, больше проблем: я хочу использовать button там, где это имеет смысл с точки зрения DOM, семантически. Однако, я не хочу дефолтных стилей для состояний. Я хочу добавлять их сам. Видимо, это тупик?

В этом момент, я представляю хипповатых сторонников чистой семантики, неодобрительно покачивающих головами, поглаживая свою бороду (в том числе женщины) и потягивая шоколадное мокачино из экологически чистых зерен с молоком. Я знаю, что они думают: я так легко был побеждён, я сдался без боя армии супа из div.

Не знаю, что вам сказать. Я не горжусь этим, но правда в том, что button в этом сценарии — сплошная боль.

Вот аналогия. Это напоминает мне, как я хожу выпить с моим братом. К концу вечера он нажирается сверх меры, начинает дурить и втягивает нас обоих в переделку. Я защищаю его, он же мой брат (девушки, я полагаю, вам надо быть благоразумнее), но между нами — он полный балбес, признаю. В следующий раз, когда брат позовет меня по городским заведениям, я откажусь.

Но я отвлёкся.

Заключение

Смотрите, я допускаю, что кнопка по умолчанию делает такие вещи, которые другие элементы не делают. У неё есть состояния, оформление по умолчанию, и самое главное — смысловое (понятное) значение. Для подавляющего большинства «кнопочных» сценариев использования, button — «правильный выбор».

Но спецификация и браузеры ничего не предусмотрели на случай, если нам нужны семантические элементы без багажа. Я уверен, что есть причина, по которой мы не можем это делать, но наша проблема от этого никуда не денется.

Мы все читали статьи, в которых нам говорили, что мы должны использовать button конкретно для кнопок (в отличие от ссылок или иных элементов). Я полностью согласен с этим утверждением; это моя позиция по умолчанию. Вам ведь не понадобится 200000 элементов button на странице (если только вы не делаете полный каталог для фирмы «Мир кнопок»), так что это не проблема?

Нет, это реальная проблема. Вполне возможно, что организации/продукты, которым приходится выбирать между использованием «правильного» или самого быстрого элемента, сделают выбор в пользу самого быстрого. Когда каждая миллисекунда на счету, любой проигрыш в скорости для элемента может стать началом медленного мучительного конца.

Я никоим образом не призываю отказываться от button. Если можно использовать её, используйте. Вместо этого я надеюсь, что в будущем появится способ использовать элемент button с семантической точки зрения, без браузерного багажа и не жертвуя скоростью. Наверное, лучшее, чего мы можем добиться в этой ситуации сейчас — обеспечить, чтобы у нашего элемента хотя бы стоял role=«button» для скринридеров.

До тех пор, пока W3C не предоставит что-нибудь типа button-style:none;, разработчики будут снова и снова выбирать другой элемент. Из-за трудностей при визуальном изменении button и сравнительной разницы в производительности становится труднее искренне утверждать: «Для этого нужно использовать button».

P.S. Это тоже может быть интересно:

11 Комментарии

  1. AntonMMF

    Глупости какие-то.

    Первое.

    «Ясно, что добавлять столько элементов на страницу нелепо. Однако, это позволяет нам чётко видеть (вы сможете увидеть разницу своими глазами) разницу в производительности между этими элементами»

    Интерпретируется как «Видно, что в реальной жизни подобная ситуация дай бог будет раз в 10 лет, но всё равно button медленее, дадада». И после этого куча начинающих верстальщиков перестанет использовать в простых сайтах button, потому что «дядя написал, что они медленные». И за подобным примером ходить далеко не надо, я уже видел немало подобных людей, которые используют только id, потому что «на хабре когда-то писали, что классы медленные». И подобное приходится выбивать из головы.

    Второе.

    «Элемент button крайне трудно стилизуется кроссбраузерно».

    Опять же, в проектах использую объёдинённые reset.css и normalize.css, после этого не имею никаких проблем со стилизацией кнопок. На крайний случай внутреннее содержимое можно обернуть в div/span/чтоугодно и работать уже с обёрткой.

    Третье.

    Button в реальных проектах с БЭМ-методологией именования классов очень полезен. Как именно? В форме может быть куча инпутов различных типов: tel, text, email. И так как в форме у нас всегда будут инпуты, а не section, для заполнения данных, то можно удобно писать что-то вроде:

    .callbackFrom input { все стили нужные нам разом }

    Только при такой записи input[type=»submit»] возьмёт на себя все эти стили. Можно конечно использовать :not, но зачем городить чёрти что, когда можно использовать button, который, кстати говоря, с вертикальным выравниванием текста внутри себя более послушный, чем input[type=»submit»]

    1. SelenIT

      На мой взгляд, автор в полушутливой манере как раз объясняет, почему начинающие (да порой и не только) верстальщики/фронтендеры уже не используют правильный тег для кнопки, заменяя его несемантичной, зато легкоуправляемой отсебятиной (типа «псевдонессылок»). И указывает на причину такого упрямого поведения button-ов — более сложное внутреннее устройство (в Chrome и его родне реализованное через теневую DOM), тянущее за собой дополнительные накладные расходы и, кроме прочего, замедляющее построение и отрисовку. Что, конечно же, ерунда для сайтов, но действительно может стать существенным для высоконагруженных приложений — так что «загоняться» этим не стоит, но знать о такой особенности, имхо, нелишне.

      Годами отточенный и отлаженный собственный нормалайз — конечно, хорошее подспорье, но новичкам вполне достаточно любой мелочи вроде того, что в Фоксе к кнопке добавляется «непонятный паддинг», а в IE она по умолчанию ведет себя как при overflow:hidden, чтобы заменить ее на «что попроще». А лично мне частенько не хватало возможности задать кнопке честный display:inline, для визуального единообразия со ссылками. И да, без доп. обертки (а то и двух) очень часто кнопку не укротить…

      На мой взгляд, статья Бена полезна хотя бы тем, что привлекает внимание разработчиков к важности стандартного метода сброса/минимизации браузерных стилей для контролов и напоминает о хорошем кандидате на эту роль — appearance:none. Но согласен, что некоторые выводы у автора получились двусмысленными и даже скользкими, так что спасибо за отличное замечание!

    2. Сергей

      Полностью согласен с примером стилизации input-ов, часто приходится копаться в чужом коде, и дико раздражает когда вместо button[type=submit] используют input.

      1. SelenIT

        В старом коде input вместо button мог использоваться еще ради совместимости с IE7-. Старые IE при сабмите формы вместо value нажатой кнопки (как надо) отсылали… innerHTML всех кнопок в форме, поэтому определить на сервере, какую из нескольких кнопок нажали, было невозможно. Еще одна историческая причина народной нелюбви к button…

      2. AntonMMF

        Можете дать какие-нибудь примеры использования button[type=button]? Я просто никогда у button в стилях не указываю тип, т.к. даже придумать не могу где бы я мог использовать тип button.

        1. Сергей

          Вы правы, написал тип только для того чтобы было более понятно. Лично я использую в разметке только , а в стилях только button без указания типа, так как использую я его только в формах.

          1. Сергей

            Вырезалось

            1. SelenIT

              Комментарии понимают базовый HTML, поэтому теги учитываются как теги. Чтобы вставить тег как текст, используйте &lt; и &gt;.

        2. SelenIT

          JS-калькулятор чего-нибудь, эффект в браузерном мини-инстаграмме на Canvas… Вообще везде, где нужна чисто клиентская функциональность приложения. На сайтах, согласен, редко (хотя я использовал для открытия вспомогательных выпадушек). Плюс опять же историческая причина — в тех же IE7 по умолчанию был type="button", и для отправки формы его приходилось явно переопределять.

  2. Максим

    А как он тесты запускал? В одной и той же вкладке по 3 раза каждый тест?

    У меня так nav стал генерироваться 5 секунд, хотя «a», «div» и «section» генерировались по 0.120 секунд.

    Я понял, что что-то не так, закрыл вкладку и запустил тест в икогнито окне (чтобы без разширений браузеров) и о чудо! JS took: 0.037 seconds to create the elements на тесте для button

    1. SelenIT

      Автор сам пишет, что время отработки самого JS не показательно, основная разница во времени отрисовки.

Оставить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Получать новые комментарии по электронной почте. Вы можете подписаться без комментирования.