spam poison

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


Какой метод использовать для добавления массива CSS-правил в сам HTML? Естественно, что таких вариантов существует несколько, и дальше они все будут рассмотрены с точки зрения производительности в клиентском браузере.


Заметка Выносим 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 в таких задачах: по возможности, стоит избегать динамических стилей для этого браузера.

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



Article information
Wroted: Mon, 17 Nov 2008 14:49:00 EET
Autors: Николай Мациевский a.k.a sunnybear
Added by: AlexParamonov at Sun, 15 Feb 2009 14:49:59 EET
Modified by: AlexParamonov at Fri, 27 Mar 2009 16:12:23 EET
Translation information
Added by: AlexParamonov at Sun, 15 Feb 2009 14:52:12 EET
Modified by: AlexParamonov at Sun, 15 Feb 2009 15:04:08 EET
Referenses
Copyright © 2008-2010 Alexander Paramonov
Valid XHTML 1.0 Transitional