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

30 Mar, 2009

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

Проблема

На сайте понадобился такой элемент. Выглядят они обычно как-то так. Я буду считать что один из его блоков должен быть всегда открыт, но на деле, это не очень важно. Ну и предполагаем, что по невероятному совпадению на сайте уже есть jquery. Потому что подключать его только ради меню глупо, согласитесь. Но, если очень хочется, то, как всегда, можно. HTML — обычный dl, в котором dt будут кликаться, а dd раскрываться, вот такой:
  1. <dl>
  2. <dt class="b-acc">Заголовок</dt>
  3. <dd>
  4. Контент
  5. </dd>
  6. ...
И все видимые блоки имеют фиксированную высоту 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. }
  11. return;
  12. });
Можно посмотреть в работе. На первый взгляд все отлично. Однако, если присмотреться, во время анимаций нижняя граница аккордеона скачет. В некоторых проектах на это и можно закрыть глаза, но когда под аккордеоном куча контента, который будет прыгать на 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, но, обычно, сайты с такими навороченными меню содержат еще кучу всяких анимаций и действий. И потому, в результате, все равно удобнее использовать фреймворк для разработки.

Ссылки

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

1.Алматинский полубомж | 17 Apr, 2010
Главное чтобы индексировались поисковиками такие вот менюшки. Полезная штука когда в меню очень много категорий и вертикальное нужно прокручивать. Хотя судя по кода проблем быть не должно.
2.MadDorris | 02 Oct, 2009
Прикольно, но: пример с дегающимся нижним краем работает чисто (ну кроме дергающегося края). А вот готовый вариант немного бажит - если кликать быстро, то с каждым кликом гармошка становится все меньше и меньше пока почти совсем не сойдет на нет. Конечно этим можно пренебречь - вряд ли кто-то будет с такой скоростью кликать по меню... Но все же... А так возможность интерестная.
3.akella | 18 Aug, 2009
эмм, а в чем его кошерность? там вроде обычный банальный вариант, и отделения там разной высоты... ?
4.barbuza | 16 Aug, 2009
кошерный вариант http://moofx.mad4milk.net/
5.cuba | 11 Aug, 2009
А как же чтобы со ссылками.
6.akella | 28 Apr, 2009
Высота должна быть одинаковая у всех блоков, иначе ж будет двигаться нижняя кромка. Можно просто джаваскриптом считать максимальную из всех будущих расхлопывающихся блоков и задавать её всем остальным
7.cinic | 28 Apr, 2009
Akella, а высоту контейнерам обязательно задавать принудительно или возможно не задавать?
8.homm | 24 Apr, 2009
Пример из пункта 3 почти хорош, только если быстро нажимать он схлопывается и уже не разворачивается.
9.delaf | 17 Apr, 2009
вот так http://floomby.ru/content/dZyVUFVfFE/
10.delaf | 17 Apr, 2009
последний работающий линк, не работает. Когда закрываются все пункты, открыть их потом не получается
11.WebMast | 10 Apr, 2009
Так сделает нормальный вариант или стоит пользоваться плагином? Я ноль в JavaScript, поэтому стоп не могу сделать..
12.akella | 10 Apr, 2009
Спасибо! А еще лучше имхо создать флаг, чтобы никакая анимация не могла начаться в процессе другой.
13.Parkim | 08 Apr, 2009
При начале новой анимации нужно останавливать все предыдущие. http://docs.jquery.com/Effects/stop.
14.Razor | 06 Apr, 2009
хорошая штучка.. вот только реально при быстром кликанье в итоге все закроется навсегда... совсем недавно реализовал свой слайдер.. правда в моем случае все слайдеры работают независимо друг от друга, а сделано это на лысом javascript без всяких фреймворков и занимает всего 2.1 кб.. но получилось довольно универсально..
15.akella | 03 Apr, 2009
Скачет, но по-разному, в одном варианте сильно и все время, в другом чуть-чуть и иногда ;) я считаю это выгодной сделкой с браузером у которого пара процентов.
16.птюч | 03 Apr, 2009
Однако ты азартен, Akella. Этож надо в наше время 7 килобайт экономить. Школа!
17.Oracle | 03 Apr, 2009
Абсолютно во всех примерах в Safari (mac) скачет низ. Для последнего работающего примера это можно увидеть, нажав сразу же на пункт «Подписаться». Safari 3.2.1.
18.akella | 30 Mar, 2009
Для меня jquery, потому что когда стали появляться фреймворки, о нем мне первом рассказали. Да и такой понятный синтаксис, а это всегда приятно, взять и сразу что-то делать. Примерно как с HTML. @ScorpAL да, в живом скрипте я еще делал защиту, чтобы во время анимации события не обрабатывались, но тут решил не усложнять. Я в целом хотел рассказать именно про step, про который нигде так и не нашел документации (но где-то по слухам есть) @Mourner - дадада, когда сделал фон для разворачиваемых пунктов, мой фф/mac тоже стал глючно прорисовывать :) Но я решил оставить, всегда забавно видеть столь редкие баги в ФФ =)
19.Mourner | 30 Mar, 2009
Хорошо. :) Только у меня какие-то глюки в FF3/Win со всеми вариантами выскакивают - рисуются странные синие полоски в некоторых случаях и пропадают при перерисовке окна браузера (например при ресайзе).
20.ScorpAL | 30 Mar, 2009
Нашел ошибочку. Попробуйте быстро покликать по менюшке. Нажмите на один пунк меню, и пока происходить эффект слайдинга тут же нажмите на другой пункт меню. Попробуйте проделать так несколько раз.
21.Арнольд | 30 Mar, 2009
В примере> в FF 3.0.8 при переключении пунктов на блоках остаются артефакты…
22.cr_ | 30 Mar, 2009
всегда удивлялся, почему именно jquery?
23.DiS | 30 Mar, 2009
» cr_: всегда удивлялся, почему именно jquery? да хотя бы потому, что он активно развивается, а также поддерживается огромным коммьюнити.
24.akella | 30 Mar, 2009
Убрал оттуда фоновую картинку, чтобы артефакты никого не смущали, но надо бы отсабмитить им багрепорт по этому поводу.
25.nice | 31 Mar, 2009
Спасибо! Недавно сам искал похожее...
26.Del'ka | 31 Mar, 2009
Если jQerry не используется, а аккордеон нужен поможет вот этот замечательный js: http://www.dezinerfolio.com/2007/07/19/simple-javascript-accordions 1kb кода)
27.Genn | 30 Mar, 2009
Ну и противное название «аккордеон».
28.Вдюпель | 22 Sep, 2010
А как сделать чтобы они были свернуты первоночально?
29.Дмитрий | 07 Dec, 2011
А подскажите плиз, что означает это выражение: toShow = $(this).next(); toShow - это функция или переменная? Как растолковать?
30.Den1xxx | 22 Oct, 2012
Всегда удивлялся почему в подобных аккордеонах скрытые подпункты открываются по клику. Это нелогично, особенно если по этому клику может открываться ссылка? Как тогда? Я тоже искал подобный вариант, и потом в конце концов написал сам, уложившись в 6 строчек кода на jQuery: http://fromgomel.com/index.php?module=articles&c=articles&b=6&a=14 Суть: 1. Скрыть всех детей при загрузке страницы. 2. При наведении на родителя переключить высоту детей. Т.е. если было скрыто — открыть, и наоборот. 3. Чтобы "не дергалось" — п.2 должен срабатывать только если высота не переключается в данный момент. Что делается селектором :not(:animated) При таком подходе если у людей не включен яваскрипт, то все "дети" останутся открыты и доступны для клика, что есть хорошо. И да, самый лучший подход — это не "плагины", в которых до 70% занимают комментарии, копирайты и установки переменных, а написать самому. Тем более если задача ясна и проста.
31.slowProg | 06 Feb, 2012
Отлично! Спасибо автору! Всегда хорошо, когда человек не останавливается на плагинах, а ищет правильное и меньшее по размерам решение. За это отдельное ОГРОМНОЕ СПАСИБО. Мне вы сэкономили примерно столько же времени сколько потратили на поиск этого решения =)
32.se | 19 Dec, 2012
Что то пример в chrome глючит...