Pull to refresh

Практический CSS: рецепт успеха

Reading time 11 min
Views 12K
Original author: Paul OB
Ниже располагается перевод заметки CSS — A Recipe for Success, в которой рассматривается создание средствами HTML/CSS в браузере некоторого образца меню. В статье освещены довольно интересные случаи, и подробно описано их решение.

Если вы посмотрите на стандартное ресторанное меню (или рецепт), там слева расположены названия блюд, за ними идет пунктирная линия до правого края, где расположена цена этого блюда. Давайте посмотрим на рисунок 1, чтобы понять, о чем идет речь.

Рисунок 1. Стандартное меню
Рисунок 1

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


Пунктирные линии



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

Нам предстоит преодолеть две основные проблемы:

Во-первых, нам нужно расположить текст и слева, и справа на одной линии.

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

Слева и справа



Для начала разберемся с позиционированием текста. Для того, что расположить цену справа, используем свойство float и установим его либо для самой цены, либо для названия блюда, но тогда цена должна быть выровнена с помощью text-align:right. Или мы можем задать float для обеих частей списка. Любой из этих методов будет верным, но в данном случае мы воспользуемся последним способом; задав float для описания в left, а для цены — в right.

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

Ниже примерный HTML для элемента списка:

<ul>
     <li>
          <em>Some menu text</em>
          <span>£9.99</span>
     </li>
</ul>


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

#wrap li span{
float:right;
}
#wrap li em{
float:left;
font-style:normal;
}


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

#wrap li{
width:100%;
float:left;
}


Эти стили вкупе с дополнительной разметкой обеспечат нам уже вполне приемлемый вид на рисунке 2

Рисунок 2. Первоначальный вид меню
Рисунок 2

Я не привожу сейчас полный код разметки, его можно скачать по ссылке на актуальную демонстрацию в конце статьи. Это просто базовый CSS и заполненный ненумерованный список, уже полностью описанный выше. Гораздо интереснее будет рассмотреть, как же сделан эффект тени вокруг такого списка, более подробно его можно рассмотреть на Рисунке 3.

Рисунок 3. Эффект тени
Рисунок 3

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

#wrap{
width:500px;
border:1px solid #eff2df;
margin:0 20px;
float:left;
background:#809900;
}
#wrap ul{
padding:20px 40px;
list-style:none;
float:left;
border:1px solid #4c7300;
position:relative;
left:-2px;
top:-2px;
background:#eff2df;
color:#4c7300;
}


Хотя у списка ul не задана ширина, он будет растянут благодаря своим потомкам; но обычно все же лучше сделать элемент статичным, присвоим ему ширину. Можно сделать довольно милый эффект тени, если использовать соответствующие цвета. Граница контейнера должна быть немного светлее его фона, это добавит реалистичности.

Подключаем точки



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

#wrap li{
border-bottom:1px dotted #000;
float:left
}


Рисунок 4. Пунктир под пунктами меню
Рисунок 4


Это выглядит уже достаточно хорошо, но нам нужно спрятать пунктир под текстом с каждого из краем (слева и справа), и немного сместить его по вертикали (чтобы он составлял с текстом единое целое. Этого легко можно добиться с помощью относительного позиционирования текста немного вниз, на место пунктира. Поскольку относительное позиционирование не влияет на поток (flow) документа, сам пунктир при этом никуда не сместится, и эффект будет аналогичен использованию полей (margin). (Для более подробной информации по относительному позиционированию можно прочитать здесь).

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

Таким образом, смещая span и em вниз мы обеспечиваем покрытие пунктира, что нам и требуется. Однако, это еще не все!

Теперь текст находится поверх пунктира, но не прячет его. Для действительного перекрытия нам нужно задать для em и span цвет фона. Этот цвет должен совпадать с текущим цветом фона (прим.: элемента списка или всего списка), чтобы гарантировать полное соответствие. Нам также придется задать для этих элементов соответствующий отступ снизу, что нивелировать смещение самого текста вниз. Все это приведет к следующему:

* {margin:0;padding:0}
h1,h2{padding:10px 20px 0}
#wrap{
width:500px;
border:1px solid #eff2df;
margin:0 20px;
float:left;
background:#809900;
}
#wrap ul{
padding:20px 40px;
list-style:none;
float:left;
border:1px solid #4c7300;
position:relative;
left:-2px;
top:-2px;
background:#eff2df;
color:#4c7300;
}
#wrap li{
border-bottom:1px dotted #000;
line-height:1.0;
margin:0 0 .5em 0;
position:relative;
width:100%;
float:left
}
#wrap li span{
background:#eff2df;
padding:1px 0 1px 5px;
float:right;
color:#000;
position:relative;
top:.2em;
}
#wrap li em{
float:left;
margin:0;
position:relative;
top:.2em;
padding:0 5px 0 0;
background:#eff2df;
font-style:normal;
}


