Раскладка в CSS: позиционирование

3.08.05 23:46

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

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

Начну с позиционирования.

Краткий обзор

Суть позиционирования очень проста: любой бокс можно расположить в любом месте страницы, задав ему точные координаты. Именно любой, а не только <div>, как многим кажется; вы легко можете позиционировать хоть <b>, если очень захочется :-)

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

Существуют четыре способа позиционирования боксов:

Static

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

Absolute

Бокс с абсолютным позиционированием располагается по заданным координатам, а из того места, где он должен был бы быть, он удаляется, и в этом месте сразу начинают раскладываться следующие боксы. Говорят, что он “исключается из потока“.

Fixed

Ведет себя так же, как absolute, но при этом он не скролится вместе с остальной страницей.

Relative

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

А теперь - углубимся.

Абсолютное позиционирование

Итак, чтобы расположить бокс абсолютно, ему надо задать нужный тип позиционирования и координаты:

#somebox {
  position:absolute;
  left:100px; top:100px;
  bottom:100px; right:100px;
}

Координаты означают расстояние бокса от краев: top:0 означает, что бокс прижат к верхнему краю, right:10px - что отстоит на 10 пикселов от правого края и т.д. Любая из координат необязательна. В случае, если координаты не задают вертикального или горизонтального положения, то оно остается таким же, какое было бы без позиционирования. То есть в случае, когда у нас есть два произвольных бокса один за другим “box1″ и “box2″:

<div id="box1">
 ...
</div>
<div id="box2">
 ...
</div>

… и второй мы позиционируем так:

#box2 {
  position:absolute;
  left:150px;
}

… то по вертикали он останется прямо под первым боксов, а по горизонтали будет отстоять от левого края на 150 пикселов.

Стакан

Возможно некоторых особенно въедливых читателей уже какое-то время беспокоит вопрос, который я намеренно опустил в начале: относительно каких таких краев двигают бокс координатные свойства? Вопрос этот не совсем тривиален, потому что хотя интуитивно и кажется, что все позиционируется “от окна”, это не всегда так.

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

Но самое интересное, что этот позиционированный бокс сам внутри себя создает такой же стакан, и все его дети (боксы, находящиеся у него внутри) позиционируются уже относительно него, а не относительно окна. И внутри него происходит то же самое: любой позиционированный (не static) бокс создает внутри себя такой стакан.

Используя более принятые в CSS термины, этот стакан называется “содержащим блоком” (containing block).

Fixed

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

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

Автоматические размеры

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

#somebox {
  position:absolute;
  top:0; left:0; right:0;
  margin:20px; padding:20px;
}

Этот бокс с заданными левой и правой координатами будет точно касаться боковых сторон своего стака… э-э-э… содержащего блока, какой бы ширины тот ни был, а margin’ы и padding’и будут откладываться внутрь бокса.

Это свойство неоценимо при создании раскладок веб-приложений, где неперекрывающиеся боксы должны занимать весь экран по определенной сетке:

Засада

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

Главная проблема состоит в том, что координаты и размеры бокса можно задать только относительно содержащего блока, в котором он лежит. А очень часто хочется другого.

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

Здесь видны две проблемы:

Относительное позиционирование

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

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

Вот пример, который показывает полезность этого свойства. Пусть у нас есть три блока: “заголовок”, “содержимое” и “низ”, а внутри “содержимого” лежит блок “об авторе”:

<div id="header">
</div>

<div id="contents">
  <div id="author">
  </div>
...
</div>

<div id="footer>
</div>

И пусть высота заголовка нам точно не известна. Боксы эти статические, идут один за другим, и какая бы высота у заголовка ни была, содержимое будет начинаться прямо под ним.

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

Напишем ему:

#author {
  position:absolute;
  top:0; right:0;
}

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

Вот это и есть случай, в котором работает position:relative. Если мы поставим его блоку содержимого, то он никуда не денется из потока, но в то же время станет содержащим блоком, и “об авторе” расположится в его правом верхнем углу.

Реальное положение вещей

