Заметка Выносим CSS в пост-загрузку была посвящена исследованию наиболее быстрого способа добавить стилевые правила в исходный документ динамически, не затрагивая при этом стадию предзагрузки (когда у нас еще белый экран в браузере). В ней, однако, не был рассмотрен следующий вопрос: какой метод использовать для добавления массива CSS-правил в сам HTML.
Естественно, что таких вариантов существует несколько, и дальше они все будут рассмотрены с точки зрения производительности в клиентском браузере.
Поскольку скорость загрузки отдельного CSS-файла достаточна велика, а требуется рассмотреть, как его содержимое может повлиять на скорость его динамического применения к документу, то нам нужны сотни или даже тысячи правил. В качестве отправной точки была опять взята главная страница Яндекса, стили которой были вынесены в отдельный файл и скопированы 10 раз. Это дало необходимую задержку (которая существенно больше погрешности, вносимой браузерами) и не сильно увеличило сжатый с помощью gzip
файл.
Все варианты представлены на тестовой странице, вкратце опишу основные подходы.
XHR
в body
Выполняется XMLHttpRequest
к CSS-файлу, затем содержимое последнего вставляется через innerHTML
в body
документа. Случай был выбран просто как базовый, потому что большое число узлов в DOM-дереве делает такую операцию сразу менее эффективной, чем вставка в head
. Да и стили внутри тела документа запрещены стандартами.
// чтобы не копировать всем известный код, запишем там var xhr = new XMLHttpRequest; if (xhr) { xhr.onreadystatechange = function() { try { if (xhr.readyState == 4) { if (xhr.status == 200) { // вставим полученные данные прямо в body document.body.innerHTML += '<style type="text/css">' + xhr.responseText + '</style>'; } } } catch(e){} }; xhr.open("GET", 'styles.css?'+Math.random(), true); xhr.send(null); }
Тут сразу встает вопрос: как считать применение стилей? Ведь у браузера нет такого события. Немного подумаем и вспомним всем известный и раскритикованный подход с использованием offsetHeight
(который, конечно, не самый лучший выбор, добавляем ко всем случаям небольшую пропорциональную задержку, что совершенно допустимо в рамках метода).
Итак, для снятия времени применения CSS-правил использовалась следующая конструкция (которая запускалась сразу после обращения к внешнему файлу):
var start = new Date(); var _timer = setInterval(function(){ // находим известный элемент документа и проверяем, применились ли к нему стили if (document.getElementById('neck').offsetHeight < 300) { // сообщаем о применении alert('CSS files loaded in '+(new Date() - start)); // убиваем таймер clearInterval(_timer); } }, 10);
Естественно, что на время загрузки влияли сетевые задержки. Для борьбы с ними (и не только) бралась серия из 15 замеров, значения, превосходящие текущее среднее более чем в 2 раза, просто отбрасывались (для контроля 3-дельта выбросов).
Все результаты приведены в конце статьи.
XHR
в head
В этом случае код вставлялся уже в head
, и применялся ряд методов для разных браузеров (ибо не все хотели через innerHTML
или innerText
вставлять полученные данные).
var text = xhr.responseText; var head = document.getElementsByTagName('head')[0]; var style = document.createElement('style'); style.type = 'text/css'; // для IE if (style.styleSheet) { style.styleSheet.cssText = text; } else { // для Safari/Chrome if (style.innerText == '') { style.innerText = text; // для остальных } else { style.innerHTML = text; } } head.appendChild(style);
XHR
в head
В следующем варианте проверялось прямо добавление к innerHTML
в head
(для тех браузеров, которые это поддерживают) стилевых правил. Оказалось, что это вариант даже медленнее, чем предыдущий.
Если осуществлять это относительно обычного HTML, то DOM-дерево изменяется быстрее (в IE6/7), поэтому на данный момент практикуется именно такой подход в общем случае.
var text = xhr.responseText; var head = document.getElementsByTagName('head')[0]; if (/WebKit|MSIE/i.test(navigator.userAgent)) { var style = document.createElement('style'); style.type = 'text/css'; if (style.styleSheet) { style.styleSheet.cssText = text; } else { style.innerText = text; } head.appendChild(style); } else { head.innerHTML += '<style type="text/css">' + text + '</style>'; }
И наконец, хит сезона. Добавляем новый файл стилей прямо в head
при помощи DOM-методов.
var link = document.createElement('link'); document.getElementsByTagName('head')[0].appendChild(link); link.setAttribute('type','text/css'); link.setAttribute('rel','stylesheet'); link.setAttribute('href','style.css');
В документ добавляется JavaScript-файл (DOM-методами), через который уже добавляется строка стилевых правил. Способ предложен
var script = document.createElement('script'); document.getElementsByTagName('head')[0].appendChild(script); script.type = 'text/javascript'; script.src= '../style.js';
В самом JavaScript-файле:
(function(){ var style = document.createElement('style'); style.type = 'text/css'; var text = ' ... styles here ... '; if (style.styleSheet) { style.styleSheet.cssText = text; } else { if (style.innerText == '') { style.innerText = text; } else { style.innerHTML = text; } } document.getElementsByTagName('head')[0].appendChild(style); })();
Ниже приведена таблица по исследованным браузерам для всех вариантов. В ней указано время в миллисекундах, прошедшее от начала вызова внешнего файла до окончания применения всех стилей.
Браузер | XHR в body | XHR в head | Быстрый XHR в head | DOM | DOM JS |
---|---|---|---|---|---|
IE6 | 482 | 379 | 342 | 335 | 385 |
IE7 | 532 | 364 | 391 | 353 | 373 |
IE8b2 | 370 | 326 | 301 | 284 | 332 |
FX3 | 420 | 294 | 300 | 282 | 271 |
Opera9 | 892 | 894 | 1287 | 764 | 798 |
Safari3 | - | 308 | 286 | 296 | 297 |
Chrome | - | 349 | 335 | 367 | 364 |
Как хорошо видно из таблицы, наиболее быстрым способом для динамического добавления стилей в документ являются DOM-методы почти во всех случаях. Для Safari/Chrome вставка через XHR и специальные методы оказывается быстрее (но не намного). Отдельно хочется отметить довольно медленную работу Opera в таких задачах: по возможности, стоит избегать динамических стилей для этого браузера.
Естественно, тут речь идет о выигрышах лишь в десятки и сотни миллисекунд. Но если с самого начала применять самые оптимальные методы при разработке, то ситуации, когда веб-приложение уже тормозит на несколько секунд (просто загружая процессор на пустом месте), можно будет с легкостью избежать. Ведь на том этапе, когда задержки станут явными, находить и устранять их намного сложнее, чем при изначальном проектировании.