Онлайн библиотека PLAM.RU


  • 6.1. Оптимизируем CSS expressions
  • CSS-выражения
  • Динамические выражения
  • Вычисление постоянных
  • Использование
  • Реализация
  • Все так просто? Нет, еще проще
  • 6.2. Что лучше, id или class?
  • Методика. Размер файлов
  • Время открытия страницы
  • Результаты
  • Firefox 2
  • Opera 9.5
  • Safari 3
  • IE 7
  • IE 6
  • IE 5.5
  • p.class
  • .class
  • p#id
  • #id
  • div>p.class
  • div>.class
  • div>p#id
  • div>#id
  • div p.class
  • div .class
  • div p#id
  • div #id
  • div.div p.class
  • div.div .class
  • div.div p#id
  • div.div #id
  • Выводы
  • 6.3. Влияние семантики и DOM-дерева
  • Графики влияния DOM-дерева
  • Выводы по DOM-дереву
  • Семантическое DOM-дерево
  • Что быстрее?
  • Методика для DOCTYPE
  • Результаты оптимизации
  • Size (b)
  • Gzip (b)
  • IE6
  • IE7
  • IE8b
  • Firefox 2
  • Firefox 3
  • Opera 9.5
  • Safari 3.1
  • 1
  • 26275
  • 8845
  • 56
  • 80
  • 76
  • 130
  • 127
  • 142
  • 33
  • 2
  • 27173
  • 8993
  • 60
  • 75
  • 320
  • 127
  • 118
  • 148
  • 27
  • 3
  • 26260
  • 8949
  • 61
  • 75
  • 320
  • 131
  • 116
  • 141
  • 23
  • 4
  • 26153
  • 8862
  • 55
  • 73
  • 306
  • 94
  • 102
  • 178
  • 28
  • «Экономия на спичках»?
  • 6.4. Ни в коем случае не reflow!
  • offsetHeight и style.display
  • IE sp62
  • IE8b
  • Firefox 2.0.0.12
  • Opera 9.22
  • Safari 3.04b
  • clean
  • 128
  • 153
  • 15
  • 15
  • 16
  • offsetHeight
  • 23500
  • 10624
  • 4453
  • 4453
  • 5140
  • style.display
  • 171
  • 209
  • 56
  • 56
  • 34
  • height vs. style
  • 140 раз
  • 50 раз
  • 80 раз
  • 80 раз
  • 150 раз
  • Немного теории
  • Использование Computed Style
  • IE sp62
  • Firefox 2.0.0.12
  • Opera 9.22
  • Safari 3.04b
  • offsetHeight
  • 23500
  • 4453
  • 4453
  • 5140
  • style.display
  • 171
  • 56
  • 56
  • 34
  • getStyle
  • 5219
  • 5318
  • Оптимизация: определение класса hide
  • IE sp62
  • Firefox 2.0.0.12
  • Opera 9.22
  • Safari 3.04b
  • offsetHeight
  • 23500
  • 10624
  • 4453
  • 5140
  • isHidden
  • 231
  • 351
  • 70
  • 71
  • isHidden2
  • 370
  • 792
  • 212
  • 118
  • offsetHeight vs. isHidden
  • 102 раза
  • 30 раз
  • 73 раза
  • 92 раза
  • Заключение
  • В качестве послесловия: стили или классы?
  • Метод
  • IE 6
  • IE 7
  • Firefox 1.5
  • Firefox 2.0
  • Opera 9
  • element.className
  • 512
  • 187
  • 291
  • 203
  • 47
  • element.style.color
  • 1709
  • 422
  • 725
  • 547
  • 282
  • Перерисовка страницы
  • Групповое изменение стилей
  • Два слова о таблицах
  • Глава 6. CSS оптимизация

    6.1. Оптимизируем CSS expressions

    CSS-производительность не находится сейчас в фокусе внимания при разработке клиентских приложений для браузера. Очень часто о некоторых ключевых моментах просто не знают (или забывают), и это может привести к появлению множества «узких» мест при работе веб-приложения, которые не зависят непосредственно от сервера или канала. Они все находятся на стороне браузера.

    После того как доставка всех необходимых объектов для отображения страницы оптимизирована, можно приступать к рассмотрению других сторон клиентской производительности. Одно из них заключается в особенностях работы CSS-движка браузера и его взаимодействии с JavaScript. Давайте рассмотрим все по порядку.

    CSS-выражения

    CSS-выражения (англ. CSS expressions) были впервые представлены в Internet Explorer 5.0, который позволял назначать JavaScript-выражение в качестве CSS-свойства. Например, следующий код позволит выставить позицию элемента в зависимости от того, какого размера окно браузера.

    #myDiv {

    position: absolute;

    width: 100px;

    height:100px;

    left: expression((document.body.offsetWidth > 110 ?

    document.body.offsetWidth – 110 : 110) + "px");

    top: expression(document.body.offsetHeight - 110 + "px");

    background: red;

    }

    Не самый лучший способ решения поставленной задачи, но в качестве примера его достаточно.

    Проблема с этими выражениями в том, что они вычисляются гораздо чаще, чем многие могли бы ожидать. Они вычисляются не только во время визуализации страницы и изменения размеров окна, но также при скроллинге и даже когда пользователь просто водит мышкой по странице. Это несложно отследить — достаточно добавить счетчик в искомое выражение.

    Единственный способ избежать огромного числа вычисления CSS-выражений — использование одноразовых выражений, когда после проведения всех необходимых вычислений они устанавливают свойство CSS-стиля к какому-то конечному статическому значению, заменяя им CSS-выражение. В том случае, если необходимо динамически изменять свойство CSS-стиля по мере пребывания пользователя на странице, мы можем применить прием с обработчиками событий в качестве альтернативы. Если избежать использования CSS-выражений на странице не удается, то нужно помнить, что они могут вычисляться тысячи раз и тем самым повлиять на производительность всей страницы.

    Динамические выражения

    CSS-выражения позволяют не только вычислить CSS-свойство при объявлении стилей, но и поддерживать его постоянно в актуальном состоянии, чтобы заданное выражение было всегда верно. Это означает, что само выражение будет пересчитываться каждый раз, когда (это касается только рассмотренного примера) изменяется document.body.offsetWidth. Если бы не этот факт, динамические выражения, возможно, принесли бы большую пользу и получили бы более широкое распространение. Но это не так, и пересчет этой строки происходит каждый раз, когда заканчивается вычисления JavaScript. И не нужно быть гением, чтобы понять, что это приведет наше веб-приложение к «подвисанию».

    Давайте рассмотрим следующий блок CSS-кода:

    #myDiv {

    border: 10px solid Red;

    width: expression(ieBox ? "100px" : "80px");

    }

    Даже при том предположении, что ieBox — это постоянный флаг, который выставляется в true, когда IE находится в режиме обратной совместимости, заданное выражение будет вычисляться каждый раз в "80px". Хотя выражение будет постоянным для данной страницы, оно все равно будет пересчитываться много раз. Основной вопрос заключается в том, как избавиться от этих ненужных вычислений.

    Вычисление постоянных

    Вот что мы собираемся сделать: пройтись по всем объявлениям стилей и заменить вычисление выражения его постоянным значением. В предыдущем примере, предполагая, что мы используем 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 = document.styleSheets;

    var i = ss.length

    while (i-- > 0) {

    simplifyCSSBlock(ss[i]);

    }

    }

    catch (exc) {

    alert("Обнаружили ошибку при обработке css. Страница будет " +

    "работать в прежнем режиме, хотя, возможно, не так “ +

    “быстро");

    throw exc;

    }

    }

    В таблицах стилей мы пройдемся по массиву импортируемых таблиц (@import), а затем уже по объявлениям стилевых правил. Для того чтобы не совершать пустых телодвижений, будем проверять, что cssText содержит expression(constExpression).

    function simplifyCSSBlock(ss) {

    // Проходимся по import'ам

    var i = ss.imports.length;

    while (i-- > 0)

    simplifyCSSBlock(ss.imports[i]);

    // если в cssText'е нет constExpression, сворачиваемся

    if (ss.cssText.indexOf("expression(constExpression(") == -1)

    return;

    var rs = ss.rules;

    var rl = rs.length;

    while (rl-- > 0)

    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 = str.indexOf("expression(constExpression(");

    if (i == -1)

    return str;

    var 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();

    });

    }

    Все так просто? Нет, еще проще

    А еще можно использовать свойства 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-выражения по минимуму. Лучше всего будет, если они вообще не встретятся у нас сайте.

    6.2. Что лучше, id или class?

    Далее давайте рассмотрим, как использование id или class влияет на скорость отображения страницы в браузере (сейчас речь не идет о множественном использовании одинаковых id — это и так запрещено спецификацией). Если элемент на странице встречается единственный раз (например, «шапка» или «подвал»), то мы можем с равным успехом использовать id и class для его стилизации.

    Методика. Размер файлов

    Естественно, что скорость работы одиночного CSS-правила весьма высока, и даже десятки и сотни их не должны заметно замедлить работу браузеров. Поэтому нужно изучать работу нескольких тысяч правил, иначе точность результатов будет весьма невысока. Использовать JavaScript для генерации HTML/CSS-кода не представляется разумным, ибо тогда придется учитывать еще и скорость работы JavaScript-движка в браузерах, и в итоге эксперимент будет недостаточно чистым.

    В результате проведенного исследования были сгенерированы статичные файлы (порядка 300 Кб каждый), которые содержали достаточное число различных CSS-селекторов. Это «достаточное» число подбиралось по нескольким параметрам, в том числе таким как размер файла и скорость работы HTML/CSS-кода в браузерах (она должна быть достаточно низкой, чтобы файлы в несколько сотен Кб уже заметно тормозили при открытии).

    Итоговые файлы содержали по 4096 объявлений различных CSS-классов (или различных идентификаторов), HTML-код содержал соответствующее количество блоков, у каждого свой индивидуальный класс (или идентификатор). Дополнительно проверялась скорость работы с простым наследованием узлов (div p, CSS1) и селектор для выбора потомка первого уровня (div>p, CSS2).

    Время открытия страницы

    При тестировании браузеров нужно было, во-первых, открыть на клиенте соответствующую данному случаю страницу, а также как-то отследить время на отображение конкретно HTML/CSS-части (понятно, что оно не совпадает со временем открытия всей страницы, которое еще содержит некоторые дополнительные задержки).

    Для этого была использована простая техника: перед объявлением CSS-блока запоминается текущая метка времени, а после окончания HTML-блока, который должен отобразиться, запомненная метка вычитается из текущей. Таким образом, мы получаем (в идеале) время на отработку данных CSS-правил и кода, который ими описывается, на клиенте (плюс, возможно, еще какие-то более-менее постоянные расходы, которые нивелируются, если брать относительный, а не абсолютный выигрыш).

    Конечно, каждую тестовую страницу можно было подгружать в невидимом iframe или даже AJAX-запросом. Но ведь мы хотим узнать, фактически, скорость рендеринга браузером CSS-правил и соответствующего кода, а это время будет расходоваться только при отображении страницы в окне браузера. Поэтому подгружаемую страницу нужно отображать на экране (по возможности, максимального размера), чтобы отследить имеющуюся разницу.

    Результаты

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

    Сравнивать абсолютные значения в рамках данного эксперимента не представляется возможным, ибо каждому браузеру дополнительно нужно было расположить на странице несколько тысяч «плавающих» блоков с заданными размерами (float:left; width:20px; height:20px, фон для которых и задавался). Эта задача не имеет ничего общего со скоростью работы CSS-селекторов, но может отнимать существенное время у браузера на подготовку изображения страницы на экране (как видно, например, для Opera).

    Firefox 2

    Opera 9.5

    Safari 3

    IE 7

    IE 6

    IE 5.5

     

    p.class

    308

    5887

    237

    82

    72

    145

    .class

    219

    6456

    225

    78

    70

    149

     

    p#id

    349

    7377

    338

    91

    87

    156

    #id

    214

    7427

    220

    83

    84

    159

     

    div>p.class

    519

    9412

    247

    97

    84

    158

    div>.class

    836

    12886

    257

    95

    81

    159

     

    div>p#id

    549

    10299

    247

    105

    92

    172

    div>#id

    858

    15172

    242

    113

    91

    169

     

    div p.class

    827

    10706

    256

    97

    84

    161

    div .class

    505

    15864

    247

    95

    86

    160

     

    div p#id

    772

    11952

    247

    108

    99

    177

    div #id

    948

    13306

    255

    108

    95

    173

     

    div.div p.class

    1001

    10519

    263

    111

    94

    165

    div.div .class

    1099

    18742

    253

    105

    92

    166

     

    div.div p#id

    1161

    10989

    266

    117

    95

    181

    div.div #id

    1247

    15816

    256

    114

    100

    187

     

    Таблица 6.1. Для каждого селектора приведено время, затраченное браузером на отображение тестового случая с этим селектором в миллисекундах

    Выводы

    Единственный вывод, который можно с твердостью сделать, — это преимущество использования #id перед p#id (средневзвешенное по всем браузерам для Рунета получается 9%). Также можно с некоторой уверенностью говорить об использовании .class вместо p.class (10%). Еще стоит обратить внимание на существенное (до 2,5 раз) ускорение при переходе от CSS1-селекторов к CSS2 (от div p к div>p, в тех браузерах, которые это поддерживают). Дополнительно нужно, наверное, отметить, что выборка элементов по классу работает в целом быстрее, чем по идентификатору (11%).

    Все остальные выводы уже можно делать, анализируя данные из таблицы. IE всех версий стабильно выполнял все тесты примерно на одном уровне, а при его текущем доминировании оптимизация должна идти в первую очередь для него.

    6.3. Влияние семантики и DOM-дерева

    Давайте рассмотрим сейчас другой вопрос, а именно: как быстро браузер создает DOM-дерево в зависимости от наличия в нем элементов с id или class?Для этого мы подготовим 3 набора HTML-файлов. Первый будет содержать 10000 элементов, у которых только часть будет иметь id (количество именованных элементов варьируется от 50 до 10000: это требуется для оценки влияния DOM-дерева). Второй HTML-файл практически идентичен первому, только элементы вместо id имеют атрибут class. В третьем наборе в DOM-дереве оставим только элементы с id (т. е. будем изменять само число элементов от 50 до 10000). Все измерения запустим в скрытом iframe, чтобы избежать отрисовки загружаемой страницы на экране.

    Графики влияния DOM-дерева

    Ниже приведены разделенные графики по средневзвешенному (естественно, основную роль играет Internet Explorer, ибо сейчас им пользуются от 50% до 70% посетителей наших сайтов) времени создания документа (рис. 6.1)Рис. 6.1. Скорость создания документа, средневзвешено по всем браузерами график для времени выборки одного элемента из дерева (по идентификатору) при наличии в этом же дереве различного числа элементов с идентификаторами. ID (10000 get) показывает время на 10000 итераций проведения такой выборки, ID clean (10000 get) — то же самое, но в дереве идентификаторы присвоены не всем элементам, а только указанному числу (рис. 6.2).Рис. 6.2. Скорость выбора элемента, средневзвешено по всем браузерам

    Выводы по DOM-дереву

    По графику средневзвешенных значений хорошо видно, что при прочих равных условиях создание документа с class обходится меньшей кровью, чем с id (в общем случае от 2% до 10% выигрыша). Если принять во внимание, что class-селекторы отрабатывают быстрее, чем #id, на те же 10%, то общий выигрыш при использовании в документе классов перед идентификаторами составит порядка 15%. В абсолютном значении эти цифры не так велики: для Centrino Duo 1.7 получается цифра примерно в 0,0085 мс на 1 идентификатор (в среднем 3 CSS-правила и 1 употребление).Для документа со 100 элементами выигрыш может составить почти 1 мс, для документа с 1000 — 8,5 мс! Стоит заметить, что средняя страница в интернете имеет 500–1000 элементов. Проверить, сколько элементов на странице, можно, просто запустив следующий код в адресной строке браузера на какой-либо открытой странице:javascript:alert(document.getElementsByTagName('*').length)Естественно, что приведенные цифры — это уже то, за что можно побороться.В случае больших веб-приложений задержка в 100 мс (при числе элементов более 10000) уже может оказаться критичной. Ее можно и нужно уменьшать (наряду с другими «узкими» местами для JavaScript, о которых речь пойдет в седьмой главе).Что и требовалось доказать: значительную нагрузку составляет именно создание DOM-дерева в документе. В целом, на эту операцию уходит от 70% всего времени рендеринга (т. е. наибольшая экономия достигается за счет минимизации размера дерева).На скорость вычисления одного элемента по идентификатору, как ни странно, наибольшее влияние оказывает опять-таки DOM-дерево, а не количество таких элементов. Даже при 1000 элементов с id более половины временных издержек можно урезать, если просто сократить общее число элементов (особенно хорошо это заметно для IE).

    В целом же основных советов два: стоит уменьшать DOM-дерево и использовать id только в случае действительной необходимости.

    Семантическое DOM-дерево

    Логическим продолжением уже проведенных исследований CSS/DOM-производительности браузеров стало рассмотрение зависимости времени создания документа от числа тегов (узлов дерева). Раздельно были проанализированы случаи, когда DOM-дерево является чисто линейным (все div лежали прямо внутри body), когда оно разветвленное (ветки по 10 вложенных div наращивались внутри body) и когда вместо ветки из div используется некоторая семантическая конструкция, а именно:

    <div>

    <ul>

    <li></li>

    <li></li>

    </ul>

    <p>

    <a href="#">

    <em></em>

    </a>

    <span></span>

    </p>

    <blockquote></blockquote>

    <h1></h1>

    </div>

    В итоге мы получили примерно следующую картину:Рис. 6.3. Средневзвешенное значение времени создания документа от числа узлов в DOM-дереве

    Что быстрее?

    Да, очевидно, что размер DOM-дерева влияет на скорость загрузки страницы. Одной из целей данного исследования было показать, как именно влияет (в конкретных числах). Средний размер страницы — 700-1000 элементов. Они загрузятся в дерево сравнительно быстро (3-7 мс, без учета инициализации самого документа, которая занимает 30-50 мс). Дальше время загрузки растет линейно, но все равно можно нарваться на нежелательные «тормоза», добавив несколько тысяч «скрытых» элементов или избыточной семантики.Различия между линейной и древовидной структурами находятся в пределах погрешности, однако семантическое дерево оказалось самым медленным (чуть ли не на 50%). Но в любом случае, уменьшение размера DOM-дерева всегда является наиболее приоритетным направлением.Конечной же целью всех экспериментов было установить, есть ли различие в отображении HTML 4.0 Transitional и XHTML 1.0 Strict документов и какова реальная польза от использования советов по оптимизации CSS-кода (имеется в виду синтаксис селекторов). Об этом рассказывается в следующем разделе.

    Методика для DOCTYPE

    Была аккуратно выкачана главная страница Яндекса (она уже хорошо оптимизирована с точки зрения производительности, поэтому проводить эксперименты на ней весьма показательно). Из нее были удалены все ссылки на картинки и внешние скрипты, чтобы не создавать дополнительных задержек. В дальнейшем полученная «чистая» версия препарировалась различными способами.Далее была добавлена стандартная схема измерения загрузки (рендеринга) страницы: время в самом начале head засекается и затем отнимается от времени срабатывания события window.onload (в данном случае это равносильно окончанию рендеринга HTML-кода). Браузеры друг с другом не сравнивались (в частности, из-за поведения Safari, который не совсем честно сообщает об этом событии), сравнивались только различные варианты.В качестве второй версии страницы бралось приведение ее к валидному XHTML Strict виду. Верстка при этом немного изменилась, но в целом результат получился весьма убедительный. Комментарии и прочий мусор (типа пустых onclick="", о них речь чуть дальше) были сохранены. Размер, действительно, несколько увеличился (на 1 Кб — несжатая версия и на 150 байтов — сжатая).Далее в третьей версии уже были убраны все onclick. Больше ничего со страницей не делалось. Ожиданий данная версия не оправдала (только Safari показал значимые отличия от предыдущего варианта, хотя было удалено 83 пустых onclick).В четвертом варианте — венце оптимизационных (в отношении CSS/HTML-кода) действий — использование id было сведено к минимуму, все селекторы для class задавались без тегов. Также были убраны все комментарии из кода.

    Результаты оптимизации

    В таблице приведены результаты для основных браузеров (август 2008): размер каждого варианта в байтах и время его загрузки. Времена приведены в миллисекундах.

    Size (b)

    Gzip (b)

    IE6

    IE7

    IE8b

    Firefox 2

    Firefox 3

    Opera 9.5

    Safari 3.1

    1

    26275

    8845

    56

    80

    76

    130

    127

    142

    33

    2

    27173

    8993

    60

    75

    320

    127

    118

    148

    27

    3

    26260

    8949

    61

    75

    320

    131

    116

    141

    23

    4

    26153

    8862

    55

    73

    306

    94

    102

    178

    28

    Таблица 6.2. Для каждого варианта приведен размер и время его отображения в миллисекундах

    «Экономия на спичках»?

    В результате тестов удалось показать, что валидный XHTML не медленнее (а даже местами быстрее), чем HTML. И оптимизация реально играет роль (возможно ускорение загрузки HTML главной страницы Яндекса на 10–12%). Если говорить о конкретных примерах, то на 100 Кб/с канале с включенным сжатием в FF3 оптимизированный вариант загрузится на 9 мс быстрее. Для более быстрого канала или более медленного компьютера отличие будет еще разительнее.Естественно, это все «копейки» для обычных пользователей (+/-50 мс —это совершенно не критично). Однако если речь идет про «экономию на спичках», когда нам важен каждый запрос к серверу и каждая миллисекунда, то тут уже стоит задуматься — что же все-таки использовать.И что важнее всего, если правильно расставить акценты, то загрузку XHTML можно сделать и быстрее, чем HTML. Различие в размере файлов оказалось в итоге минимальным (26153 против 26275 в несжатом варианте, и 8862 против 8845 в сжатом, т. е. меньше 0,5%). При этом в IE7 наблюдается ускорение отображения страницы на 7 мс (от 60–80 мс при загрузке страницы). Это в среднем дает 10% выигрыша в скорости. FF3 ведет себя похожим образом (но выигрыш в скорости 20% (25 мс от 127 мс)). Все остальные браузеры показали отличие в загрузке на 2-3 мс, что укладывается в погрешность; Opera была медленнее, что подтверждается предыдущими тестами.В целом в свете тотального распространения мобильных браузеров с их маломощными процессорами такой вид оптимизации выглядит весьма перспективно.

    6.4. Ни в коем случае не reflow!

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

    HTML-элемент в документе может быть скрыт с помощью JavaScript или CSS-свойства display. Дублировать с помощью JavaScript логику, заложенную в CSS-движке, достаточно сложно и не всегда нужно. Проще запросить offsetHeight объекта (если оно равно 0, значит элемент скрыт). Проще-то оно конечно проще, вот только какой ценой?

    Для проверки видимости элемента принято проверять значение стиля display или наличие класса hide. Когда мы пишем функцию скрытия/отображения сами, то знаем, какое значение стиля display у объекта по умолчанию или какой класс какому состоянию соответствует. Однако универсальная (библиотечная) функция знать об этом не может.

    offsetHeight и style.display

    Проведем тестирование скорости вычисления значений offsetHeight и style.display.

    Для удобства профайлинга вынесем доступ к этим значениям в отдельные функции:

    function fnOffset(el)

    {

    return !!el.offsetHeight;

    }

    function fnStyle(el)

    {

    return el.style.display=='none';

    }

    где el — тестовый контейнер.

    Проведем тест на тысяче итераций, на каждой итерации будем добавлять в тестовый контейнер элемент <span>. Проверим время, затрачиваемое на добавление тысячи элементов, без вызова тестовых функций тест clean. Проведем тестирование во всех браузерах, замеряя время следующим способом:

    var time_start=new Date().getTime();

    /* ... тест ... */

    var time_stop=new Date().getTime();

    var time_taken=time_stop-time_start;

    где time_taken — это время, затраченное на тест, в миллисекундах.

    IE sp62

    IE8b

    Firefox 2.0.0.12

    Opera 9.22

    Safari 3.04b

    clean

    128

    153

    15

    15

    16

    offsetHeight

    23500

    10624

    4453

    4453

    5140

    style.display

    171

    209

    56

    56

    34

    height vs. style

    140 раз

    50 раз

    80 раз

    80 раз

    150 раз

    Таблица 6.3. Результаты выполнения тестов по определению видимости элементов. Времена приведены в миллисекундах. Взято среднее значение серии из 5 тестов

    Судя по результатам тестов, доступ к offsetHeight медленнее в 50-150 раз.

    Получается, что по отдельности и offsetHeight, и добавление элементов работают быстро, а вместе — очень медленно. Как же так?

    Немного теории

    Reflow — это процесс рекурсивного обхода ветви дерева DOM, вычисляющий геометрию элементов и их положение относительно родителя. Начало обхода — изменившийся элемент, но возможно и распространение в обратном порядке. Существуют следующие типы reflow:

    начальный — первичное отображение дерева;

    инкрементный — возникает при изменениях в DOM;

    изменение размеров;

    изменение стилей;

    «грязный» — объединение нескольких инкрементных reflow, имеющих общего родителя.

    Reflow делятся на неотложные (изменение размеров окна или изменение шрифта документа) и асинхронные, которые могут быть отложены и объединены впоследствии.

    При манипулировании DOM происходят инкрементные reflow, которые браузер откладывает до конца выполнения скрипта. Однако исходя из определения reflow, «измерение» элемента вынудит браузер выполнить отложенные reflow. Т.к. возможно распространение снизу вверх, то выполняются все reflow, даже если измеряемый элемент принадлежит к неизменившейся ветви.

    Операции reflow очень ресурсоемки и являются одной из причин замедления работы веб-приложений.

    Если судить по тесту clean, все браузеры хорошо справляются с кэшированием многочисленных reflow. Однако запрашивая offsetHeight, мы «измеряем» элемент, что вынуждает браузер выполнить отложенные reflow. Таким образом, браузер делает тысячу reflow в одном случае и только один — в другом.

    Замечание: в Opera reflow выполняется еще и по таймеру, что, однако, не мешает ей пройти тест быстрее остальных браузеров. Благодаря этому в Opera виден ход тестов — появляются добавляемые звездочки. Такое поведение оправдано, т.к. вызывает у пользователя ощущение большей скорости браузера.

    Использование Computed Style

    Что же показало тестирование? По меньшей мере, некорректно сравнивать универсальный (offsetHeight) и частный (style.display) случаи. Тестирование показало, что за универсальность надо платить. Если же все-таки хочется универсальности, то можно предложить другой подход: определение Computed Style — конечного стиля элемента (после всех CSS-преобразований).

    getStyle = function()

    {

    var view = document.defaultView;

    if(view && view.getComputedStyle)

    return function getStyle(el,property)

    {

    return view.getComputedStyle(el,null)[property] ||

    el.style[property];

    };

    return function getStyle(el,property)

    {

    return el.currentStyle && el.currentStyle[property] ||

    el.style[property];

    };

    }();

    Проведем тестирование этого способа и сведем все результаты в таблицу.

    IE sp62

    Firefox 2.0.0.12

    Opera 9.22

    Safari 3.04b

    offsetHeight

    23500

    4453

    4453

    5140

    style.display

    171

    56

    56

    34

    getStyle

    5219

    5318

    Таблица 6.4. Резульаты выполнения функции getStyle. Времена приведены в миллисекундах

    Во-первых, для IE и Firefox (наиболее популярных браузеров) функция эта работает некорректно (в общем случае возвращает неверные данные). Во-вторых, работает она чуть ли не медленнее, чем offsetHeight.

    Вообще говоря, рекомендуется не пользоваться такими универсальными функциями (getStyle есть практически в каждой JavaScript-библиотеке), а реализовывать необходимую функциональность в каждом конкретном случае. Ведь если мы договоримся, что скрытые элементы должны иметь класс hide, то все сведется к определению наличия этого класса у элемента или его родителей.

    Оптимизация: определение класса hide

    Давайте подробнее остановимся на предложенном мной решении. Предлагаю следующую реализацию:

    function isHidden(el)

    {

    var p=el;

    var b=document.body;

    var re=/(^|\s)hide($|\s)/;

    while(p && p!=b && !re.test(p.className))

    p=p.parentNode;

    return !!p && p!=b;

    }

    Предполагается, что корневые элементы DOM скрывать не имеет смысла и поэтому проверки ведутся только до document.body.

    Предложенное решение явно не спустит лавину reflow, так как никаких вычислений и измерений не проводится. Однако немного смущает проход до корня документа: что же будет при большой вложенности элементов? Давайте проверим. Тест isHidden проводится для вложенности 2 (document.body / test_div), а тест isHidden2 — для вложенности 10 (document.body / div * 8 / test_div).

    IE sp62

    Firefox 2.0.0.12

    Opera 9.22

    Safari 3.04b

    offsetHeight

    23500

    10624

    4453

    5140

    isHidden

    231

    351

    70

    71

    isHidden2

    370

    792

    212

    118

    offsetHeight vs. isHidden

    102 раза

    30 раз

    73 раза

    92 раза

    Таблица 6.5. Резульаты выполнения функции isHidden. Времена приведены в миллисекундах

    Как показывают тесты, даже при большой вложенности падение скорости невелико. Таким образом, мы получили универсальное решение, которое быстрее доступа к offsetHeight в 30–100 раз.

    Заключение

    Все вышеприведенные мысли предназначены не столько для решения проблемы выяснения видимости элемента в общем случае, сколько для объяснения одного из наиболее часто встречающихся узких мест взаимодействия с DOM и детального разбора методов оптимизации. В ходе тестов был намеренно воспроизведен наихудший случай. В реальных ситуациях такой прирост скорости получится только при использовании в анимации. Однако понимание причин и механизма reflow позволяет писать более оптимальный код.

    В качестве послесловия: стили или классы?

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

    Существует два способа это сделать: при помощи стилей или установив цвет (или фон) напрямую из JavaScript. Для начала немного кода — с помощью класса:

    var items = el.getElementsByTagName('li');

    for (var i = 0; i < 1000; i++) {

    items[i].className = 'selected'

    }

    И с помошью стилей:

    var items = el.getElementsByTagName('li');

    for (var i = 0; i < 1000; i++) {

    items[i].style.backgroundColor = '#007f00';

    items[i].style.color = '#ff0000';

    }

    Результаты простые и понятные:

    Метод

    IE 6

    IE 7

    Firefox 1.5

    Firefox 2.0

    Opera 9

    element.className

    512

    187

    291

    203

    47

    element.style.color

    1709

    422

    725

    547

    282

    Таблица 6.6. Применение стилей и классов к элементам

    Перерисовка страницы

    Однако когда мы изменяем класс элемента, код отрабатывает значительно быстрее, но вот страница обновляется медленно. Это все из-за того, что изменение свойства className не перерисовывает страницу мгновенно, вместо этого браузер просто помещает событие обновления в очередь reflow. Отсюда и огромная скорость, казалось бы, более сложной процедуры. А что по поводу :hover? К сожалению, :hover работает только для ссылок в Internet Explorer 6. Поэтому в любом случае придется пользоваться какой-то его эмуляцией.

    Из всего вышеперечисленного можно сделать два ключевых вывода

    Используйте className везде, где это возможно. Это дает больше гибкости и контроля над внешним видом сайта.

    Если на странице много элементов в контейнере и необходимо построить очень быстрый интерфейс, стоит устанавливать стили напрямую через свойство style.

    Групповое изменение стилей

    Если мы уже задумались над максимально быстрым изменением интерфейса нашего веб-приложения (отрисовке) через свойство style, то стоит иметь в виду следующий момент. Мы можем изменять свойство cssText, которое отвечает за компилируемые стили элемента:

    element.style.cssText = "display:block;width:auto;height:100px;...";

    Таким образом, мы можем дополнительно ускорить наше единовременное обновление стилей у элемента, потому что произойдет всего одно присвоение свойств и всего один reflow (и он случится сразу же после изменения этого свойства, а не в отложенном режиме).

    Два слова о таблицах

    Таблицы замечательно подходят для организации информации. Однако если в HTML-документе встречается таблица, то браузеру приходится пробежаться по ней дважды: в первый раз — чтобы выбрать все элементы, рассчитать их взаимные размеры, и чтобы отрисовать их все — во второй раз. Если на странице выводятся большие массивы данных (например, параметры товаров или статистические данные), то гораздо быстрее будет визуализировать такие таблицы в один проход.

    Давайте рассмотрим, как можно помочь браузерам в их нелегком труде. Следующие действия позволят начать отображение таблицы еще до того, как будет получена вся информация о ней.

    Необходимо установить для table CSS-атрибут table-layout в значение fixed.

    Затем явно определить объекты col для каждого столбца.

    И установить для каждого элемента col атрибут width.

    В качестве примера можно привести такой фрагмент кода:

    <table style="table-layout: fixed">

    <!-- первый столбец имеет ширину 100 пикселей -->

    <col width="100"></col>

    <!-- второй — 200 -->

    <col width="200"></col>

    <!-- третий и четвертый — по 250 -->

    <col width="250"></col><col width="250"></col>

    <thead>...</thead>

    <tfoot>...</tfoot>

    <tbody>...</tbody>

    </table>









    Главная | Контакты | Нашёл ошибку | Прислать материал | Добавить в избранное

    Все материалы представлены для ознакомления и принадлежат их авторам.