Как работает автоматическое позиционирование в CSS?

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

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

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

Иллюстрация

Содержащие блоки задают контекст позиционирования

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

В своей публикации я описал как можно создать выпадающее меню с помощью шаблона Suckerfish. Я использую Suckerfish уже давно, так же как наверное и вы. По умолчанию подменю располагается далеко за пределами страницы, обычно на -999em слева от нее. При наведении курсора значение для left изменяется на auto и подменю появляется прямо под родительским пунктом меню.

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

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

Иллюстрация

Как браузеры интерпретируют значение auto?

Мы оба предполагали, что значение auto эквивалентно 0, тоесть запись left:auto равняется left:0. В некоторых случаях значение auto в СSS действительно равняется нулю, но не в этом.

Когда для элемента указано position: absolute, его расположение (и часто размер) регулируются свойствами top, right, bottom и left. Эти свойства указывают отклонение от исходного размещения элемента относительно содержащего блока. Для элементов, прописанных с помощью непарных тегов (<img>, <input>), результат при применении значения auto (для свойства top, right, bottom или left) зависит от того задано ли оно для остальных атрибутов позиционирования.

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

Выпадающее меню Suckerfish задает и впоследствии изменяет значение свойства left, остальные три свойства остаются auto. Когда мы изначально указываем left: -999em, в действие вступает свойство right и подменю располагается далеко за пределами страницы. При наведении курсора, когда значение свойства left меняется на auto, все четыре позиционирующие атрибуты получают значение auto.

Статья об элементах с абсолютным авто-позиционированием объясняет что именно происходит, так же как и эта статья с Dev.Opera об абсолютном и фиксированном позиционировании. Ниже приведена цитата из последней.

Значением по умолчанию для атрибутов top, right, bottom и left является auto, значит блок с абсолютным позиционированием расположен там где он был бы расположен без позиционирования. Однако, он удален из нормального потока и перекрывает любой элемент который следует за ним.

Вы можете увидеть примеры на изображениях ниже и выше в этой статье. На изображении выше меню появляется при наведении курсора на родительскую ссылку. В этом случае значением для свойства left (а также для top, right и bottom) является auto.

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

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

В нашем случае происходит по другому. Когда для всех 4 сторон (или же для двух противоположных сторон) указано значение auto, расположение элемента зависит от интерпретации браузером.

Мы используем такое поведение для выравнивания центрального блока по горизонтали с помощью margin-left: auto и margin-right: auto. В этом случае margin делится на две равные части и элемент центрируется. В случае с выпадающими меню, дочерний список отображается там, где он бы находился без позиционирования.

Иллюстрация

Итог

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

Иногда мы используем те или иные технологии не пытаясь разобраться как они работают. Когда приходится объяснить принцип их работы, мы затрудняемся это сделать. Хотя иногда стоит разобраться в механизме работы используемых приемов.

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