Простой аккордеон

30 March, 2009

XHTML/CSS, Полезности

Это те самые, странно разворачивающиеся и очень редко нужные менюшки. ;)
Так как jquery стал почти стандартом в индустрии, дальше я буду говорить только о нем.

Проблема

На сайте понадобился такой элемент. Выглядят они обычно как-то так. Я буду считать что один из его блоков должен быть всегда открыт, но на деле, это не очень важно.
Ну и предполагаем, что по невероятному совпадению на сайте уже есть jquery. Потому что подключать его только ради меню глупо, согласитесь. Но, если очень хочется, то, как всегда, можно.
HTML — обычный dl, в котором dt будут кликаться, а dd раскрываться, вот такой:

  1. <dl>
  2. <dt class="b-acc">Заголовок</dt>
  3. <dd>
  4. Контент
  5. </dd>

И все видимые блоки имеют фиксированную высоту 150px.

#1: Своими силами

Сделать такой эффект своими силами вроде бы совсем несложно. Всего-то нужно схлопывать-расхлопывать два блока одновременно.

  1. $(“.b-acc”).click(function(){
  2. if($(this).hasClass(‘b-acc-active’)) {
  3. // сделать что-то если блок уже открыт
  4. }
  5. else{
  6. $(“dd:visible”).slideUp();// закрыть «видимый» блок
  7. $(this).next().slideDown();// открыть новый блок
  8. // и переключить класс b-acc-active
  9. }
  10. return;
  11. });

Можно посмотреть в работе. На первый взгляд все отлично.
Однако, если присмотреться, во время анимаций нижняя граница аккордеона скачет. В некоторых проектах на это и можно закрыть глаза, но когда под аккордеоном куча контента, который будет прыгать на 1-2 пикселя, это очень неприятно.
Пришлось искать решение.

#2: Плагин

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

Я сделал простой пример работы плагина. Нижняя кромка больше не прыгает.
Однако добавилось 8 Кбайтов плагина.

#3: Улучшенный вариант

Но так хотелось все оставить простым, так не хотелось подключать кучу лишнего кода, который, я знал, никому на сайте уже не понадобится. И я решил раскопать проблему, почему же нижняя граница дергается, и как это все исправить.
В конце концов оказалось, что функции slideUp и slideDown меняют высоту блоков не по целым значениям. То есть, в процессе анимации высоте блока присваиваются значения вроде 2.157305232px. При этом, округление в браузерах работает очень по-разному, что и приводило к «вибрации» нижней кромки аккордеона.

Наконец забравшись в недра плагина, я нашел решение, которое также познакомило меня с малоизвестными возможностями jquery.
Идея примерно такая: мы начинаем «схлопывать» видимый блок, при этом, на каждом шаге анимации, рассчитывать высоту расхлопываемого блока так, чтобы сумма их оставалась константой. То есть, если на каком-то шаге высота закрываемого 50 пикселей, то высота открываемого будет 100 (в сумме 150px).

Для этого в jquery есть специальный механизм. В функции animate, есть возможность исполнять что-то на каждом шаге анимации. Называется этот коллбэк — step.
Финальное работающее решение выглядит примерно так:

  1. toShow = $(this).next();
  2. toHide = $(“.accordion dd:visible”);
  3.  
  4. staticheight = toHide.height(); //определить высоту блоков, 150px
  5. toShow.css({ height: 0,display: ‘block’ }); // открываемый делаем “видимым” с высотой 0, теперь он готов к анимации
  6. toHide.animate({height:”hide”},{ // начинаем скрывать видимый
  7. step: function(now) { // на каждом шаге выполняем, now – значение height в каждый момент
  8. var current = staticheight – now; // собственно рассчет
  9. if ($.browser.msie || $.browser.opera || $.browser.safari) { // все округляют по-своему
  10. current = Math.ceil(current);
  11. }
  12. toShow.height(current); // присваиваем высоту
  13. },
  14. duration: 500
  15. });

Работающий пример. Вуаля, нижняя граница опять зафиксирована, и мы сэкономили 7 Кбайт несжатого кода.

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

В конце

Все это далеко не rocket science, но мои долгие попытки найти легкое решение этой проблемы приводили только к плагину. Который я, ну никак не хотел подключать. Потому надеюсь кому-то это сэкономит усилия и код. И буду рад услышать ваше мнение об этом.

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

Ссылки

XHTML/CSS, Полезности

32 комментариев к “Простой аккордеон”

cr_ | 1. 30 March, 2009

всегда удивлялся, почему именно jquery?

DiS | 2. 30 March, 2009

» cr_: всегда удивлялся, почему именно jquery?

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

Mourner | 3. 30 March, 2009

Хорошо. :) Только у меня какие-то глюки в FF3/Win со всеми вариантами выскакивают – рисуются странные синие полоски в некоторых случаях и пропадают при перерисовке окна браузера (например при ресайзе).

ScorpAL | 4. 30 March, 2009

Нашел ошибочку.

Попробуйте быстро покликать по менюшке.

Нажмите на один пунк меню, и пока происходить эффект слайдинга тут же нажмите на другой пункт меню. Попробуйте проделать так несколько раз.