Реальность, как обычно, накладывает на разработчика еще большие ограничения, чем сама технология в чистом виде. Самую большую проблему представляет Internet Explorer для Windows, поддержка CSS в котором не обновлялась уже 4 года. Вообще, у него багов, связанных с CSS’ом довольно много, и все они подробно описаны на страницах интернета. Для исчерпывающего списка советую покопаться в “css-discuss wiki” и в “Position is everything“. Я же остановлюсь на двух особенно заметных проблемах.

Атоматические размеры

В WinIE не работает задание размеров боксов через одновременную установку координат противоположных сторон. Это, однако, довольно легко обходится использованием интересной способности IE рассчитывать значения свойства для CSS с помощью Javascript. Возьмем тот же пример с боксом во всю ширину и margin’ами и padding’ами по 20 пикселов:

Код для этого выглядит так:

#somebox {
  position:absolute;
  top:0; left:0; right:0;
  margin:20px; padding:20px;
  border:solid red 1px;
}

Здесь IE просто проигнорирует right:0 и бокс останется у левого края, обтягивая свое содержимое:

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

#somebox {
  position:absolute;
  top:0; left:0; right:0;
  margin:20px; padding:20px;
  border:solid red 1px;
  width:expression(document.body.offsetWidth-82);
}

document.body.offsetWidth - это текущая ширина бокса страницы. А число 82 - это margin’ы+padding’и+border’ы с двух сторон бокса.

Если нужно взять ширину не “body”, а какого-то произвольного бокса, то ему надо дать id (пусть будет “mainbox”) и тогда его ширина будет браться так: mainbox.offsetWidth.

Fixed

В WinIE не работает position:fixed. Это очень неприятно, потому что не только пропоадает эффект прилипания (что было бы еще ничего), но бокс еще и не позиционируется совсем. А это, понятное дело, обычно полностью рушит всю раскладку.

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

<div id="contents">
</div>

<div id="menu">
</div>

Мы хотим, чтобы блок меню висел фиксировано в каком-нибудь месте, а соержимое бы свободно скролилось в окне. Если поставиь для меню position:absolute, то оно будет скролиться вместе с окном, потому что оно лежит внутри скролящегося окна. Гениальный же хак заключается в том, чтобы заставить скролиться не окно, а только бокс содержимого:

html {
  width:100%; height:100%;
  overflow:hidden;
}

body {
  width:100%; height:100%;
  margin:0; padding:0;
  overflow:auto;
}

#menu {
  position:absolute;
  top:20px; left:20px;
}

В деталях это означает следующее. Поведение элемента “html” во многом отвечает за поведение окна (так уж сложилось). Мы говорим ему занять все окно целиком и отключаем скроллинг (overflow:hidden). Зато элементу “body” говорим по размеру занять, опять-таки, все окно, а скроллинг включаем (overflow:auto). И теперь абсолютно подвешенный бокс “menu” не двигается, потому что скролится не его содержащий блок “html”, а совсем другой - “body”.

Все это выглядит слегка наворочено, но со временем должно стать более понятным. В общем-то, это довольно глубокая магия, и на начальных этапах обычно не требуется :-).

Заключение

Позиционирование обладает своими плюсами и минусами.

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

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

