назад далее

Opera и арифметика вложенных div-ов

Есть факт, что старый Internet Explorer неправильно складывает ширину плавающих слоев - так называемый 3px bug. Этот баг возник благодаря неуклюжей попытке создать запас на учет толщины границы охватывающего слоя - правила селекции областей. Старый Internet Explorer имел скрытый margin в 3 пиксела шириной, который в случае отсчета от границ body, отсчитывался вовнутрь окна.

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

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

Я называю этот эффект - эффектом нотного стана. Рассмотрим пример таблицы из одной строки, как это бывает в горизонтальных меню.

В этом примере нет ничего, кроме смены className на новый стиль, содержащий другой цвет фона слоя. Кроме всего прочего, этот пример показывет, что свойство таблицы table-layout: fixed корректно работает только в Internet Explorer. В FireFox и Opera таблица все равно разьезжается. Интересно, что без свойства table-layout: fixed, таблица расширяется во всех броузерах, но по-разному. В Internet Explorer небольшой отступ появляется снизу, в FireFox - сверху. В Opera отступ изначально не виден, но появляется при смене className, создавая эффект нотного стана.

Попробуем выяснить, что конкретно не влазит в ячейку.

Вот ячейка таблицы, содержащая слой с высотой и ширинной по 100%.

(...content here...)

Никаких проблем нет. Однако, нам нужно расположить внутри этого слоя еще несколько пустых слоев.

(...content here...)

В Internet Explorer все отлично работает вплоть до момента попытки заменить текст на заголовок. Вот так выглядит эта ячейка с дефолтовым заголовком:

(...content here...)

Наконец понятно, что здесь что-то зарыто, но собака ли это? Известно, что умолчания для параграфа, списков и заголовков для разных броузеров различны. Попробуем свести различия к минимому, обнулив margin у заголовка.

(...content here...)

В Internet Explorer и FireFox это сработало, но для Opera этого недостаточно. Если мы обнулим у заголовка также и padding это тоже не поможет. Опера по-прежнему сталкивает содержимое. Eсли создать специальный onmouseover класс для заголовка с нулевыми margin и padding в предположении, что Opera просто забывает атрибуты при изменении className, то это также ничего не даст.

Оставим только один слой с простым заголовком внутри. Эффект нотного стана останется, но поведение вложенного слоя неожиданно изменилось. Теперь ясно видно, что в Opera изменяется и высота слоя, и его положение.

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

Значит ли это, что Opera просто теряет размеры ограничивающей ячейки? Именно так. При перерисовке области внутри ячейки в Opera разрушается (теряется) иерархия отношений элементов таблицы. В нашем примере таблица была условно устроена следующим образом:

<table border="0" cellpadding="0" cellspacing="0">
<tr height="27px"><td>
<div class="HeaderWraper">
<h5 class="Menu">(...content here...)</h5>
</div>
</td></tr>
</table>

Очевидно при первом вычислении страницы опера понимает, что ячейка <td> имеет высоту, заданную в строке <tr>. При повторном вычислении <td> уже имеет неопределенное значение. Работать будет только явное назначение размеров для каждого <td> сделанное таким образом:

<table border="0" cellpadding="0" cellspacing="0">
<tr><td height="27px">
<div class="HeaderWraper">
<h5 class="Menu">(...content here...)</h5>
</div>
</td></tr>
</table>

 Теперь мы снова можем отказаться от явного назначения высоты вложенного в ячейку слоя:

Необходимо также избавиться от различий высот заголовков для FireFox и Internet Explorer. Например, для шрифта Verdana высотой 11px заголовки по умолчанию для Internet Explorer будут следующими:

Header 1

font-size: 24pt bold size 6font-size: 32px bold

Header 2

font-size: 18pt bold size 5font-size: 24px bold

Header 3

font-size: 14pt bold size 4font-size: 18px bold

Header 4

font-size: 12pt bold size 3font-size: 16px bold
Header 5
font-size: 10pt bold size 2font-size: 13px bold
Header 6
font-size: 8pt bold size 1font-size: 10px bold

Из этого примера видно, что никакие атрибуты шрифта заголовков, кроме высоты шрифта в пикселах, не обеспечивают кроссброузерной совместимости. 

Таким образом, результирующий стиль, снимающий ограничения блочных элементов разных моделей броузеров будет следующим:

<style type="text/css"><!--
body { font-family: Verdana; font-size: 11px; border-style: none; border-width: 0px }
p { font-family: Verdana; font-size: 11px }
tr { font-family: Verdana; font-size: 11px }
/* FireFox only */
div          { box-sizing: border-box; -moz-box-sizing: border-box }
p:first-child, h2:first-child, h3:first-child, h4:first-child, h5:first-child, h6:first-child { margin-top: 0px; margin-bottom: 0px }
p:last-child, h1:last-child, h2:last-child, h3:last-child, h4:last-child, h5:last-child, h6:last-child { margin-top: 0px; margin-bottom: 0px }
h1 {font-size: 32px; font-weight: bold }
h2 {font-size: 24px; font-weight: bold }
h3 {font-size: 18px; font-weight: bold }
h4 {font-size: 16px; font-weight: bold }
h5 {font-size: 13px; font-weight: bold }
h6 {font-size: 10px; font-weight: bold }
-->
</style>

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

html>body h1 { font-size: 32px; font-weight: bold; margin-top: 19px; margin-bottom: 19px }
html>body h2 { font-size: 24px; font-weight: bold; margin-top: 19px; margin-bottom: 19px }
html>body h3 { font-size: 18px; font-weight: bold; margin-top: 19px; margin-bottom: 19px }
html>body h4 { font-size: 16px; font-weight: bold; margin-top: 19px; margin-bottom: 19px }
html>body h5 { font-size: 13px; font-weight: bold; margin-top: 19px; margin-bottom: 19px }
html>body h6 { font-size: 10px; font-weight: bold; margin-top: 19px; margin-bottom: 19px }

Для FireFox вместо комбинации стилей

h5:first-child { margin-top: 0px; margin-bottom: 0px }
h5:last-child { margin-top: 0px; margin-bottom: 0px }
h5 {font-size: 14px; font-weight: bold }

Можно написать просто:

h5 {font-size: 14px; font-weight: bold; margin-top: 0px; margin-bottom: 0px }

Вот окончательная таблица:

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

Но даже с рассчитываемыми значениями отступов не все так просто.

Заголовок с нулевыми полями:

(...Сontent here...)

Высота этого заголовка не 14 пикселов, как можно было бы подумать, а 16, причем что-то добавлено сверху. Очевидно, реальная высота букв в заголовке высотой 14 пикселов составляет 16 пикселов вместе с диакритическими знаками. (В данном примере 14 пикселов - это высота скобки. Любопытно, что в многострочном заголовке высота строки составит уже 17 пикселов, а не 16. Для справки: в шрифте 11px высота строки составит 13px, а высота заглавных букв - 8px.)

Таким образом, верхний отступ в заголовке с нулевыми полями составит (27 - (14 + 2)) / 2 - 2 = 3 пиксела:

(...Сontent here...)

Заголовок с высотой 100% и верхним отступом 3 пиксела уже не совместим с FireFox:

(...content here...)