|
||||
|
Глава 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> |
|
||
Главная | Контакты | Нашёл ошибку | Прислать материал | Добавить в избранное |
||||
|