• Верстка табов с помощью <dl>

    Когда-то возникла необходимость сделать табы на странице. Это довольно типовая задача, поэтому, чтобы не изобретать велосипед, полез в Гугл посмотреть, как народ делает. Все реализация, которые я увидел, выглядели довольно стандартно, примерно так:

    <div class="tabs">
    	<ul class="nav">
    		<li>Tab 1</li>
    		<li>Tab 2</li>
    		<li>Tab 3</li>
    		<li>Tab 4</li>
    		<li>Tab 5</li>
    	</ul>
    
    	<ul class="content">
    		<li>Tab content 1</li>
    		<li>Tab content 2</li>
    		<li>Tab content 3</li>
    		<li>Tab content 4</li>
    		<li>Tab content 5</li>
    	</ul>
    </div>
    

    То есть два независимых списка, в одном заголовки, во втором — содержимое.

    Лично меня такая структура не устраивает, главным образом из-за того, что отсутствует семантическая связь между заголовком и содержимым. Хоть я и не страдаю наркозависимостью верстать все «валидно и семантично» ради респекта и уважухи гиков, тут отсутствие четкой связи было все-таки недостатком. Во-первых, данные из бэкэкнда придется выводить в два цикла (сначала заголовки, а потом контент), а не одним. Это хоть немного, но увеличивает объем кода и явно не добавляет производительности. Во-вторых, клиентский скрипт для переключения табов тоже будет больше и сложнее, особенно с учетом того, что на странице может быть несколько блоков с табами.

    Решил попробовать избавиться от этих проблем, а заодно размять мозги 🙂 При выборе структуры элементов первым делом выбор пал на <dl>-список, который как нельзя лучше подходит для этой задачи. Думаю, не я первый решил попробовать сделать табы с помощью этого списка. Вот что у меня получилось.

    Чтобы приведенный пример не стал очередным источником бездумного копи-пэйста, давайте рассмотрим, за счет чего это работает.

    Предположим, у нас есть набор элементов, которым указан float: left:

    stage12

    А что будет, если одному из элементов этой последовательности указать float: right?

    stage21

    Уже неплохо: элементы, следующие за <dd>, встали ровно за предыдущими. Но нам нужно, чтобы содержимое таба растягивалось на всю ширину, поэтому прописываем этому элементу width: 100%:

    stage31

    Как и ожидалось, элемент разорвал поток. Размышляем так: если элемент прижат к правому краю контейнера, значит при изменении ширины будет изменяться левый край элемента (то есть <dd> растет справа налево). Значит, нам нужно подавить влияние левого края элемента. Сделать это можно с помощью margin-left: -100%:

    stage41

    Осталось сместить элемент вниз, сделать это можно с помощью margin-top:

    stage5

    Вот и все. По умолчанию все <dd>-элементы скрываем с помощью display: none, выбранному элементу говорим display: block. Скрипт переключения табов на jQuery выглядит совсем уж просто:

    $(function(){
    	$('dl.tabs dt').click(function(){
    		$(this)
    			.siblings().removeClass('selected').end()
    			.next('dd').andSelf().addClass('selected');
    	});
    });
    

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

    Метки: , ,
  • 55 комментариев

    1. ambientos
      12 апреля 2009

      Довольно интересно, спасибо 🙂

    2. Аркатов Дмитрий
      12 апреля 2009

      Очень круто!

    3. 12 апреля 2009

      Ага а jQuery значит не оптимально используем 🙂 зачем по обработчику на каждую закладку? Лучше воспользоваться всплыванием событий:

      $(function() {
      $(‘dl.tabs’).click(function(e) {
      var target = $(e.target);
      if(target.is(‘dt’)) {
      target.siblings().removeClass(‘selected’)
      .end().next(‘dd’).andSelf().addClass(‘selected’);
      }
      });
      });

      Так еще и возможность динамически добавлять закладки появляется и не нужны всякие там $(…).live(…)

    4. Сергей Чикуенок
      12 апреля 2009

      Ага а jQuery значит не оптимально используем зачем по обработчику на каждую закладку?

      А если внутри dt будет еще куча элементов? Все время бегать вверх по дереву? 🙂
      На самом деле хоть сколько-нибудь значимые проблемы с моим примером возникнут в том случае, если табов будет несколько сотен.

    5. 12 апреля 2009

      Просто jQuery хранит все обработчики в отдельном объекте и по событию window.onunload начинает их все удалять, поэтому чем меньше их, тем лучше, да и как-то правильнее так 🙂 Но да, если внутри dl будет много элементов, стоит подумать, какой способ лучше.

    6. 12 апреля 2009

      Octane, я сам иногда использую предложенный вами способ, однако в данном конкретном случае оверхед, на мой взгляд, великоват. dl кроме dt также содержит элементы dd, размер которых может быть большим. Кроме того, мне кажется странным оптимизировать unload в ущерб runtime.

    7. remal
      13 апреля 2009

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

      ЗЫ: Буду очень благодарен за направление туда, где посмотреть про влияния разных margin’ов, как тут например.

    8. 13 апреля 2009

      только подумал, что необходимо найти решение по табам ближе к маю, как оно тут уже есть 🙂

    9. Сергей Чикуенок
      13 апреля 2009

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

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

      ЗЫ: Буду очень благодарен за направление туда, где посмотреть про влияния разных margin’ов, как тут например.

      http://www.w3.org/TR/CSS21/box.html 🙂

      Там же можно узнать, например, что процентные значения margin-top и margin-bottom отсчитываются не от высоты, а от ширины блока.

    10. GreLI
      13 апреля 2009

      Если я правильно понимаю, то закладки можно делать и не через float:left, а через display:inline-block, например. В таком случае можно сделать, чтобы они не переносились (white-space:nowrap), тогда не будет наезжания. Я только не уверен, что float:right от этого не съедет.

    11. Сергей Чикуенок
      13 апреля 2009

      Если я правильно понимаю, то закладки можно делать и не через float:left, а через display:inline-block, например.

      В принципе, да. Только надо на кроссбраузерность проверить (в Fx3 все нормально, так даже лучше: нет привязки к высоте закладки).

    12. GreLI
      13 апреля 2009

      В принципе, да. Только надо на кроссбраузерность проверить (в Fx3 все нормально, так даже лучше: нет привязки к высоте закладки).

      Точно! В Опере совсем не так, вниз не съедет.

    13. Евгений Бойко
      13 апреля 2009

      Ты это, затрахал клевые штуки придумывать. Давай уже книгу что-ли напиши 🙂

    14. Сергей Чикуенок
      13 апреля 2009

      Я не знаю, что в ней писать 🙂 Предлагайте идеи.

    15. remal
      13 апреля 2009

      Кстати, в можно понапихать по ссылке. Тогда не будет проблем с :hover в ИЕ и можно будет делать закладки прямо на табы.

    16. 14 апреля 2009

      Я не знаю, что в ней писать 🙂 Предлагайте идеи.

      тупо всё вместе собери и издай. для теста вполне пойдёт даже электронная версия. а там уже определишься с форматом. а вперемешку с советами пиши мысли. так объём получиться хороший.

    17. Сергей Чикуенок
      14 апреля 2009

      тупо всё вместе собери и издай

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

    18. Мохов Олег
      23 апреля 2009

      Сергей, если не трудно скажите какие книжки вы рекомендуете почитать по вёрстке?

    19. 23 апреля 2009

      Если я правильно понимаю, то закладки можно делать и не через float:left, а через display:inline-block, например.

      Неа. У IE 6 и 7 (а также у IE 8 в режиме совместимости) не полная поддержка display: inline-block;. В этих браузерах inline-block будет работать только если изначально эти элементы имеют display: inline;. А dt по умолчанию имеет display: block;.

      По материалам quirksmode.org.

    20. Сергей Чикуенок
      23 апреля 2009

      какие книжки вы рекомендуете почитать по вёрстке?

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

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

      Например, если кто-то говорит «делай вот так», то не нужно стесняться спросить «а почему именно так?» или «а я считаю, что лучше делать вот так». Если вам попался нормальный спец, он всегда ответит, почему вы правы/не правы и приведет примеры, когда лучше один способ, а когда — второй. Если гик — то ответ будет в стиле «ты — лох, учи матчасть, а потом спорь со мной». В этом случае предлагаю сразу закончить разговор и не обращать внимания на этого человека.

    21. Сергей Чикуенок
      23 апреля 2009

      В этих браузерах inline-block будет работать только если изначально эти элементы имеют display: inline;.

      Открою маленький секрет: если блочному элементу (типа <dt>) в IE указать display:inline; zoom:1;, то вести он себя будет точно так же, как и display: inline-block 🙂

    22. 23 апреля 2009

      Открою маленький секрет: если блочному элементу (типа <dt>) в IE указать display:inline; zoom:1;, то вести он себя будет точно так же, как и display: inline-block

      Спасибо за совет. Я этого не знал. Наверно, теперь начну использовать display: inline-block; 🙂

    23. 4 мая 2009

      Классная вертска

    24. Янис
      31 мая 2009

      Странно, что для сброса состояния курсора ты используешь cursor:text, а не auto. Пользователям оперы будет непривычно.

    25. Сергей Чикуенок
      31 мая 2009

      Да, с auto будет правильнее. Спасибо.

    26. 16 июня 2009

      Уже неоднократно пользовался этим способом, если делать полностью на флоатах иногда всплывают странные баги в ие7, природа которых мне совсем непонятна, но с инлайн-блоками всё отлично ; )

    27. 25 июня 2009

      Спасибо за урок!

    28. 26 августа 2009

      Здорово, на много табов один маленький скриптик универсальный… 🙂 У меня сейчас с табами завал — на каждый свой WCC и скрипт… просто капец, сплошные скрипты, блин… Только я не пойму, а как сюда можно «шоу» привязать для спец.эффектов, например, для плавного переключения или перелистывания табов, можете такой примерчик написать?

      П.С. сделали бы читателям удобство — поставили бы плагин «Подписаться на комментарии» 😉

    29. 27 августа 2009

      Кто-нибудь, подскажите, пожалуйста, как правильно вставить «.fadeIn(‘slow’)» — пробовал до «.removeClass(‘selected’)» и после — работает только первое нажатие, потом табы не переключаются… ??? Уже все комбинации перепробовал, блин, не выходит сделать эффект затухания. В данном случае это вообще возможно или нет?

    30. Сергей Чикуенок
      27 августа 2009

      Попробую на выходных сделать скрипт затухания.

    31. 27 августа 2009

      Для «.tabs dt.selected {]» достаточно «z-index:1;», остальные индекс-поднятия не нужны — у меня в Хроме нормально работает 🙂

      >>Попробую на выходных сделать скрипт затухания.

      Вот скрипт не очень интересует — примеров много… Хотелось бы узнать как в данном случае какой-либо эффект прикрутить на примере. Допустим, можно воткнуть простой «.show(‘slow’)» перед «.addClass(‘selected’)» или любой другой… Также фильтр пробовал сюда засунуть (именно сюда, в приведенный пример» — всё работает на 1 клик, второй уже не катит.

      Просто хотелось бы как-то разукрасить переключалку, а не просто переключить. Эх, жаль… а идея сначала очень понравилась — один скрипт на все таб-блоки.

      Было б здорово, если б вы все-таки поставили плагин «подписаться на комментарии», а то не удобно каждый час-два заходить и проверять 😉

    32. Сергей Чикуенок
      27 августа 2009

      Можно сделать сколько угодно спецэффектов, другой вопрос, что стандартные заготовки из jQuery не подходят для этого, как и для других качественных анимационных эффектов.

    33. 1 сентября 2009

      Бился с ИЕ часа полтора, поток рвётся внутри TD, как результат первый таб наверху, остальные внизу, при переключении остальные табы один за одним смещаются наверх, лечится …блочной вёрсткой
      Побочным результатом экспериментов стал кроссбраузерный аккордеон (закомментировал float и position)
      Думаю использование DL-списка для сборки аккордеона самое логичное из всех

      PS: Там плагин spamfree ругается, если я указываю свой ЖЖ

    34. 1 сентября 2009

      PPS: Нет больше не ругается, просто страница была долго открыта, куки послетали

    35. Сергей Чикуенок
      1 сентября 2009

      Можете пример прислать?

    36. 1 сентября 2009

      Конечно Сергей, если вы имеете ввиду аккордеон, то вот

      http://sibvision.ru/demos/chiko_acco/

      Если по поводу проблемы в IE7, вкратце 1) была верстка 2 колонки таблицей, 2) пытался вставить табы в «TD.mainContent», 3) не получилось, 4) переверстал в блоки, 5) заработало

      CSS знаю, типовые проблемы знаю, но тут что не так с ослом по-видимому

      Пример уже не буду делать, восстанавливать исходное состояние моих HTML/CSS просто долго

    37. 10 сентября 2009

      Спасибо, было интересно почитать
      надеюсь на продолжение

    38. Денис
      25 сентября 2009

      Скажите пожалуйста, а возможно ли сдлеать таб с картинками кнопок вместо текста? Чтобы для каждой вкладки была своя уникальная картинка?

    39. Сергей Чикуенок
      25 сентября 2009

      Можно, конечно — ставьте картинки куда надо и все должно работать

    40. egorinsk
      8 октября 2009

      Здравсвуйте!

      Чуть переделал вашу идею — http://egor.000space.com/css-tabs.html 🙂 Убрал dt/dl, поставил дивы, заголовки не фиксированной ширины, но зато сделал переключение табов через :target (css3). Правда, одна проблема осталась: если табов много, и они не влезают в строку, они некрасиво переваливаются друг через друга 🙁

      Зато работает история и не нужен яваскрипт (правда Опера глючит иногда, при нажати кнопки назад не переключает таб).

    41. egorinsk
      8 октября 2009

      Ах да, если скрывать табы не через display:hidden, а через visibility:hidden, вся конструкция становится фиксированной высоты и перестает прыгать низ страницы (на большой странице это даже наверно притормаживать может).

    42. 2 декабря 2009

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

    43. 16 апреля 2010

      Пока нигде не видел хорошего решения чтобы вот это:

      Transfer rate: 452KB/s
      Local filename: /home/rpausch/raycd.m4v
      Remote filename: /var/www/lectures/raycd.m4v
      Duration: 01:16:27
      Color profile: SD (6-1-6)
      Dimensions: 320×240

      выглядело вот так:
      Transfer rate:452KB/s
      Local filename:/home/rpausch/raycd.m4v
      Remote filename:/var/www/lectures/raycd.m4v

      и так далее.Пока приходится использовать список.

    44. 16 апреля 2010

      Млин, тэги пропали ) в общем пример c dl отсюда:
      http://dev.w3.org/html5/spec/Overview.html#the-details-element

      Но надо чтобы выглядело как список.

    45. Карат
      10 ноября 2010

      Скажите, а как сделать, чтобы при нажатие на открытый таб он закрывался?

    46. Стас
      9 марта 2011

      Очень круто! Спасибо

    47. Александр Ерофеев
      8 июля 2011

      А почему не использовать jquery ui, там кажется есть готовые табы.

    48. 5 октября 2011

      Спасибо, Серёжа.
      И решение отличное и объясняешь ход своих рассуждений доступно.

      Я научился новому.

    49. Егор
      4 ноября 2011

      А как JS переключать табы?

    50. Adam_Ether
      27 ноября 2011

      Прекрасно!
      Но есть один вопрос: как в этом случае дать ссылку selected dd?
      Ну т.е. если дать ссылку на страницу, то будет всегда открытым первый dd, а нужно как-то хранить в ссылке selected dd
      (как в некоторых вариантах без dl — http://site/#tab1 )

    51. None
      28 января 2012

      У меня такой же вопрос, что и у Adam_Ether. Такое возможно реализовать?

    52. Сергей
      1 февраля 2012

      Hi.
      Уважаемые, подскажите нубу…
      Встраиваю в страницу, где уже есть скрипты.
      Не работает… Табы отображаются, но не переключаются.
      Может из-за того, что где-то уже есть function(), т.е. без имени

      Заранее спасибо.

    53. 3 мая 2012

      Хочу от всей души поблагодарить вас за Табы!!! Получилось просто супер! Сейчас делаю верстку сайта на локалке и какие бы табы не ставила — все не то, то прыгает страница на верх при нажатии на табы, то пока не загрузится полностью сайт — видны все элементы вкладок (строки новостей со всех вкладок), то не работают другие скрипты (карусель и тд.) из за ява скрипта табов. Ваша верстка — просто прекрасна! Код не перезагружает страницу, легко переключаются табы, да и вообще — полностью настраиваемый дизайн! Супер! Спасибо вам за ваш труд!

    54. 25 июля 2012

      Спасибо за отличное решение.

      Если кому-то ещё нужно, то вот скрипт с плавным появлением:
      dl поставил id tabs, у dd убрал класс .selected за ненадобностью

      var active_tab=$(‘#tabs dt:eq(0)’);
      active_tab.addClass(‘selected’).next(‘dd’).fadeIn(‘fast’);

      $(‘#tabs dt’).click(function(){
      if(!$(this).is(active_tab)){
      active_tab.removeClass(‘selected’).next(‘dd’).fadeOut(‘fast’);
      $(this).addClass(‘selected’).next(‘dd’).fadeIn(‘fast’);
      active_tab=$(this);
      }
      });

    55. Rayst
      18 августа 2012

      Ребяты, кто-нить пробовал воткнуть заголовки табов в карусель? Т.е. по нажатию на элемент в карусели — чтоб открывался таб? (вопрос как раз из разряда большого кол-ва табов, которые не умещаются в один ряд)