и HTML

<div id="wrap">
    <ul>
        <li><em>Some text</em><span>£9.99</span></li>
        <li><em>Some text a bit longer</em><span>£9.99</span></li>
        <li><em>More text</em><span>£10.00</span></li>
        <li><em>Other text a bit longer</em><span>£11.00</span></li>
        <li><em>text</em><span>£12.00</span></li>
        <li><em>Some text a bit longer</em><span>£9.99</span></li>
        <li><em>More text</em><span>£10.00</span></li>
        <li><em>Other text a bit longer</em><span>£11.00</span></li>
        <li><em>text</em><span>£12.00</span></li>
    </ul>
</div>


На рисунке изображен результат:

Рисунок 5. Меню почти готово
Figure 5

Актуальную версию можно увидеть здесь.

В этой версии я использовал универсальный метод «ластика» (*{margin:0;padding:0}), но я бы советовал использовать более грамотные решения, чтобы, например, элемент form остался без изменений. Для более подробной информации по поводу отступов и полей в браузерах по умолчанию можно ознакомиться с этой заметкой.

Спасаемся от бедствия



Это довольно хороший пример для относительно небольшого по объему кода. Можно изменять размер шрифта, и общий вид останется по-прежнему вполне приемлемым. Если вы уже устали читать, то можно объявить, что поставленная цель, по-видимому, достигнута. Однако, стоит задуматься о том, что же случиться, если текст слева окажется длиннее ширины элемента списка и разобьется на несколько строк. Что произойдет с нашим макетом?

Это можно очень просто проверить, просто добавив немного текста в один из пунктов списка. Результат представлен ниже на рисунке 6.

Рисунок 6. Слишком много текста слева
Рисунок 6

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

Рисунок 7. Правильный вид
Рисунок 7

Текст описания правильно перенесен на следующую строку, и пунктирная линия начинается сразу по его окончанию и продолжается до цены справа. Хотя это выглядит предельно простым, но на нашем пути встретится немало новых подводных камней. Во-первых, стоит вернуться к рисунку 6 и попытаться понять, почему пунктирная линия перешла на следующую строку относительно текста? Можно предположить, что после конца текста идет разрыв, и это будет правильно.

Проблема заключается в том, что у нас оба элемента «плавающие», что означает, что они будут растянуты собственным содержанием и займут все возможные 100% ширины родительского контейнера, сместив другие «плавающие» элементы на следующую строку. Как раз это и произошло на рисунке 6, когда текст описания растянул свой контейнер и занял все доступное место, сместив цену вниз. Если вы спросите о том, почему же цена сползла вниз аж на две строки, то ответом будет прямоугольная форма «плавающего» элемента (текста), который ее сдвинул. Цена просто не может занять его место, поэтому находится там, где находится.

Это будет проще понять, если мы обведем блоки с текстом слева красной линией (рисунок 8).

Рисунок 8. Плавающие блоки слева
Рисунок 8

Когда ширина у «плавающего» блока становится 100%, он смещает правый «плавающий» блок вниз вместе с пунктирной линией. Повторюсь, должно быть какое-то простое и изящное решение, которое бы обеспечивало для текста фиксированную ширину, предотвращая его наползание на правую часть. Можем быть, просто рассчитать оптимальную ширину у текстового контейнера, обеспечив достаточно места для правого блока, и просто ее задать? Однако, как следует из рисунка 9, ситуация с добавлением фиксированной ширины только ухудшилась!

Рисунок 9. Фиксированная ширина у левой части
РИсунок 9

Во-первых, стоит отметить, что пунктир теперь продолжается на до правого самого края как раз из-за фиксированной ширины (его «затирает» цвет фона левой части). Во-вторых, цена расположена слишком высоко для длинного текста. Очевидно, что мы не можем воспользоваться данным методом. Но что нам предпринять? Ага… А что, если нам попробовать убрать обтекание у левой части, сделав ее статичной, а заодно убрать и ширину?

