Frontend Dev @ EPAM Systems
0,0
рейтинг
16 января 2012 в 18:12

Разработка → Модальные окна на CSS из песочницы

CSS*
В наше время для различных сайтов нормой стали всевозможные всплывающие модальные окна — popup'ы — для регистрации, авторизации, информационные окна, — всевозможных форм и размеров. Также существует огромное количество плагинов к тому же jQuery для простого и удобного создания таких попапов — тот же Shadowbox, например.



Внешний вид, размеры и оформление таких попапов совершенно разнообразными — с оверлеем, тенюшками, анимациями — всего не счесть. Объединяет их только, пожалуй, тот факт, что обычно они выводятся в самом центре страницы — как по горизонтали, так и по вертикали. И центрирование это производится средствами JS. Я не буду вдаваться в подробности этих расчетов, опишу их лишь вкратце:

HTML-код попапа обычно имеет такую структуру:

<div class="popup__overlay"></div> <!-- Оверлей --><br/><div class="popup">...</div> <!-- Попап вместе с содержимым --> <br/>

И CSS (здесь и ниже я умышленно буду опускать написание некоторых свойств, необходимых лишь для некоторых браузеров и их версий, оставив лишь самое основное):

.popup__overlay {<br/>    position: fixed;<br/>    left:  0;<br/>    top:  0;<br/>    background: #000;<br/>    opacity: .5;<br/>    filter: alpha(opacity=50);<br/>    z-index: 999<br/>    }<br/>.popup {<br/>    position: absolute;<br/>    width: 20%;<br/>    z-index: 1000;<br/>    border: 1px solid #ccc;<br/>    background: #fff<br/>    } <br/>

JS определяет браузер и версию браузера, и на основании этого высчитывает размеры рабочей области и размеры самого попапа (если они не заданы), а затем производятся нехитрые вычисления положения его левого верхнего угла (css-свойства left и top для .popup). Многие плагины также реагируют на изменение размеров страницы, пересчитывая всё это дело каждый раз, с тем, чтобы попап располагался точно в центре рабочей области.

Я по натуре своей перфекционист (знаю, порой это плохо), и частенько заморачиваюсь даже над мелкими деталями, пытаясь улучшить и добавить максимально возможную расширяемость этим деталям, и меня не мог не зацепить именно этот момент в работе всех этих плагинов. Возникла мысль, что всю работу по позиционированию попапа можно переложить с плеч JS на плечи самого браузера, то есть выполнять эту работу средствами CSS.

Этим и займёмся.

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

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

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

<div class="popup__overlay"><br/>    <div class="popup"></div><br/></div> <br/>

И немного CSS:

.popup__overlay {<br/>    position: fixed;<br/>    left:  0;<br/>    top:  0;<br/>    width: 100%;<br/>    height: 100%;<br/>    z-index: 999<br/>    }<br/>.popup {<br/>    } <br/>

Фиксированные размеры

Самый простой вариант. Ничего нового изобретать не нужно:

.popup {<br/>    left: 50%;<br/>    top: 50%;<br/>    width: 400px;<br/>    height: 200px;<br/>    margin-left: -200px;<br/>    margin-top: -100px<br/>    } <br/>

Отрицательные margin'ы в половину ширины и высоты — банально и скучно, ничего оригинального в этом нет. Идём дальше.

Размеры попапа зависят от содержимого

Сперва — выравнивание по горизонтали — это вроде бы проще. Если попап фиксированной ширины — то достаточно будет следующего:

.popup {<br/>    margin: auto<br/>    } <br/>

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

Вертикальное выравнивание. Здесь уже становится интересно. С такой задачей, конечно, без проблем справилась бы таблица или эмуляция таблицы с помощью display: table & display: table-cell, но заставить такое работать в старых IE — себе дороже. Таблица также отпадает — по понятным причинам.

Итак, margin уже отпадает — размеров мы не знаем. Вспоминаем, что же есть из свойств с подобными эффектами. Ага, text-align. Но только для инлайновых элементов. ОК. Кажется, сам Бог велел использовать display: inline-block — блочный элемент, к которому можно было бы применить свойства для инлайновых элементов. С поддержкой этого свойства у всех браузеров тоже всё, можно сказать, в порядке. Код становится примерно таким:

.popup__overlay {<br/>    position: fixed;<br/>    left:  0;<br/>    top:  0;<br/>    width: 100%;<br/>    height: 100%;<br/>    z-index: 999;<br/>    text-align: center<br/>    }<br/>.popup {<br/>    display: inline-block;<br/>    vertical-align: middle<br/>    } <br/>

Остаётся вертикальное выравнивание — нам подойдёт vertical-align. В любой другой ситуации было бы также уместно использовать line-height, но поскольку у нас нет фиксированной высоты страницы (line-height в данном контексте), здесь использовать её нельзя. На помощь приходит один трюк с вертикальным выравниванием элементов неизвестных размеров. Я точно помню, что нашел этот способ на Хабре, но, к сожалению, не смог найти ссылку на тот топик. Заключается этот способ в следующем: добавляется inline-block элемент нулевой ширины и 100%-ой высоты родителя, который «расхлопывает» высоту строки до 100% высоты родителя, то есть до высоты рабочей области страницы. Сделаем это изящнее — вместо лишней разметки воспользуемся псевдоэлементами:

.popup__overlay:after {<br/>    display: inline-block;<br/>    width:  0;<br/>    height: 100%;<br/>    vertical-align: middle;<br/>    content: ''<br/>    } <br/>

Осталось добавить полупрозрачное затемнение оверлея — с этим справится rgba. Всё! Теперь положение попапа регулируется только средствами браузера на уровне CSS.

Итоговый пример

Из замеченных минусов метода — порой возникают глюки с отображением в IE 6-7, в частности, при использовании флоатов.

Повторюсь — мой метод не избавляет от использования JS вообще, он лишь частично оптимизирует скорость работы всплывающих окон за счет переноса части нагрузки с JS на CSS.

Уверен, что метод можно улучшить — как визуально, так и изнутри, и буду рад любым идеям и предложениям по этому поводу, а также замечаниям по работе и отображению в различных браузерах.

UPD: По просьбам, добавил пример, работающий в IE7+ — http://jsfiddle.net/vend3tta/SE4tS/
Yevhen Isakov @Vend3tta
карма
8,0
рейтинг 0,0
Frontend Dev @ EPAM Systems
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (40)

  • +6
    Чего только стоит избавиться от «гемороя» расчета координат для центровки. Здорово!
  • +1
    Самое интересное не разъяснили:
    С такой задачей, конечно, без проблем справилась бы таблица или эмуляция таблицы с помощью display: table & display: table-cell, но заставить такое работать в старых IE — себе дороже. Таблица также отпадает — по понятным причинам.

    Расскажите, пожалуйста, по каким понятным причинам отпадает таблица? Ведь проблем с ней нет ни в старых версиях браузера, ни в новых.

    Далее. В каком доктайпе это работает? На странице JSFiddle стоит xhtml1-strict, хотя по настройкам слева — HTML5, а документ в фрейме. Означает ли это, что у документа в фрейме — тот же самый доктайп xhtml1-strict (он виден в Firebug, например)? А как вообще по-настоящему в HTML5 и кроссбраузерно — это везде работает? В котором проверяли?
    • +3
      Таблица — это не кошерно, что ли… Да и потом, завернуть всё в таблицу можно всегда, и это гарантированно будет работать — но ведь это скучно, гораздо интереснее заставить этот код работать во всех браузерах без использования таблиц, насколько это возможно.

      В примере на jsFiddle доктайп html5. А о вопросе проверки разных доктайпов, честно говоря, не задумывался — для меня этот вопрос отпал больше года назад, с тех пор использую только doctype html. Можно вовсе отказаться от использования псевдоэлементов, добавив вместо этого один лишний элемент в разметку.
      • –3
        Это сайт так пишет, что HTML5, а Firebug определяет xhtml1-strict (v.1.90, Fx8.0 Lin) и исходный код страницы подтверждает. Посмотрите по Ctrl-U — «xhtml1-strict.dtd». В Опере то же самое. (Это к тому, что надо же знать, что мы верстаем. Я делал такое окно на таблице и тоже в xhtml1-strict.dtd. Особенно большие затраты занимает поддержка IE6(с деградацией)-7 с тенями и бордюрами. За другие способы не брался, потому что IE7 критичен, а что он там выкинет без таблиц — трудно предполагать, к тому же, с эффектами теней.)

        Если будем брать HTML5 то будет иметь значение, какой фреймворк будем использовать для поддержки старых браузеров, а тут (у Вас) вроде нет никакого (и даже нового доктайпа нет). Попробуйте приложить фреймворк — где-то что-то поедет, и — работу начинать сначала. Результат, конечно, интересный, но по этой причине он ничего не доказывает — можно или нельзя делать попапы без таблиц во всех (хотя бы от IE7) браузерах.
        • НЛО прилетело и опубликовало эту надпись здесь
          • 0
            Спасибо, всегда удивляюсь Вашим глубоким знаниям :). (Без шуток.) Суть-то тут как раз в старых браузерах. Для IE9+Fx4+Chrome от где-то 10-12-го?, Оперы 11, Safari5 — всё это делается вообще очень легко на одном дыхании :). А вот необходимость второй вёрстки для старых браузеров вместо одной общей заставит отказаться от новаторства и делать по традиции :). (Правда, для меня это на настоящий момент осталось на уровне хобби, ну и хорошо, пусть они отмирают пока.)
            • НЛО прилетело и опубликовало эту надпись здесь
            • 0
              DOCTYPE html везде рендерится одинаково и предсказуемо. Потому, что по-большому счёту браузерам плевать на доктайп. Есть два режиме: quirks и standards. Только IE9 при старых доктайпах отрубает всякие фишки в CSS/JS, всем остальным — плевать. Вы можете юзать XHTML Strict и понавставлять фреймов тонну — и всё будет как и задумано.

              Ставьте всегда DOCTYPE html и не мучайтесь.
              • НЛО прилетело и опубликовало эту надпись здесь
                • 0
                  Almost же только в Firefox, не?
                  • НЛО прилетело и опубликовало эту надпись здесь
    • НЛО прилетело и опубликовало эту надпись здесь
      • +1
        Да, посмотрел, во фрейме, действительно <!DOCTYPE html>, спасибо. Тогда остаются остальные замечания выше, по поводу отсутствия СSS-фреймворка под HTML5. Т.е. интересно, но не очень практично; нельзя сделать однозначные выводы, будет ли работать при полноценном подходе где-то на сайте.
        • НЛО прилетело и опубликовало эту надпись здесь
    • НЛО прилетело и опубликовало эту надпись здесь
  • –3
    position: fixed?
    display: inline-block?
    content: ''?
    :after?

    Я уже боюсь открыть это в IE6 :)
    • +7
      Say goodbie IE6 :) www.ie6countdown.com
      • 0
        goodbye же
    • 0
      Под IE 6 данным метод, пожалуй, не рассчитан. Точнее, заставить его работать в IE6 можно, только овчинка выделки не стоит.

      В остальном — inline-block в IE 6-7 заставить работать можно, а использование псевдоэлемента :after — лишь в угоду лаконичности кода. Conditional comments и еще один лишний элемент — вот и вся хитрость.

      Пожалуй, мне стоит добавить ссылку на пример, работающий в IE 7+. Сейчас сделаю.
      • 0
        Добавил пример для IE7+ в конце статьи.
  • –1
    Просто inline-block будет работать не во всех браузерах.
    Могу предложить так:

    display:-moz-inline-stack;
    display:inline-block;
    _overflow:hidden;
    *zoom:1;
    *display:inline;

    Не помню где вычитал эту кучу хаков, но работает даже в ИЕ-6 ))
    • +3
      -moz-inline-stack это уже прошлый век, попробуй найди такого динозавра, который сидел бы на FF2. В остальном — да, можно только без overflow обойтись. Да, и zoom после *display: inline — сначала сделать инлайн, затем включить hasLayout.
      • 0
        Старый способ, не спорю.
        overflow нужен для ИЕ6, без него помнится не работало )) Хотя уже давно под него ничего не верстал, не помню если честно.
        • 0
          Не, для ие6 overflow не нужен. Хватит вот этого:
          .cls {
              display: inline-block;
              /display: inline;
              /zoom: 1;
          }
  • +1
    Во-первых этот способ не абсолютно универсален. Добавляем очень много текста и все поплыло.
    Для того, чтобы не поплыло, надо указать max-width попапа < width ширины окна или родителя.
    • 0
      Спасибо за замечание, действительно есть такое, как-то упустил этот момент из виду. Обновлю примеры, с учётом вашего замечания.
  • +3
    Кстате, если добавить к открытию модального окна:
    $('body').css('overflow','hidden');
    а в закрытие соответственно overflow:auto, то избавимся от возможности скролить страницу, что очень важно для модальных окон. Удивлялся, почему про это забывают многие. Или здесь есть подводные камни?
    • +1
      Только если окно гарантированно маленькое. Не слишком приятно обнаружить, что кнопка отправки формы под сгибом.
      • НЛО прилетело и опубликовало эту надпись здесь
  • 0
    Для закрывающей кнопки лучше использовать & times;
  • 0
    Высоту и ширину в данном случае .popup мы знаем.
    Не проще сделать так:

    .popup {
    position:absolute;
    width:200px;
    height:160px;
    margin-left:-80px;
    margin-top:-100px;
    left:50%;
    top:50%;
    }
    • 0
      margin-left:-100px;*
      margin-top:-80px;*
      Фиксированно по центру получается.
      • 0
        Да то же самое получается :)
  • +5
    А теперь задача следующая:
    Убираем лишний элемент для оверлея и делаем оверлей псевдоэлементом самого попапа (:before). Центрировать попап можно и через:
    .popup {
    position: fixed;
    top: 50%;
    left: 50%;
    -webkit-transform: translate(-50%, -50%);
    -moz-transform: translate(-50%, -50%);
    -ms-transform: translate(-50%, -50%);
    -o-transform: translate(-50%, -50%);
    transform: translate(-50%, -50%);
    }


    Для IE < 9 можно высоту считать либо через expression либо джаваскрипт (на выбор). Также для IE < 8 нужен простенький фолбек скрипт для заворачивания в <div class="_ie_popup_overlay"></div> и стили соответствующие.
    • НЛО прилетело и опубликовало эту надпись здесь
  • НЛО прилетело и опубликовало эту надпись здесь
  • +2
    Вот интересно, каких только фич не добавили в CSS3, когда же сделают нормальное выравнивание. А то для такой простой задачи как вывод блока по центру экрана, приходится кучу лишнего css писать…
    Почему нельзя сделать, что-то типа
    align: center middle;
  • +2
    Кстати, советую посмотреть на мой вариант попапов только на CSS: kizu.ru/fun/popups/#SomePopup2 — правда, я там акцентировал внимание на том, чтобы показывать/скрывать попап используя только CSS, и на анимации появления/скрытия через транзишны, т.е. над центрированием по вертикали не заморачивался.

    Но, может, заморочусь как-нибудь — иногда это бывает надо, да.
  • 0
    Для центрированием по вертикали использую похожий метод: dabblet.com/gist/1629941 (нашел на CSS-Tricks)
    Только вот он работает, в отличие от того Fiddle, ссылку на который автор дал в конце статьи.
  • 0
    Прошу прощения, а есть ли CSS-растягивание на весь экран не только по горизонтали, но и по вертикали.
    Я имею ввиду вот что. Вот имеется к примеру 9 блоков:

    1 2 3
    4 5 6
    7 8 9

    Их может быть и 4 на 4 и хоть 10 на 5, тут не важно. Тут важно, чтобы например блоки 1 3 7 9 растекались по углам. Блок 2 сверху, блок 8 снизу, соответственно 4 слева и 6 справа, а 5 в центре.

    Т.е. через CSS оно бы раскрывалось на весь экран и не важно какого он разрешения.

    Ну или хотя бы просто 3 блока (123) (456) (789), чтобы 123 всегда был прижат к верху (ну это и так понятно), а вот чтобы 789 всегда был прижат к низу, а в центре был 456.

    Хотя с девятью блоками тоже интересно.

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