Примечание: ниже находится перевод статьи "CSS Expression Optimization", в которой автор немного освещает использование и проблематику динамических свойств в CSS (aka CSS expressions). Также автор предлагает способ их оптимизации (исполнение один-единственный раз вместо постоянного выполнения). Далее приведены несколько тезисов с ClientSide'2007 по заявленной тематике. Мои комментарии даны курсивом.
CSS expressions были впервые представлены в Internet Explorer 5.0, который позволял назначать JavaScript-выражение в качестве CSS-свойства. Например, следующий код позволит разместить элемент в зависимости от того, какого размера окно браузера.
#myDiv { position: absolute; width: 100px; height: 100px; left: expression(document.body.offsetWidth - 110 + "px"); top: expression(document.body.offsetHeight - 110 + "px"); background: red; }
Не самый лучший способ решения поставленной задачи, но в качестве примера его достаточно.
CSS expressions позволяют не только вычислить CSS-свойство при объявлении стилей, но и поддерживать его постоянно в актуальном состоянии, чтобы заданное выражение было всегда верно. Это означает, что само выражение будет пересчитываться каждый раз, когда (для данного примера) изменяется document.body.offsetWidth
. Если бы не этот факт, динамические выражения (dynamic expressions), возможно, принесли бы большую пользу и получили бы более широкое распространение. Но это не так, и пересчет этой строки происходит каждый раз, когда заканчивается вычисления JavaScript'а. Это означает, что если вы двигаете мышкой, и на странице присутствует обработчик onmousemove
, то все динамические выражения будут пересчитываться. И не нужно быть гением, чтобы понять, что это приведет ваше веб-приложение к «подвисанию».
Давайте рассмотрим следующий блок CSS-кода:
#myDiv { border: 10px solid Red; width: expression(ieBox ? "100px" : "80px"); }
Даже при том предположении, что ieBox
— это постоянный флаг, который выставляется в true
, когда IE находится в режиме обратной совместимости (quirks mode), заданное выражение будет вычисляться каждый раз в "80px"
(или "100px"
). Хотя выражение будет постоянным для данной страницы, оно все равно будет пересчитываться много раз. Основной вопрос заключается в том, как избавиться от этих ненужных вычислений.
Что мы собираемся сделать: пройтись по всем объявлениям стилей и заменить вычисление выражения его постоянным значением. В предыдущем примере, предполагая, что мы используем IE6 в стандартном режиме, нам хотелось бы видеть следующий код:
#myDiv { border: 10px solid Red; width: 80px; }
Итак, как нам убедиться в том, что наше выражение постоянно? Самым простым путем является пометить само выражение, чтобы мы могли его легко обнаружить. Решением в данном случае будет заключение выражение в вызов функции, которая нам известна и заранее объявлена.
function constExpression(x) { return x; }
Итак, в нашем CSS-блоке мы напишем следующее:
#myDiv { border: 10px solid Red; width: expression(constExpression(ieBox ? "100px" : "80px")); }
Во-первых, мы должны подключить библиотеку cssexpr.js
прежде, а только потом вызывать такие нашу функцию constExpression
.
<script type="text/javascript" src="cssexpr.js"></script>
После этого можно использовать constExpression
в любом задаваемом блоке стилей (<style>
), или любом подключаемом файле стилей (<link>
), или при использовании директивы @import
. Следует заметить, что атрибут style у тегов не проверяется для ускорение работы.
Идея заключается в том, чтобы перебрать все объявленные таблицы стилей, в них все правила и их конечные объявления. Для этого мы начнем с массива document.styleSheets
.
function simplifyCSSExpression() { try { var ss,sl, rs, rl; ss = document.styleSheets; sl = ss.length for (var i = 0; i < sl; i++) { simplifyCSSBlock(ss[i]); } } catch (exc) { alert("Got an error while processing css. The page " + "should still work but might be a bit slower"); throw exc; } }
В таблицах стилей мы пройдемся по массиву импортируемых таблиц (@import
), а затем уже по объявлениям стилевых правил. Для того чтобы не совершать пустых телодвижений, будем проверять, что cssText
содержит expression(constExpression(
.
function simplifyCSSBlock(ss) { var rs, rl; // Проходимся по import'ам for (var i = 0; i < ss.imports.length; i++) simplifyCSSBlock(ss.imports[i]); // если в cssText'е нет constExpression, сворачиваемся if (ss.cssText.indexOf("expression(constExpression(") == -1) return; rs = ss.rules; rl = rs.length; for (var j = 0; j < rl; j++) simplifyCSSRule(rs[j]); }
Затем мы уже можем обрабатывать для каждого правила cssText
и заменят его, используя функцию simplifyCSSRuleHelper
, чтобы текст объявления из динамического становился статическим.
function simplifyCSSRule(r) { var str = r.style.cssText; var str2 = str; var lastStr; // обновляем строку, пока она еще может обновляться do { lastStr = str2; str2 = simplifyCSSRuleHelper(lastStr); } while (str2 != lastStr) if (str2 != str) r.style.cssText = str2; }
Вспомогательная функция находит первое возможное выражение и исполняет его, затем заменяет выражение полученным значением.
function simplifyCSSRuleHelper(str) { var i, i2; i = str.indexOf("expression(constExpression("); if (i == -1) return str; i2 = str.indexOf("))", i); var hd = str.substring(0, i); var tl = str.substring(i2 + 2); var exp = str.substring(i + 27, i2); var val = eval(exp) return hd + val + tl; }
Наконец, нам нужно добавить вызов simplifyCSSExpression
при загрузке страницы.
if (/msie/i.test(navigator.userAgent) && window.attachEvent != null) { window.attachEvent("onload", function () { simplifyCSSExpression(); }); }
Далее идут уже мои (ну, или не совсем мои) мысли на тему.
После доклада Павла Корнилова на ClientSide'2007 о тюнинге CSS-правил под IE становится ясно, что таким простым подходом не обойтись: ведь может исполняться не одно выражение (expression), а несколько, и разделены они будут запятыми, а не точками с запятым.
Павел предлагает использовать свойства currentStyle
(доступное для чтения) и runtimeStyle
(доступное для записи), чтобы переопределять само стилевое свойство при его объявлении (звучит несколько сложно, не так ли?). На самом деле, все чрезвычайно просто. Применительно к нашему примеру мы должны будем написать:
#myDiv { border: 10px solid Red; width: expression(runtimeStyle.width = (ieBox ? '100px' : '80px')); }
В докладе приводится еще пара примеров. Например, исправление всплывания alt'а вместо title'а для картинок:
img { behavior: expression( (alt&&!title) ? title = '' : '', runtimeStyle.behavior = 'none' ) }
Или прозрачность через фильтр:
.button1 { opacity: .1 } .button2 { opacity: .2 } .button3 { opacity: .3 } .button4 { opacity: .4 } .button1, .button2, .button3, .button4 { filter: expression( runtimeStyle.filter = 'alpha(opacity='+currentStyle.opacity*100+')' ) }
Далее процитирую самого же Павла:
Таким образом, наш экспрешен быстро применяется при загрузке страницы и последующем создании новых нодов скриптом.
Такой способ оптимизации подходит только для «статичных» элементов, которым не нужно менять свое отображение динамически. Изменение родительского класса, равнение по высоте окна и эмуляция position: static — все это проблемные участки оптимизации. Лучше их не оптимизировать, а использовать пореже.
Еще одним проблемным местом, на мой взгляд, является общее выполнение скриптов при onresize
(наверное, это и имелось в виду, просто явно не прозвучало). Ну и общий совет: используйте CSS expressions по минимуму. Лучше всего будет, если они вообще не встретятся у вас на сайте.
Сама презентация и обсуждение опубликованы здесь.