Статьи Архив статей

Автор: Мациевский Николай aka sunnybear
Опубликована: 17 ноября 2008

Динамические стили: быстро и просто

Заметка Выносим 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 в bodyXHR в headБыстрый XHR в headDOMDOM JS
IE6482379342335385
IE7532364391353373
IE8b2370326301284332
FX3420294300282271
Opera98928941287764798
Safari3-308286296297
Chrome-349335367364

Выводы

Как хорошо видно из таблицы, наиболее быстрым способом для динамического добавления стилей в документ являются DOM-методы почти во всех случаях. Для Safari/Chrome вставка через XHR и специальные методы оказывается быстрее (но не намного). Отдельно хочется отметить довольно медленную работу Opera в таких задачах: по возможности, стоит избегать динамических стилей для этого браузера.

Естественно, тут речь идет о выигрышах лишь в десятки и сотни миллисекунд. Но если с самого начала применять самые оптимальные методы при разработке, то ситуации, когда веб-приложение уже тормозит на несколько секунд (просто загружая процессор на пустом месте), можно будет с легкостью избежать. Ведь на том этапе, когда задержки станут явными, находить и устранять их намного сложнее, чем при изначальном проектировании.

Читать дальше

Все комментарии (habrahabr.ru)