четверг, 14 августа 2014 г.

[вёрстка] Как сделать вкладки на чистом CSS и HTML (часть вторая)

В первой части мы рассмотрели, как сделать вкладочный интерфейс при помощи свойства overflow и ссылок-якорей. Этот способ совместим со всеми браузерами, включая самые древние.

Сегодня поговорим о том, как вкладки делаются при помощи небольшой магии с переключателями (радио-кнопками). Этот способ является более технологичным и гибким и более соответствует духу CSS.

Чтобы запоминать выбранную пользователем вкладку, нам нужно хранить некоторое состояние, то есть нам нужны переменные. Работать с переменными мы можем из языков программирования, таких как JavaScript. Но поскольку наша задача — сделать вкладки без JavaScript, нужно придумать что-то еще. Как я говорил в предыдущей части, CSS является описательным языком и не имеет переменных. Тем не менее, запомнить «куда нажал пользователь» мы всё же можем, применив одну хитрость. Для этого используем имеющиеся в HTML элементы управления: флажки (англ. checkboxes) и переключатели (англ. radio buttons).

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

<div class="notebook">
  <input type="radio" name="notebook1" id="notebook1_1" checked>
  <label for="notebook1_1">Первая вкладка</label>

  <input type="radio" name="notebook1" id="notebook1_2">
  <label for="notebook1_2">Вторая вкладка</label>

  <input type="radio" name="notebook1" id="notebook1_3">
  <label for="notebook1_3">Третья вкладка</label>

  <input type="radio" name="notebook1" id="notebook1_4">
  <label for="notebook1_4">Четвертая вкладка</label>
</div>

Выглядит пока совсем не похоже на вкладки, но мы это быстро исправим:

Некоторые пояснения для тех, кто не очень хорошо знаком с HTML:

  • Все переключатели, относящиеся к одному набору, должны иметь одинаковое свойство name. Именно по этому свойству браузер понимает, к какому набору относится переключатель.
  • Метка (тэг label) связывается с переключателем по свойству id. У каждого переключателя должен быть свой уникальный id, а у каждой метки в свойстве for должен быть указан id переключателя, к которому она относится.
  • Свойством checked отмечен переключатель, который будет включен по умолчанию.

Так... Но на самом деле, наш код надо немного подправить. Я буду выстраивать элементы при помощи режима inline-block, и нам будут мешать лишние пробелы. Между тегами input и label не должно быть никаких пробелов и переводов строк, это важно для правильного прилегания элементов друг к другу. Поэтому все лишние символы удаляем:

<div class="notebook">
    <input type="radio" name="notebook1" id="notebook1_1" checked
    ><label for="notebook1_1">Первая вкладка</label
    ><input type="radio" name="notebook1" id="notebook1_2"
    ><label for="notebook1_2">Вторая вкладка</label
    ><input type="radio" name="notebook1" id="notebook1_3"
    ><label for="notebook1_3">Третья вкладка</label
    ><input type="radio" name="notebook1" id="notebook1_4"
    ><label for="notebook1_4">Четвертая вкладка</label>
</div>

Чтобы код не выглядел совсем страшно без переводов строк, я поставил перевод строк внутри тегов — эти переводы строк браузер проигнорирует.

В прошлой статье мы выстроили заголовки вкладок в горизонтальном нправлении при помощи флоатов, и лишние пробелы нам ничуть не мешали. Но я ведь собирался показать вам новые возможности CSS. А новые возможности — новые проблемы. :)

Теперь добавим стили, действуя по принципу, который был рассмотрен в этой статье:

.notebook {
    white-space: nowrap;
    overflow: hidden;
}

.notebook > input {
    display: none;
}

.notebook > input + label {
    display: inline-block;
    border: 1px solid gray;
    padding: 4px;
    cursor: pointer;
    position: relative;
}

.notebook > input + label:not(:last-of-type) {
    border-right: none;
}

.notebook > input + label:hover {
    color: blue;
}

.notebook > input + label:last-of-type::after {
    display: block;
    border-bottom: 1px solid gray;
    width: 2000px;
    content: "";
    position: absolute;
    bottom: -1px;
    left: 100%;
}

.notebook > input:checked + label {
    color: blue;
    border-bottom: none;
    padding-bottom: 5px;
}

.notebook > input:checked + label:last-of-type::after {
    bottom: 0px;
}

Что происходит в этих стилях? Во-первых, мы скрываем настоящие переключатели, выставляя им display: none. Метки мы выстраиваем в линию при помощи display: inline-block.

