26 January 2009

Баги IE. Часть 1. Наличие или отсутствие hasLayout

Website development
Этим постом я планирую начать серию статей о багах IE и возможных вариантах их исправления. Цикл статей решил написать в первую очередь для себя, дабы как-то систематизировать и сохранить свои знания, но я надеюсь, что он будет полезен как новичкам так и опытным верстальщикам.

Первым постом хочу затронуть одно из фундаментальных понятий при исправлении багов IE — hasLayout.

Что такое hasLayout?


В движке рендеринга IE каждый элемент, по сути, не отвечает за свое расположение. Расположение элемента зависит лишь от его положения в исходном коде и расположения в нормальном потоке. При этом, все дочерние элементы некоторго блока-контейнера располагаются, на самом деле, не относительно своего непосредственного родителя, а в зависимости от наиболее близкого предка имеющего «layout».

hasLayout это проприетарное свойство IE, которое определяет имеет ли элемент «layout» т.е. то, как элемент располагается в потоке, его размеры, позиционирование, реакцию на события и влияние на другие элементы.

HTML элементы, которые по умолчанию имеют «layout» (hasLayout = true): <html>, <body>, <table>, <tr>, <td>, <th>, <img>, <input>, <button>, <textarea>, <select>, <fieldset>, <legend>, <hr>, <iframe>, <embed>, <object>, <applet>, <marquee>.

Свойство hasLayout в IE нельзя напрямую установить, но на его наличие можно косвенно влиять различными значениями некоторых css свойств. Следующие значения перечисленных свойств дают элементу «layout» (hasLayout = true):
  • position: absolute
  • float: left или right
  • height, width: любое значение кроме auto
  • display: inline-block
  • zoom: любое значение кроме normal (невалидное свойство)
  • writing-mode: tb-rl
  • overflow, overflow-x, overflow-y: auto|scroll|hidden (только в IE7)
  • position: fixed (только в IE7)
  • min-width, min-height: любое значение (только в IE7)
  • max-width, max-height: любое значение кроме none (только в IE7)

Чтобы скинуть hasLayout нужно указать значение отличное от перечисленных выше (например: width: auto или float: none).

Более подробно о том, что такое hasLayout читайте в замечательной статье On having layout — the concept of hasLayout in IE/Win (перевод).

Как и зачем устанавливать hasLayout


Придание элементам hasLayout может исправить множество багов IE. Большинство багов, связанных с неправильным позиционированием, отображением или измерением связаны с наличием или отсутствием у элемента hasLayout.

Чтобы успешно исправлять эти баги прежде всего нужно узнать включен ли у элемента hasLayout. Это можно проверить аналитическим или опытным путем.

Первый подразумевает просмотр исходного кода стилей, применяемых к проблемному элементу, и сканирование их на наличие свойств-триггеров, перечисленных выше. Если у элемента, к примеру, явно задана высота (свойство height имеет значение отличное от auto) то свойство hasLayout установлено (его значение true). Если же свойств-триггеров не найдено, либо они имеют значения, которые сбрасывают hasLayout и элемент не относится к списку элементов имеющих «layout» по умолчанию, то свойство hasLayout не установлено.

Второй способ заключается в непосредственном просмотре значения свойства hasLayout элемента с помощью DOM инспектора в плагине Internet Explorer Developer Toolbar.
После того как вы установите плагин, на панели IE появится кнопка IEDevToolbarButton, при нажатии на которую откроется панель плагина:

IEDevToolbar
Также, открыть панель можно через меню View в IE6 (View → Explorer Bar → IE Developer Toolbar) или через меню Tools в IE7 (Tools → Toolbars → Explorer Bar → IE Developer Toolbar).

