Заметка Выносим 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>';
}
DOM-метод
И наконец, хит сезона. Добавляем новый файл стилей прямо в 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');
DOM через внешний JavaScript
В документ добавляется JavaScript-файл (DOM-методами), через который уже добавляется строка стилевых правил. Способ предложен
sirus. В HTML-документе:
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 в таких задачах: по возможности, стоит избегать динамических стилей для этого браузера.
Естественно, тут речь идет о выигрышах лишь в десятки и сотни миллисекунд. Но если с самого начала применять самые оптимальные методы при разработке, то ситуации, когда веб-приложение уже тормозит на несколько секунд (просто загружая процессор на пустом месте), можно будет с легкостью избежать. Ведь на том этапе, когда задержки станут явными, находить и устранять их намного сложнее, чем при изначальном проектировании.