Аттрибут white-space: nowrap говорит браузеру не разбивать эту линию на несколько строк, если места по горизонтали на экране не хватает. Если браузер попытается сделать из нашей строки несколько строк, всё форматирование разъедется. Поэтому мы запрещаем ему это.

Для меток назначается граница при помощи border: 1px solid gray. Но правая граница у всех меток, проме последней убирается — чтобы не было неопрятно выглядящих двойных границ между метками. Обратите внимание, как «работает» селектор .notebook > input + label:not(:last-of-type) — он означает: «для всех label внутри .notebook, которые находятся сразу после input, кроме последней». Если вы активно изучаете CSS, можете сейчас вернуться назад и сравнить с тем, как я рисовал границы в предыдущей статье. Я использую здесь новые возможности селекторов по максимуму.

Селектор .notebook > input:checked + label поможет нам задать внешний вид метки для включенного переключателя, т.е. для активной вкладки. В этом примере я изменил цвет текста и убрал нижнюю границу.

Селектором .notebook > input + label:hover аналогично задаётся стиль метки при наведении на неё мыши.

cursor: pointer задаёт внешний вид указателя мыши при наведении на метку — такой же, как при наведении на ссылку.

При помощи псевдоэлемента .notebook > input + label:last-of-type::after задаётся линия вправо от последней метки.

Результат:

Отлично! Теперь это похоже на настоящий вкладочный интерфейс. Осталось добавить содержимое вкладок. Каждую вкладку опишем в отдельном div и заполним каким-нибудь содержимым:
<div class="notebook">
    <input type="radio" name="notebook1" id="notebook1_1" checked
    ><label for="notebook1_1">Первая вкладка</label
    ><input type="radio" name="notebook1" id="notebook1_2"
    ><label for="notebook1_2">Вторая вкладка</label
    ><input type="radio" name="notebook1" id="notebook1_3"
    ><label for="notebook1_3">Третья вкладка</label
    ><input type="radio" name="notebook1" id="notebook1_4"
    ><label for="notebook1_4">Четвертая вкладка</label>
    <div>
        <h3>Это первая вкладка. :)</h3>
    </div>
    <div>
        <h3>Это вторая вкладка!</h3>
        <p>Бесцветные зелёные мысли яростно спят.</p>
    </div>
    <div>
        <h3>А это третья вкладка.</h3>
        <p>— На суку сидит ворона и клюет своя нога.<br>
        — Почему клюет нога?<br>
        — У него такой судьба.</p>
    </div>
    <div>
        <h3>А вот и четвёртая вкладка</h3>
        <p><img src="http://www.kolobok.us/smiles/standart/blum3.gif"/></p>
    </div>
</div>
Дописываем стили:
.notebook > div {
    white-space: normal;
    display: none;
    border: 1px solid gray;
    border-top: none;
    margin: 0px;
    padding: 2px 20px;
}

.notebook > input:nth-of-type(1):checked ~ div:nth-of-type(1),
.notebook > input:nth-of-type(2):checked ~ div:nth-of-type(2),
.notebook > input:nth-of-type(3):checked ~ div:nth-of-type(3),
.notebook > input:nth-of-type(4):checked ~ div:nth-of-type(4) {
    display: block;
}

Правила CSS, которые заставят наши вкладки переключаться, заключены в этих длинных селекторах: input:nth-of-type(1):checked ~ div:nth-of-type(1), input:nth-of-type(2):checked ~ div:nth-of-type(2) и т.п. Они означают «первый div, расположенный после включенного первого переключателя», «второй div, расположенный после включенного второго переключателя» и т.п. Этими селекторами вы выбираем, какую вкладку показать, в зависимости от состояния переключателя.

Обратите внимание, что для меток я давал селекторы через символ плюс. Плюс означает «элемент такой-то, который идёт сразу после такого-то элемента». Здесь я указал селекторы через тильду. Тильда означает «элемент такой-то, который идёт после такого-то элемента, но между ними могут быть другие элементы».

Результат:

Это первая вкладка.

Это вторая вкладка

Бесцветные зелёные мысли яростно спят.

Это третья вкладка.

— На суку сидит ворона и клюет своя нога.
— Почему клюет нога?
— У него такой судьба.

А вот и четвёртая вкладка

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

Анимированные вкладки со скруглёнными уголками

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

  • Заголовок вкладки 1
  • Заголовок вкладки 2
  • Заголовок вкладки 3
  • Заголовок вкладки 4
  • Содержимое вкладки 1
  • Содержимое вкладки 2
  • Содержимое вкладки 3
  • Содержимое вкладки 4

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

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

  • Заголовок вкладки 1
  • Содержимое вкладки 1
  • Заголовок вкладки 2
  • Содержимое вкладки 2
  • Заголовок вкладки 3
  • Содержимое вкладки 3
  • Заголовок вкладки 4
  • Содержимое вкладки 4

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