akella | 5. 30 March, 2009

Для меня jquery, потому что когда стали появляться фреймворки, о нем мне первом рассказали. Да и такой понятный синтаксис, а это всегда приятно, взять и сразу что-то делать. Примерно как с HTML.

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

@Mourner – дадада, когда сделал фон для разворачиваемых пунктов, мой фф/mac тоже стал глючно прорисовывать :) Но я решил оставить, всегда забавно видеть столь редкие баги в ФФ =)

Арнольд | 6. 30 March, 2009

В примере> в FF 3.0.8 при переключении пунктов на блоках остаются артефакты…

akella | 7. 30 March, 2009

Убрал оттуда фоновую картинку, чтобы артефакты никого не смущали, но надо бы отсабмитить им багрепорт по этому поводу.

Genn | 8. 30 March, 2009

Ну и противное название «аккордеон».

nice | 9. 31 March, 2009

Спасибо! Недавно сам искал похожее…

Del'ka | 10. 31 March, 2009

Если jQerry не используется, а аккордеон нужен поможет вот этот замечательный js: http://www.dezinerfolio.com/2007/07/19/simple-javascript-accordions
1kb кода)

птюч | 11. 3 April, 2009

Однако ты азартен, Akella. Этож надо в наше время 7 килобайт экономить. Школа!

Oracle | 12. 3 April, 2009

Абсолютно во всех примерах в Safari (mac) скачет низ. Для последнего работающего примера это можно увидеть, нажав сразу же на пункт «Подписаться». Safari 3.2.1.

akella | 13. 3 April, 2009

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

Razor | 14. 6 April, 2009

хорошая штучка.. вот только реально при быстром кликанье в итоге все закроется навсегда…
совсем недавно реализовал свой слайдер.. правда в моем случае все слайдеры работают независимо друг от друга, а сделано это на лысом javascript без всяких фреймворков и занимает всего 2.1 кб.. но получилось довольно универсально..

Parkim | 15. 8 April, 2009

При начале новой анимации нужно останавливать все предыдущие.
http://docs.jquery.com/Effects/stop.

akella | 16. 10 April, 2009

Спасибо!
А еще лучше имхо создать флаг, чтобы никакая анимация не могла начаться в процессе другой.

WebMast | 17. 10 April, 2009

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

delaf | 18. 17 April, 2009

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

delaf | 19. 17 April, 2009

вот так
http://floomby.ru/content/dZyVUFVfFE/

homm | 20. 24 April, 2009

Пример из пункта 3 почти хорош, только если быстро нажимать он схлопывается и уже не разворачивается.

cinic | 21. 28 April, 2009

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

akella | 22. 28 April, 2009

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

cuba | 23. 11 August, 2009

А как же чтобы со ссылками.

barbuza | 24. 16 August, 2009

кошерный вариант
http://moofx.mad4milk.net/

akella | 25. 18 August, 2009

эмм, а в чем его кошерность? там вроде обычный банальный вариант, и отделения там разной высоты…
?

MadDorris | 26. 2 October, 2009

Прикольно, но:

пример с дегающимся нижним краем работает чисто (ну кроме дергающегося края).
А вот готовый вариант немного бажит – если кликать быстро, то с каждым кликом гармошка становится все меньше и меньше пока почти совсем не сойдет на нет.
Конечно этим можно пренебречь – вряд ли кто-то будет с такой скоростью кликать по меню… Но все же…

А так возможность интерестная.

Алматинский полубомж | 27. 17 April, 2010

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

Вдюпель | 28. 22 September, 2010

А как сделать чтобы они были свернуты первоночально?

Дмитрий | 29. 7 December, 2011

А подскажите плиз, что означает это выражение: toShow = $(this).next();
toShow – это функция или переменная? Как растолковать?

slowProg | 30. 6 February, 2012

Отлично! Спасибо автору!

Всегда хорошо, когда человек не останавливается на плагинах, а ищет правильное и меньшее по размерам решение. За это отдельное ОГРОМНОЕ СПАСИБО. Мне вы сэкономили примерно столько же времени сколько потратили на поиск этого решения =)

Den1xxx | 31. 22 October, 2012

Всегда удивлялся почему в подобных аккордеонах скрытые подпункты открываются по клику.
Это нелогично, особенно если по этому клику может открываться ссылка? Как тогда?
Я тоже искал подобный вариант, и потом в конце концов написал сам, уложившись в 6 строчек кода на jQuery: http://fromgomel.com/index.php?module=articles&c=articles&b=6&a=14
Суть:
1. Скрыть всех детей при загрузке страницы.
2. При наведении на родителя переключить высоту детей. Т.е. если было скрыто — открыть, и наоборот.
3. Чтобы “не дергалось” — п.2 должен срабатывать только если высота не переключается в данный момент. Что делается селектором :not(:animated)
При таком подходе если у людей не включен яваскрипт, то все “дети” останутся открыты и доступны для клика, что есть хорошо.
И да, самый лучший подход — это не “плагины”, в которых до 70% занимают комментарии, копирайты и установки переменных, а написать самому. Тем более если задача ясна и проста.

se | 32. 19 December, 2012

Что то пример в chrome глючит…

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