Комментарии: 25

  1. GVA:

    Мнение:
    Вольный перевод спецификации…

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

    Замечание:
    Разве задав ширину дочернего блока 100%, мы не получим желаемого?

  2. Иван Сагалаев:

    Нет, 100% не сработает. Я специально выбрал пример так, чтобы там были margin’ы, border’ы и padding’и. Они добавляются к ширине, поэтому если сказать в том примере width:100%, то бокс будет занимать 100%+82 пиксела и, соответственно, не уместится в контейнер.

    Я об этом писал в предыдущей статье.

  3. Александр Шугард:

    В примере кода, в тексте после заголовка “Fixed в IE” у вас написано:
    body {
    width:100; height:100%;
    margin:0; padding:0;
    overflow:auto;
    }
    Я может чего путаю, но помоему это опечатка. Должно быть width:100%

    Спасибо за статью, сколько рылся, подобного простого объяснения мало где встречал. Узнал новое.

  4. Иван Сагалаев:

    Спасибо, “%” поправил.

  5. uncle.f:

    Хорошая статья, но извините, хочу придраться вот к этому:

    “В CSS существует, по большому счету, четыре способа раскладывать боксы по странице: прямой поток, позиционирование, float’ы и таблицы.”

    Это высказвание неверно с точки зрения спецификации и идеологии CSS.
    Взглянув сюда:

    http://www.w3.org/TR/CSS21/visuren.html#positioning-scheme

    не могу не заметить следующее:

    • Самое грубая ошибка по поводу таблиц. Таблицы не являются способом “раскладывать боксы в CSS”. Ключевое слово “в CSS”. Вся иделогия позиционирования элементов в CSS состоит в том чтобы НЕ использовать логических элементов для визуальной презентации. Таблица - это логическая структура. Да, она имеет вполне определённое отображение, но это не метод CSS для позиционирования. Это дурное наследие дней раннего HTML. Структура это то же самое, что и (или любой другой “block”) в глазах CSS. Точно так же вы сделаете ошибку, если будете использовать таг <b> для придания жирности шрифту, вместо того, чтобы использовать свойство font-weight:bold. Чтобы исправить неточность в Вашем тексте, предлагаю заменить CSS на HTML, ибо, действительно, для многих несведущих о разделении контента и его презентации, таблицыпо сей день остаются главным способом позиционирования элементов на странице.

    • Вторая неточность в формулировке, это то, что Вы ставите прямой поток, позиционирование и float’ы наравных в своём списке. Прямой поток и плавающие блоки это подвиды CSS позиционирования, которых, согласно ссылке выше всего 3:

      1) normal (включающее static block/inline, relative block/inline и run-in).
      2) floats
      3) absolute (включающее, собственно, absolute и fixed)

    • Не говорится ни слова о media (screen/printer/etc.) так как одни и те же CSS свойства и элементы отображаются по разному на разных носителях.

    • Учитывая всё это, вводная фраза должна быть приблизительно такой:

    В верстке web страниц существует два основных подхода к позиционированию элементов: использование самих HTML элементов (таких как таблицы, параграфы, нумерованные списки и т.д.) и CSS позиционирование, в котором внешний вид элементов логически отделён от наполняющего контента. В CSS существует, по большому счету 3 способа раскладывать боксы по странице: прямой поток (включающий static и relative блоки), float’ы (плавающие элементы) и абсолютное позиционирование (включающее методы absolute и fixed).

  6. GVA:

    Вобщем я делал так. Дочерний блок имеет padding и border. Родительский блок имеет padding вместо margin-а у дочернего блока. Ширина не задаётся явно. Во всех броузерах работает одинаково корректно. Вот код:

     содержимое блока
    

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

  7. GVA:

    упс… код не видно 8) но думаю и так понятно

  8. Иван Сагалаев:

    Ага… Тут требуется разъяснение :-)

    Дело в том, что эта статья - продолжение цикла (надо будет это явно где-то указать). И в первой статье цикла я как раз рассказывал про подходы к верстке HTML. В этой конкретной статье я рассказываю именно о способах раскладки элементов страницы CSS’ом.

    То, что вы называете позиционированием, я называю раскладкой: то есть общий набор всех инструментов что-то располагать на экране. Позиционирование - одно из них, то, которое делается через свойство “position”. Именно из-за созвучия.

    Среди других способов я упомянул таблицы отчасти в виде провокации, для поддержания интереса :-). Я имею в виду не HTML’ный <table>, а значения для свойства display: table, table-row, table-cell и остальные table*. Это здесь: http://www.w3.org/TR/CSS21/tables.html#q2

    Моя основная задача, все же, не перевести спецификацию (переводы-то есть), а написать доходчивый учебник для начинающих, поэтому я сознательно опускаю многие тонкости. Например, run-in, inline-block и inline-table я вряд ли буду рассматривать вообще, потому что они почти нигде не реализованы (только в 8-й Опере, кажется, появился вменяемый inline-block). То же касается и различных media. Я о них буду писать, но гораздо-гораздо позже. Потому что если это все загрузить в начинающего, он меня больше читать не будет :-).

  9. Иван Сагалаев:

    GVA, если я правильно угадал, что вы имели в виду, то вы используете два бокса: контейнер, который позиционируется, и его ребенок, который статический. Родителю ставятся нужные размеры, а у статического ребенка ширина подбирается автоматически, и он со всеми margin’ами-padding’ами влезает в родителя тютелька в тютельку. Так?

    Этот подход я где-то встречал под названием ‘layout boxes’. Его минус как раз в том, что исключительно для оформительских целей CSS’а в HTML приходится добавлять лишний структурный элемент (контейнер), который для документа не несет никакой смысловой нагрузки. Да, иногда такие хаки использовать приходится, но CSSники справедливо считают что хак, который вынуждает менять HTML - это самый плохой вид хака. Это приводит к более загруженному HTML-коду, от которого мы, в общем-то, и хотели отказаться с помощью CSS’а. Поэтому я и порекомендовал в статье expression’ы.

  10. uncle.f:

    Ясно.
    Но даже при таком понимании “раскладывания”, CSS позиционирование и таблицы (а тем более CSS таблицы) не могут быть равнозначными способами расположения элементов (с точки зрения CSS!) и стоять в одном ряду.

    “display: ” - это не “position:” и, заодно, “display:table” - это уже не “display:block.

    Можно говорить о раскладке боксов и можно говорить о форматировании элементов таблиц при помощи CSS, но ставить обе темы в один ряд, мне кажется, всё же, неправильно. Таблица, хоть даже и CSS таблица, - это бокс. А значит, вы говорите о раскладывании боксов боксами. А это не есть правильно. Всё равно как сказать: боксы можно раскладывать позиционированием, флоатами и списками.

    Таблицы, как и, например, нумерованные/авто-генерируемые списки это особый вид контента, у которого есть дополнительные визуальные свойства (в том числе и CSS), в отличие от обычных боксов. Это отдельная тема, которая к раскладыванию(!) элементов при помощи CSS(!) имеет весьма далёкое отношение. Не спроста в спецификации CSS таблицы (как и списки) выделены в совершенно отдельную (от позиционирования) главу.

  11. Иван Сагалаев:

    Дело в том, что display:table - это не форматирование элементов таблиц. Это именно способ раскладки, не больше, не меньше. Их можно применять (и применяют) к любым элементам для достижения визуального эффекта “как бы табличной” раскладки. Короткий пример, как с помощью таблиц CSS делается колоночная раскладка:

    HTML:

    <div id="header"></div>
    	
    <div id="main">
      <div id="sidebar"></div>
      <div id="content"></div>
    </div>
    	
    <div id="footer"></div>

    CSS:

    #main {display:table;width:100%;}
    #sidebar, #content {display:table-cell;}

    Собственно, как вы и пишете - это отдельная вещь. И я в начале написал, что буду писать их по порядку, а начну с позиционирования. Так что, я предлагаю пока просто подождать, пока оно все будет написано, а потом уже там и спорить по сути :-)

  12. korvin0:

    Странно, последний Ваш пример у меня не работает в IE (как вообщем и в других браузерах)… Может, приведете код, который работает у вас в IE, с имитацией position fixed ?
    И еще нашел интересную штуку. Такой код:

    <html>
    <style>
    #menu {
      position:fixed;
      border: 1px solid red;
    }
    </style>
    	
    <body>
    <div id=”menu”>menu</div>
    </body>
    </html>

    выдает в IE 6.0 рамку, растянутую на всю ширину страницы…

  13. Иван Сагалаев:

    Код, который я привел работает, если перевести IE в стандартный режим, написав в первой строке файла “<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.01//EN” “http://www.w3.org/TR/html4/strict.dtd”>”. Плюс я забыл точку с запятой после height:100%, уже поправил, спасибо.

    А ваш код выдает div по всей ширине именно потому, что IE игнорирует position:fixed и оставляет бокс статическим, он себя и ведет как статический: занимая всю ширину. Можно попробовать убрать position:fixed, ничего не изменится.

  14. Troy:

    Господа, мне вот что интересно. Как можно например сделать такие декоративные элементы, как например закругленные углы у блока, не тронув структуру документа? По-моему никак. И это грустно.

  15. Иван Сагалаев:

    В общем-то, есть средства. Но все неидеальны:

    • Добавлять нужные структурные элементы скриптом. Это более чистый метод, потому что исходный документ, который получают поисковики, не содержит этого мусора.

    • Использовать XBL. Это есть только в Gecko-браузерах: возможность в стилях привязывать к элементу любую структурную обвязку и скриптовать ее.

    • Использовать HTC. Это есть только в IE. По назначению похоже на XBL, но говорят, что менее удобно.

    Ну и самый правильный вариант - подождать широкой реализации CSS3. Закругленные углы там есть, а также можно вешать любое количество фоновых картинок и назначать картинки для рамок: CSS3 Backgrounds and Borders Module

  16. Александр Шугард:
  17. Иван Сагалаев:

    Александр, Troy как раз спрашивал, как избежать таких “решений” :-)

  18. Александр Шугард:

    Ой блин =))) … ну да ладно - кому-нибудь до пригодиться =))

  19. Troy:

    за http://pro.html.it/esempio/nifty/
    Александру спасибо. Javascript’ом я думаю и буду отныне пользоваться. XBL и HTC тыкать, если честно, никакого желания нет :)

  20. Ozz:

    Иван Сагалаев, спасибо Вам! Отличная статья! Отличная главным образом от других тем, что понятна простому человеку не умудренному технической терминологией… К сожалению, пока еще очень мало хорошей литературы по “продвинутому” CSS на русском языке, и Ваши статьи очень помогают. Я новичок в CSS, но уже полностью отказываюсь от табличной верстки. Мне самому пришлось перерыть кучу информации пока строил “резиновый” шаблон трех-колончатого сайта (посмотреть можно здесь), но мне так и не удалось избавиться от лишних обрамляющих контейнеров и некоторых ошибок (например, наполнение основного контента в середине должно всегда быть больше, чем по бокам, иначе дизайн портится)

    Хотелось бы узнать, где еще можно почитать на русском про позиционирование, блоки, рамки, возможности и т.д. CSS? Поделитесь ссылками!
    А от себя делюсь этой, очень занимательной ссылкой - кстати, было бы отлично, если кто-то перевел все это на русский..

  21. Ozz:

    Throy: посмотри также и это. Рекомендую всем!

  22. H.NET :: Выпуск #200 (юбилейный) :: August :: 2005:

    […] Раскладка в CSS: позиционирование. Хорошая статья про способы позиционирования блоков HTML-документа с помощью CSS. Написана удивительно грамотно и хорошо иллюстрирована. Такое редко встречается среди русскоязычных статей по этой теме. В CSS существует, по большому счету, четыре способа раскладывать боксы по странице: прямой поток, позиционирование, float’ы и таблицы. К сожалению, ни один из них не дает полного набора средств, которыми можно было бы сверстать что-нибудь реальное. Поэтому используются они обычно все вместе, да еще и с изрядной долей хаков для обхода несовместимостей в реализациях стандарта. […]

  23. radiant’s blog » [webdev] css:

    […] Link: Раскладка в CSS: позиционирование […]

  24. Kildor:

    position: fixed
    Спасибо за гениальный код… ;-)
    вообще, fixed можно реализовать и через top:expression();, но у вас — гораздо элегантнее…

    Однако такой вопрос — сия конструкция не хочет работать в опере — так и задумано? Конечно, это элементарно обходится через * > #menu {
    position :fixed;
    }
    но всё-же…

  25. Иван Сагалаев:

    Код, может, и гениальный, но не мой :-). Это первое или второе, что попалось в Google. Мне было важно сам принцип показать: что просто позиционирующийся бокс выносится за тот элемент, который скролится.

    В Опере не проверял. Однако, мне написали, что я нашел не совсем корректный способ, есть более общий, который должен работать везде: http://tagsoup.com/-dev/null-/css/fixed/