Чтобы сделать плавное анимированное переключение вкладок, мы сложим все вкладки друг на друга при помощи position: absolute и z-index и укажем подходящие задержки для transition.

Вот что получилось:

Это первая вкладка с котом.

Это вторая вкладка с лисой

Это третья вкладка с волком.

Это четвёртая вкладка с рысью

Исходный код примера:

<style>
.notebook2 {
    width: 640px;
    height: 450px;
    position: relative;
}

.notebook2 > input {
    display: none;
}

.notebook2 > div {
    position: absolute;
    box-sizing: border-box;
    top: 30px;
    left: 0px;
    bottom: 0px;
    right: 0px;
    border-radius: 10px;
    padding: 0px 1em;

    z-index: 0;
    background-color: #666;

    transition: all 0.5s ease 0s, z-index 0s 0.5s;
}

.notebook2 > div > *:first-child {
    display: block;
    position: absolute;
    box-sizing: border-box;
    top: -30px;
    width: 150px;
    height: 30px;

    font-family: Arial;
    font-size: 13px;

    border-radius: 10px 10px 0px 0px;
    background-color: inherit;

    color: #fff;
    line-height: 28px;
    cursor: pointer;
    text-align: center;

    transition: all 0.5s ease 0s;
}

.notebook2 > div > *:not(:first-child) {
    opacity: 0;
    transition: all 0.5s ease 0s;
}

.notebook2 > div:nth-of-type(1) > *:first-child { left: 20px; }
.notebook2 > div:nth-of-type(2) > *:first-child { left: 170px; }
.notebook2 > div:nth-of-type(3) > *:first-child { left: 320px; }
.notebook2 > div:nth-of-type(4) > *:first-child { left: 470px; }

.notebook2 > input:nth-of-type(1):checked ~ div:nth-of-type(1),
.notebook2 > input:nth-of-type(2):checked ~ div:nth-of-type(2),
.notebook2 > input:nth-of-type(3):checked ~ div:nth-of-type(3),
.notebook2 > input:nth-of-type(4):checked ~ div:nth-of-type(4) {
    z-index: 10;
    background-color: #ccc;
    transition: all 0.5s ease 0.5s, z-index 0s 0.5s;
}

.notebook2 > input:nth-of-type(1):checked ~ div:nth-of-type(1) > *:first-child,
.notebook2 > input:nth-of-type(2):checked ~ div:nth-of-type(2) > *:first-child,
.notebook2 > input:nth-of-type(3):checked ~ div:nth-of-type(3) > *:first-child,
.notebook2 > input:nth-of-type(4):checked ~ div:nth-of-type(4) > *:first-child {
    color: #333;
    transition: all 0.5s ease 0.5s;
}

.notebook2 > input:nth-of-type(1):checked ~ div:nth-of-type(1) > *:not(:first-child),
.notebook2 > input:nth-of-type(2):checked ~ div:nth-of-type(2) > *:not(:first-child),
.notebook2 > input:nth-of-type(3):checked ~ div:nth-of-type(3) > *:not(:first-child),
.notebook2 > input:nth-of-type(4):checked ~ div:nth-of-type(4) > *:not(:first-child) {
    opacity: 1;
    transition: all 0.5s ease 0.5s;
}

</style>

<div class="notebook2">
    <input type="radio" name="notebook2a" id="notebook2a_1" checked="checked">
    <input type="radio" name="notebook2a" id="notebook2a_2">
    <input type="radio" name="notebook2a" id="notebook2a_3">
    <input type="radio" name="notebook2a" id="notebook2a_4">

    <div>
        <label for="notebook2a_1">Первая вкладка</label>
        <p>Это первая вкладка с котом.</p>
        <p><img src='https://lh3.googleusercontent.com/-6ac0fJDitng/VekGjxXrAZI/AAAAAAAAAUM/svkfw3ICSqA/s400-Ic42/1656410_6222d0c3.jpg'></p>
    </div>
    <div>
        <label for="notebook2a_2">Вторая вкладка</label>
        <p>Это вторая вкладка с лисой</p>
        <p><img src='https://lh3.googleusercontent.com/-iVaTOyL6ZNw/VekGjo3YHLI/AAAAAAAAAUQ/r-GTiK3m1wk/s400-Ic42/WL_M_F_003.jpg'></p>
    </div>
    <div>
        <label for="notebook2a_3">Третья вкладка</label>
        <p>Это третья вкладка с волком.</p>
        <p><img src='https://lh3.googleusercontent.com/-4cateiSadRE/VekGwBksb-I/AAAAAAAAAUY/4WrLlsRJPKk/s400-Ic42/wpapers_ru_%2525D0%252592%2525D0%2525BE%2525D0%2525BB%2525D0%2525BA-%2525D0%2525BD%2525D0%2525B0-%2525D1%252584%2525D0%2525BE%2525D0%2525BD%2525D0%2525B5-%2525D0%2525BD%2525D0%2525B5%2525D0%2525B1%2525D0%2525B0.jpg'></p>
    </div>
    <div>
        <label for="notebook2a_4">Четвертая вкладка</label>
        <p>Это четвёртая вкладка с рысью</p>
        <p><img src='https://lh3.googleusercontent.com/-VbI7_zCH-Eg/VekG8CK_t5I/AAAAAAAAAUg/ZuexmL-1lT8/s400-Ic42/2.jpg'></p>
    </div>