Для того чтобы выбрать элемент, у которого мы хотим проверить наличие свойства hasLayout, нужно нажать на кнопку inspect и мышкой выделить его (аналогично тому как выделяются элементы в Firebug после нажатия на кнопку «Inspect»). После этого в панели «Current Style» будут показаны все стили, применяемые к выделенному элементу браузером. Если среди них нет свойства hasLayout значит элемент не имеет «layout». Если же среди них оно есть и его значение равно -1 (true), значит свойство hasLayout установлено, и элемент имеет «layout».

После того, как мы узнали как проверить включено ли свойство hasLayout самое время научиться правильно его устанавливать. Все перечисленные выше свойства-триггеры так или иначе будут влиять на отображение элемента. Если, к примеру, мы укажем элементу float: left; то, кроме необходимого нам эффекта (установки hasLayout), элемент будет вести себя в соответствии прямому назначению этого свойства (будет вырван из нормального потока и его будут обтекать другие элементы). Хорошо, если это совпадает с нашей задумкой, но чаще всего нам нужно просто включить элементу hasLayout.

Поэтому нам нужно такое свойство-триггер, которое, в идеале, никак прямо не влияет на поведение элемента. Таких свойств немного, в некоторых ситуациях таким свойством может быть height со значением 1% или display: inline-block, но все они имеют ограничения. Если использовать height: 1%, то в случае явно установленной высоты родительского блока, высота элемента, естественно, будет вычисляться относительно выставленного значения. На мой взгляд, наиболее безопасным можно считать свойство zoom с установленным значением 1 (масштаб 100%). Но, так как это свойство невалидно, лучше всего использовать его в отдельном файле стилей для IE, который подключать с помощью Conditional Comments. Все бы хорошо, но это свойство не поддерживается в IE < 6 так что, если вам необходима поддержка этих браузеров для них можно использовать display: inline-block или height: 1%.

Алгоритм поиска багов, которые можно исправить с помощью hasLayout таков:
  1. Найти несоответствие в отображении конкретного участка страницы в IE.
  2. Просмотреть кусок HTML кода этого участка с целью запоминания иерархии элементов.
  3. Найти стили в CSS, которые применяются к элементам на этом участке.
  4. Проанализировать все применяемые стили на наличие свойств-триггеров hasLayout как у проблемного элемента так и у его предков.
  5. Поочередно применять zoom: 1; к элементам, у которых не установлен hasLayout, начиная от самого верхнего в HTML дереве, при этом, после каждого изменения CSS файла проверять результат в IE.

Если, после проведения всех этих действий, так и не удалось исправить изначальный баг значит дело не в hasLayout :)

Примеры: положительное влияние hasLayout


Предположим, что нам нужно расположить два блока внутри некоторого элемента. Один в левом верхнем углу, а другой в нижнем левом углу.
Код будет выглядеть примерно так:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ru">
<head>
<title>Пример 1. Неправильное позиционирование элементов с position:absolute.</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<style type="text/css">
* {
margin:0;
padding:0;
}

.zoom {
zoom:1;
}

div.parent {
position:relative;
border:1px solid #000;
padding:20px;
margin:10px;
}

div.parent div {
position:absolute;
width:20px;
height:20px;
top:0;
left:0;
background:#0c0;
}

div.parent div.bottom {
top:auto;
bottom:0;
}
</style>
</head>
<body>
<div class="parent">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<div></div>
<div class="bottom"></div>
</div>
</body>
</html>

* This source code was highlighted with Source Code Highlighter.

Пример 1

В нормальных браузерах все отображается как и задумывалось. Два зеленых квадратика, один из которых в левом верхнем углу элемента с рамкой, а другой в левом нижнем. Но, в IE6 все выглядит так:
Пример 1
Для того чтобы исправить это поведение нужно дать «layout» родительскому элементу (div.parent) для этого я добавлю ему специально заготовленный для этого класс «zoom». После этого все встанет на свои места:

Пример 1 (баг исправлен)

Рассмотрим следующий пример. Его код:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ru">
<head>
<title>Пример 2. Неправильная высота списка</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<style type="text/css">
* {
margin:0;
padding:0;
}