Если мы зададим обтекание только для правой части, а левая будет статичной, нам нужно будет, для начала, поменять порядок их расположения в HTML (правая часть должна быть до содержания, которое она должна обтекать). (Небольшое замечание: вообще говоря, должна существовать возможность оставить HTML без изменений в данном случае, так как блоки с обтеканием должны отображаться в одну линии с линейными (inline) элементами. «Плавающие» элементы смещают вниз только блочные. Однако, с эти нормально справляется только Opera, поэтому нам все же придется изменить наш HTML.)

<li><span>£11.00</span><em>Other text a bit longer</em></li>


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

Рисунок 10. Цена обтекает текст
Рисунок 10

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

Кажется, что с каждым сделанным шагом мы сталкиваемся с новой проблемой, но, фактически, не приближаемся к нашей цели (которая изображена на рисунке 7). Становится очевидным, что изначально нами было выбрано не совсем верное направление, и конечный результат достигается несколько по-другому.

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

Описание и правый блок с обтеканием сейчас начинаются каждый на своей строке, как показано на рисунке 11.

Рисунок 11. Описание и цена на разных строках
Рисунок 11

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

Все это приводит почти к идеальной картине с одним маленьким исключением: теперь между элементами списка у дополнительный разрыв в одну высоту строки. Это можно исправить, задав отрицательное верхнее поле (margin) для элемента списка, что закрыть это смещение. Очевидно, что для этого снова используем em, что позволит сохранить целостность макета и хорошее масштабирование. Точные цифры могут зависеть от вашего конкретного случая, может потребоваться несколько попыток для достижения необходимого эффекта.

И наконец, нам нужно устранить последнее несоответствие — нам нужно, чтобы описание переносилось на следующую строку там, где встречается с ценой, чтобы все выглядело идеально. Я добавил у p отступ (padding) справа в 5em, который сейчас содержит em с текстом описания. Я просто предположил, что 5em должно быть вполне достаточно, чтобы при любой высоте строки текст описания не налезал на цену.

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

<li>
    <p><em>Other text a bit longer</em></p>
    <span>£11.00</span><em>
</li>


Теперь у нас появился дополнительный элемент p (чтобы все браузеры отображали макет аналогично поведению Opera'ы, которое было описано выше), и span снова расположен после текста, но уже после элемента p. (Я оставил span как span, потому что мы рассматриваем этот пример только как демо-версию, более семантически в данном случае будет использовать блочный элемент, например, p. Это, естественно, повлечет применение классов стилей для различения первого и второго абзаца или использование в одном случае div вместо p для уникальности тегов. Но ради большей ясности и прозрачности всего процесса я оставил span.)

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

#wrap li<font>{
line-height:1.2;
margin:-.9em 0 0 0;
position:relative;
float:left;
width:100%;
text-align:left;
border-bottom:1px dotted #000;
clear:both;
}
* html #wrap li{
border:none;
background: url(images/dotted-leader.gif) repeat-x left bottom;
}
#wrap li span{
background:#eff2df;
padding:1px 0 1px 5px;
color:#000;
position:relative;
top:.4em;
float:right;
}
#wrap li em{
margin:0;
position:relative;
top:1.6em;
padding:0 5px 0 0;
background:#eff2df;
}
#wrap p{padding:0 5em 0 0}


Результат показан на рисунке 12 ниже.

Рисунок 12. Меню готово
Рисунок 12

Хмм… выглядит здорово!

Скриншот выше сделан в Firefox 2.0, интересно, как это выглядит в IE6?

Итак...

Рисунок 13. Меню в IE6
Рисунок 13

Выглядит очень хорошо, но что случилось с нашей замечательной пунктирной границей? Она выглядит как набор тире, а не точек, что не соответствует дизайну. Это проблема старых версий IE, когда IE6 (и более ранние) отображают черточки вместо точек для границы, когда размер границы 1 пиксел. Мы с этим ничего не можем поделать. Или можем?

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

* html #wrap li{
border:none;
background: url(images/dotted-leader.gif) repeat-x left bottom;
}


Граница убирается для элемента списка, вместо нее добавляется фоновая картинка. Селекторы, начинающиеся со *, работают только для IE6 и младше.

Этот код уже добавлен в CSS выше, рабочую версию можно посмотреть здесь. Открывайте исходник страницы и просто копируйте код. Весь CSS оставлен прямо в HTML-документе ради удобства.

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

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

Web Optimizator: проверка скорости загрузки сайтов
Tags:
Hubs:
+65
Comments 49
Comments Comments 49

Articles