</div>


Вот и всё на сегодня. Далее я напишу о том, как делается переключение вкладок при помощи :target, а также о том, как сделать вкладки, которые не требуют щелчка мышью.

17 комментариев

Анонимный

Спасибо, отличная статья. К сожалению, у меня этот способ не работает с Internet Explorer.

Андрей

Возник один нюанс.
Мне нужно сделать на странице блоков 10 из вкладками.
При создание одного блока все работает прекрасно, но когда создал второй появился глюк. При клике на переключения табов на втором блоке они переключаются на первом
Как создать несколько блоков из вкладками чтоб они все работали коректно?

Анонимный

Скажите, пожалуйста, а можно вынести div class="page" из div class="notebook"? Если можно, то как корректно изменить стиль?

sdc

Андрей,

У каждой группы вкладок должны быть свои уникальные идентификаторы. В коде в статье вы видите такие наборы свойств в HTML-коде:
В первом теге input: name="notebook1" id="notebook1_1".
Во втором теге input: name="notebook1" id="notebook1_2".
И так далее.
В первом теге label: for="notebook1_1"
Во втором теге label: for="notebook1_2"
И так далее.

notebook1 - уникальный идентификатор набора вкладок.
Во втором наборе вкладок замените везде notebook1 на notebook2.
В третьем - на notebook3 и т.п.

Идентификаторы вкладок проставляются аналогично:
notebook2_1 - первая вкладка второго набора
notebook2_2 - вторая вкладка второго набора
notebook2_3 - третья вкладка второго набора
и т.д.

Анонимный

Спасибо большое! Очень помог Ваш пример!
P.S. Очень милые картинки ^^

Laura

Спасибо, большое, очень красиво!!!

Анонимный

Добрый день.
А как решить вопрос со вставкой Яндекс карты, которая не показывается в неактивном табе? При переключении табов возникает серый квадрат вместо карты. Спасибо.

Unknown

можно ли задать в последнем варианте атрибутом hover изменение шрифта при наведении?

Анонимный

Отличная статья! +1 к карме)

Виталий Баранов

Автор пишет, что "При помощи псевдоэлемента .notebook > input + label:last-of-type::after задаётся линия вправо от последней метки". При этом в коде псевдоэлемента нет ни строчки о правой границе, а задается блок шириной 2000px с содержанием content:" " и абсолютным позиционированием. Что здесь происходит?

Сергей

Хорошая, доходчивая статья. Только у меня маленькая проблема возникла. Между рамками названий вкладок есть небольшой промежуток. Как его убрать? Margin-left: -4px не очень красивое решение

Oleksandra Kosenko

Подскажите пожалуйста, пытаюсь сделать пятую вкладку, она создается, но на ней ничего не отображается, я немного сократила css, может что то важное убрала, честно говоря некоторые строки не совсем понимаю..
в html я добавила инпут пятый и див, id у меня разные, name одинаковые
input type="radio" name="db_tables" id="5 ....
CSS целый код писать не буду, я лишь добавила вот эту строку
.......
.DB > input:nth-of-type(5):checked ~ div:nth-of-type(5){
.....
но ничего не отображается, я чтото делаю не так))

Timo A. Weiss

Второй, анимированный пример с фиксированной высотой div class="notebook2"
Как сделать, чтобы высота была авто?
У меня не работает.

Unknown

Спасибо! Все очень понятно и полезно!!!

Юлия

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

Dave

Шикарно описано. Большое спасибо

Dave

Юлия, таргет на id пробовали??
Timo A. Weiss, так потому что position: absolute у дивов.

Отправить комментарий