.zoom {
zoom:1;
}

div.parent {
border:1px solid #000;
padding:20px;
margin:10px;
}

div.parent ul {
background:#00c;
}

div.parent ul li {
border:1px solid #000;
background:#0cc;
}
</style>
</head>
<body>
<div class="parent">
<ul>
<li>Пункт</li>
<li>Пункт</li>
<li>Пункт</li>
</ul>
</div>
</body>
</html>


* This source code was highlighted with Source Code Highlighter.

Пример 2

В нормальных браузерах, список внутри блока с рамкой заканчивается ровно там, где заканчивается последний элемент списка. В IE 6 и 7 нижняя граница списка совпадает с нижней границей блока с рамкой (это четко видно из-за того, что я указал списку синий бекграунд):
Пример 2
Опять же, после включения hasLayout у родительского элемента (div.parent) баг исчезает:

Пример 2 (баг исправлен)

Следующий пример. Есть некоторый общий контейнер в котором несколько зафлоаченных элементов:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ru">
<head>
<title>Пример 3. Выпадение зафлоаченных блоков из контейнера</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<style type="text/css">
* {
margin:0;
padding:0;
}

.zoom {
zoom:1;
}

div.parent {
border:2px solid #000;
margin:10px 20%;
}

div.parent div.float {
background:#cc0;
float:left;
margin:2px;
text-align:center;
width:50px;
height:50px;
line-height:50px;
}
</style>
</head>
<body>
<div class="parent">
<div class="float">Float</div>
<div class="float">Float</div>
<div class="float">Float</div>
<div class="float">Float</div>
<div class="float">Float</div>
<div class="float">Float</div>
<div class="float">Float</div>
<div class="float">Float</div>
<div class="float">Float</div>
<div class="float">Float</div>
<div class="float">Float</div>
<div class="float">Float</div>
<div class="float">Float</div>
<div class="float">Float</div>
<div class="float">Float</div>
<div class="float">Float</div>
<div class="float">Float</div>
<div class="float">Float</div>
<div class="float">Float</div>
<div class="float">Float</div>
<div class="float">Float</div>
<div class="float">Float</div>
<div class="float">Float</div>
<div class="float">Float</div>
<div class="float">Float</div>
<div class="float">Float</div>
<div class="float">Float</div>
<div class="float">Float</div>
<div class="float">Float</div>
<div class="float">Float</div>
<div style="clear:both;"></div>
</div>
</body>
</html>


* This source code was highlighted with Source Code Highlighter.

Пример 3

IE снова глючит:
Пример 3
После добавления класса zoom для родительского дива баг исчезает:

Пример 3 (баг исправлен)

Помимо исправления подобных багов, включение у элемента hasLayout полезно также для следующего:
  • Создание нового контекста форматирования (Пример 4).
  • В следствиии создания нового контекста форматирования запрет на обтекание плавающих элементов (Пример 5).
  • Фильтры применяются только к элементам с установленным hasLayout (Пример 6).
  • Исправление бага при котором display:inline-block применяется только к изначально строчным элементам. (Пример 7).

Негативное влияние hasLayout


Помимо положительного влияния, включение hasLayout может также негативно влиять на отображение. Чаще всего это выражается в несхлопывании вертикальных маржинов, появлении лишних отступов, пропадании частей элементов или «прыгании» элементов при изменении размеров окна или наведении курсора на ссылку.
В таких случаях нужно либо отказаться от hasLayout, либо, если без него никак, попробовать обернуть проблемный элемент в другой, установить у оберточного элемента hasLayout а у исходного его сбросить.

Кроме этого, нужно учесть, что применение hasLayout плохо влияет на производительность.
Tags:iebugsверсткаcsshaslayout
Hubs: Website development
+77
11.4k 154
Comments 62
Top of the last 24 hours