Форум программистов, компьютерный форум, киберфорум
mr_dramm
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  

Аккордион с transition

Запись от mr_dramm размещена 24.10.2023 в 16:15
Показов 2006 Комментарии 3
Метки html, javascript

Вертикальный на классах

В давней теме я сделал прототип влавного спадающего списка Плавное выдвижение блока в js и немного увлекся что привело к созданию этой записи в блоге

Особенности реализации:
- класс .dropdown не должен иметь padding или border, если нужны padding для контента устанавливаем их в классе content или item.

Логика работы:
- элемент .content скрывается с помощью .dropdown с установленным свойством overflow:hidden,
- ширина .content равна ширине .dropdown это помогает контенту автоматически адоптироваться к изменению ширины, т.е. не нужно писать доп. обработчики отслеживания ширины элемента
- высота элементов .content и .dropdown совпадает если в dropdown не установлен height или установлен height auto, поэтому можно всегда определить на какую высоту увеличить dropdpwn чтобы показать скрытый content. dropdown меняет высоту на 0 чтобы скрыть content, и во время анимации на высоту content, как только transition закончится height для drodown станет auto

Возможные проблемы:
- если во время transition height будет меняться ширина элемента dropdown, которая повлечет изменение высоты content в конце transition будет скачек к высоте content. Это нужно только если у вас будет специфическая реализация элемента в которой во время клика по табам меняется ширина таба, в данной реализации ширина таба меняется только если во время transition менять ширину viewport документа.


на классах
демо
Код
Кликните здесь для просмотра всего текста

HTML5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
  <body>
    <div class="box">
      <div class="item">
        <div class="title"><span class="triangle"></span>First Line</div>
        <div class="dropdown">
          <div class="content">
            Lorem ipsum dolor sit amet consectetur adipisicing elit. Fugiat
            commodi consequuntur, nostrum hic maiores similique. Molestiae
            deleniti expedita ex, doloribus sequi deserunt, officia obcaecati
            ratione numquam veniam hic aut totam. Lorem ipsum dolor sit amet
            consectetur adipisicing elit. Esse mollitia nam nulla commodi, odit
            architecto, consectetur modi deleniti dolore aliquam officia. In
            blanditiis eum magnam ab, iste eveniet quod mollitia!
          </div>
        </div>
      </div>
      <div class="item">
        <div class="title"><span class="triangle"></span>Second Line</div>
        <div class="dropdown">
          <div class="content">
            Lorem ipsum dolor sit, amet consectetur adipisicing elit. Quasi
            numquam blanditiis sapiente odio facere ratione ea dolor,
            consequatur voluptates quo quaerat non commodi laboriosam
            perferendis unde odit veritatis, velit recusandae.
          </div>
        </div>
      </div>
      <div class="item">
        <div class="title"><span class="triangle"></span>Third Line</div>
        <div class="dropdown">
          <div class="content">
            Lorem ipsum dolor sit amet consectetur adipisicing, elit. Delectus,
            maiores quaerat expedita unde neque ex saepe eum sequi esse quidem!
            Maiores aliquam nam placeat aliquid cumque voluptatem possimus
            obcaecati pariatur?Lorem ipsum dolor sit amet consectetur
            adipisicing elit. Quo nisi atque ducimus, beatae unde eius. Optio,
            voluptas, quas. Tenetur, error? Quod eligendi id qui ab nihil atque
            maxime quaerat dolore?
          </div>
        </div>
      </div>
    </div>
CSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
      * {
        padding: 0;
        margin: 0;
        box-sizing: border-box;
      }
 
      .box {
        width: 30%;
        margin: 0 auto;
      }
 
      .item {
        padding: 1rem;
        border: 2px solid black;
        user-select: none;
      }
 
      .item {
        cursor: pointer;
      }
 
      .item + .item {
        margin-top: 0.5rem;
      }
 
      .dropdown {
        overflow: hidden;
        height: 0;
        transition: height 1s;
      }
 
      .item.show .dropdown {
        height: auto;
      }
 
      .item .title .triangle {
        display: inline-block;
        transition: transform 1s;
      }
      .item.show .title .triangle {
        transform: rotate(90deg);
      }
JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
      class Tab {
        static prevClick = null;
        constructor($tab) {
          this.$tab = $tab;
          this.$dropDown = $tab.querySelector(".dropdown");
          this.$content = $tab.querySelector(".content");
          this.show = false;
          this.click = this.click.bind(this)
          this.$tab.addEventListener("click", this.onClick.bind(this));
          this.$tab.addEventListener(
            "transitionend",
            this.onTransitionEnd.bind(this)
          );
        }
        click = (show) => {
          const heightContent = this.$content.getBoundingClientRect().height + "px";
          if (show) {
            // открываем
            this.$tab.classList.add("show");
            // меняем статическую высоту блока контент, чтобы заново запустить анимацию
            this.$dropDown.style.height = heightContent;
          } else {
            // закрываем
            this.$tab.classList.remove("show");
            // запускаем меняем свойство высоты 
            this.$dropDown.style.height = heightContent;
            // и в следующем тике еще раз меняем свойство высоты удаляя установленное свойство тогда анимация запустится
            setTimeout(() => {
              this.$dropDown.removeAttribute("style");
            });
          }
        };
        onClick() {
          // кликаем на разные элементы
          if (Tab.prevClick !== this.click) {
            if(Tab.prevClick instanceof Function) Tab.prevClick(false)
            Tab.prevClick = this.click;
            this.show = true;
            this.click(this.show);
          } else {
            // кликаем на один и тот же элемент
            this.click((this.show = !this.show));
          }
        }
        onTransitionEnd() {
          if (this.$tab.classList.contains("show")) {
            // убираем статическую величину высоты
            this.$dropDown.removeAttribute("style");
          }
        }
      }
      [...document.querySelectorAll(".item")].forEach((e) => new Tab(e));


Вертикальный на замыканиях
логика работы такая же как и у вертикального на классах
демо

Код
Кликните здесь для просмотра всего текста

html
HTML5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    <div class="box">
        <div class="item">
            <div class="title"><span class="triangle"></span>First Line</div>
            <div class="dropdown">
                <div class="content">Lorem ipsum dolor sit amet consectetur adipisicing elit. Fugiat commodi consequuntur, nostrum hic maiores similique. Molestiae deleniti expedita ex, doloribus sequi deserunt, officia obcaecati ratione numquam veniam hic aut totam. Lorem ipsum dolor sit amet consectetur adipisicing elit. Esse mollitia nam nulla commodi, odit architecto, consectetur modi deleniti dolore aliquam officia. In blanditiis eum magnam ab, iste eveniet quod mollitia!</div>
            </div>
        </div>
        <div class="item">
            <div class="title"><span class="triangle"></span>Second Line</div>
            <div class="dropdown">
                <div class="content">Lorem ipsum dolor sit, amet consectetur adipisicing elit. Quasi numquam blanditiis sapiente odio facere ratione ea dolor, consequatur voluptates quo quaerat non commodi laboriosam perferendis unde odit veritatis, velit recusandae.</div>
            </div>
        </div>
         <div class="item">
            <div class="title"><span class="triangle"></span>Third Line</div>
            <div class="dropdown">
                <div class="content">Lorem ipsum dolor sit amet consectetur adipisicing, elit. Delectus, maiores quaerat expedita unde neque ex saepe eum sequi esse quidem! Maiores aliquam nam placeat aliquid cumque voluptatem possimus obcaecati pariatur?Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo nisi atque ducimus, beatae unde eius. Optio, voluptas, quas. Tenetur, error? Quod eligendi id qui ab nihil atque maxime quaerat dolore?</div>
            </div>
        </div>
    </div>
css
CSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
     * {
        padding: 0;
        margin: 0;
        box-sizing: border-box;
    }
 
    .box {
        width: 30%;
        margin: 0 auto;
    }
 
    .item {
        padding: 1rem;
        border: 2px solid black;
        user-select: none;
    }
 
    .item {
        cursor: pointer;
    }
 
    .item+.item {
        margin-top: .5rem;
    }
 
    .dropdown {
        position: relative;
        overflow: hidden;
        height: 0;
        transition: height 1s;
    }
 
    .item.show .dropdown {
        height: auto;
    }
 
    .item .title .triangle{
        display: inline-block;
        transition: transform 1s;
    }
    .item.show .title .triangle{
        transform: rotate(90deg);
    }
JS
JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
         const items = document.querySelectorAll(".item");
 
      let clickPrev = null;
 
      const handler = (target, indx) => {
        const dropDown = target.querySelector(".dropdown");
 
        const content = target.querySelector(".content");
        if (!dropDown || !content) return;
 
        let heightContent = content.getBoundingClientRect().height + "px";
 
        const click = (show) => {
          if (!dropDown || !content) return;
          heightContent = content.getBoundingClientRect().height + "px";
          if (show) {
            // открываем
            target.classList.add("show");
            // меняем статическую высоту блока контент, чтобы заново запустить анимацию
            dropDown.style.height = heightContent;
          } else {
            // закрываем
            target.classList.remove("show");
 
            dropDown.style.height = heightContent;
            setTimeout(() => {
              // нужно выполнить в следующем тике,
              // иначе изменения измения произойдут в одном тике
              // т.е. высота сразу станет 0
              dropDown.removeAttribute("style");
            });
          }
        };
        let show = false;
        target.addEventListener("click", () => {
          if (clickPrev != click) {
            if (clickPrev instanceof Function) clickPrev(false);
            clickPrev = click;
            show = true;
            click(show);
          } else {
            click((show = !show));
          }
        });
        dropDown.addEventListener("transitionend", () => {
          {
            if (target.classList.contains("show")) {
              // убираем статическую величину высоты
              dropDown.removeAttribute("style");
            }
          }
        });
      };
 
      for (let i = 0; i < items.length; i++) handler(items[i], i);


Объяснение

Кликните здесь для просмотра всего текста


Когда контент скрыт унего класс dropdown с высотой 0
CSS
1
2
3
4
5
6
    .dropdown {
        position: relative;
        overflow: hidden;
        height: 0;
        transition: height 1s;
    }
нужно немного шарить в js чтобы, знать, что такое замыкания

JavaScript
1
2
3
4
5
6
7
// для каждого элемента создаю обработчик со своим состоянием, которое будет у каждого свое
      for (let i = 0; i < items.length; i++) {
        const item = items[i];
        const h = handler(item, i); // создает замыкание, хранящее стейт элемента для которого используется обработчик
        item.addEventListener("click", h);
        item.addEventListener("transitionend", h);
    }
тут приведен код замыкания и обработчика клика далее подробное описание логики
JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
const handler = (target, indx) => {
// логика работы с состоянием 
        const dropDown = target.querySelector(".dropdown");
 
        const content = target.querySelector(".content");
        if (!dropDown || !content) return;
 
        let heightContent = content.getBoundingClientRect().height + "px";
 
        const click = (show) => {
            if (!dropDown || !content) return;
            heightContent = content.getBoundingClientRect().height + "px";
            if (show) {
                // открываем
                target.classList.add("show");
                // меняем статическую высоту блока контент, чтобы заново запустить анимацию
                dropDown.style.height = heightContent;
 
            } else {
                // закрываем
                target.classList.remove("show");
 
                dropDown.style.height = heightContent;
                setTimeout(() => {
                    // нужно выполнить в следующем тике, 
                    // иначе изменения измения произойдут в одном тике 
                    // т.е. высота сразу станет 0 и анимации не произойдет
                    dropDown.removeAttribute("style");
                }, 0);
            }
        }
        let show = false
        // обработчик события
        return (e) => {
            e.stopPropagation();
            switch (e.type) {
                case "click":
                    {
                        if(clickPrev !== click) {
                            // кликаем на разные элементы
                            if(clickPrev instanceof Function) clickPrev(false)
                            clickPrev = click
                            show = true
                            click(show)
                        }
                        else{
                            // кликаем на один и тот же элемент
                            click(show = !show)
                        }
                        
                    }
                    break;
 
                case "transitionend":
                    {
                        if (target.classList.contains("show")) {
                            // убираем статическую величину высоты
                            dropDown.removeAttribute("style");
                        }
                    }
                    break;
                default:
            }
        };
    };
работа обработчика клика, как видно тут два варианта либо мы кликаем на разные элементы, либо на один и тот же

JavaScript
1
2
3
4
5
6
7
8
9
10
11
if(clickPrev !== click) {
                            // кликаем на разные элементы
                            if(clickPrev instanceof Function) clickPrev(false)
                            clickPrev = click
                            show = true
                            click(show)
                        }
                        else{
                            // кликаем на один и тот же элемент
                            click(show = !show)
                        }
теперь немного описания как показываем и скрываем элементы:
- изначально все элементы скрыты за то показывается или скрывается элемент отвечает переменная show для каждого элемента она своя.
- итак элемент скрыт у него
- overflow: hidden; - для того чтобы контент на влиял на высоту, т.е. контент дочернего блока будет скрыт, но свои размеры не поменяет, если мы принудительно сделаем размеры родительского блока меньше, например, height 0
- height: 0; - высота скрытого элемента
- transition: height 1s; - ну и анимация
- происходит клик
начинает работать функция click
этот вызов на всякий случай, если ширина родительского блока не разу не менялась, то можно размер блока вообще закешировать.
JavaScript
1
const content = target.querySelector(".content");
сначала мы скрываем блок который был открыт.
Для открытых элементов нужно установить height auto. У всех блоков dropdown в состоянии покоя установлено heigh auto опять же для того, а вдруг поменяется ширина родительского блока dropdow и соответственно высота дочерних блоков может поменяться. Поэтом после окончания анимации для открытых блоков dropdown устанавливается height auto.


JavaScript
1
2
3
4
5
6
7
8
               case "transitionend":
                    {
                        if (target.classList.contains("show")) {
                            // убираем статическую величину высоты
                            dropDown.removeAttribute("style");
                        }
                    }
                    break;
CSS
1
2
3
.item.show .dropdown {
        height: auto;
    }
класс show с большей специфичностью чем у dropdown тут нужен перебить height 0
CSS
1
2
3
4
5
.dropdown {
        ...
        height: 0;
        ...
    }
итак, если у нас есть открытый блок у него установлен класс show. Чтобы анимация сработала нам нужно снять класс show и прописать inline style height величину которую сейчас занимает контент и в следующем тике его удалить. Нельзя удалять в этом же потому что все действия которые мы пишем последовательно в синхронном коде браузер попытается выполнить в одном тике.
и таким образом не сработает
JavaScript
1
2
element.style.height = "100px"
element.style.height = "0"
просто применится последнее значение, понимание тиков очень важно
JavaScript
1
2
3
4
5
6
                setTimeout(() => {
                    // нужно выполнить в следующем тике, 
                    // иначе изменения измения произойдут в одном тике 
                    // т.е. высота сразу станет 0 и анимации не произойдет
                    dropDown.removeAttribute("style");
                }, 0);
это сработает потому что со значением высоты остался только класс dropdown с height 0

теперь мы открываем новый элемент делаем все тоже самое только наоборот
временно устанавливаем inline style с height которое будет жить только во время анимации
как только анимация закончилась убираем inline style и добавляем на элемент show с height auto

и теперь немного еще о том как работает переключение:
Обычно это можно сделать способом перебора всех элементов с целью найти открытый например по классу show чтобы его скрыть, и потом открыть новый.
Но тут немного другая логика не то чтобы перебор элементов занимал много времени, точно нет если только у нас не 100к+ элементов на странице.
В общем

есть такая переменная

JavaScript
1
let clickPrev = null
каждый раз как только выполняется клик выполняется этот код

JavaScript
1
2
3
4
5
6
7
if(clickPrev !== click) {
                            // кликаем на разные элементы
                            if(clickPrev instanceof Function) clickPrev(false)
                            clickPrev = click
                            show = true
                            click(show)
                        }
вот так вот просто без перебора всех элементов и с меньшим строчек кода можно найти открытый элемент за время O(1)


Горизонтальный

Только на css для любого количества элементов размещаемых в html демо
Код
Кликните здесь для просмотра всего текста
css

CSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
   * {
        padding: 0;
        margin: 0;
        box-sizing: border-box;
    }
 
    .box {
        --width: 80vw;
        --gap: 20px;
        --collapsed: 30%;
        --active: 100%;
        display: flex;
        width: var(--width);
        gap: var(--gap);
        height: calc(var(--width) / 3);
        margin: 0 auto;
    }
 
    .item {
        height: 100%;
        flex-basis: var(--collapsed);
        overflow: hidden;
        transition: flex-basis 1s;
        border-radius: 2rem;
        flex-grow: 1;
        cursor: pointer;
        background-color: gray;
    }
    /* активируем первый элемент если курсор вне блока аккордеона */
    .box:not(:hover) .item:first-child, .item:hover{
        flex-basis: var(--active);
     }
 
    .img {
        height: 100%;
        display: flex;
        justify-content: center;
        transition: transform 1s;
        /*  эффект сдвига влево неактивного элемента  */
        transform: translateX(calc(var(--active) / 2 * -1));
    }
    
    img{
        display: block;
        height: 100%;
    }
    .box:not(:hover) .item:first-child .img, .item:hover .img {
        /*  центрируем когда активен  */
        transform: translateX(0);
    }
    .box + .box{
        margin-top: 1rem;
    }

html

HTML5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
   <div class="box">
        <div class="item">
            <div class="img">
                <img src="https://placehold.co/600x300" alt="">
            </div>
        </div>
    </div>
    <div class="box">
        <div class="item">
            <div class="img">
                <img src="https://placehold.co/600x300" alt="">
            </div>
        </div>
        <div class="item">
            <div class="img">
                <img src="https://placehold.co/600x300" alt="">
            </div>
        </div>
    </div>
    <div class="box">
        <div class="item">
            <div class="img">
                <img src="https://placehold.co/600x300" alt="">
            </div>
        </div>
        <div class="item">
            <div class="img">
                <img src="https://placehold.co/600x300" alt="">
            </div>
        </div>
        <div class="item">
            <div class="img">
                <img src="https://placehold.co/600x300" alt="">
            </div>
        </div>
                <div class="item">
            <div class="img">
                <img src="https://placehold.co/600x300" alt="">
            </div>
        </div>
    </div>
Метки html, javascript
Размещено в ui
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 3
Комментарии
  1. Старый комментарий
    красиво
    Запись от seregadushka размещена 24.10.2023 в 17:02 seregadushka вне форума
  2. Старый комментарий
    Аватар для mr_dramm
    Спасибо!
    Запись от mr_dramm размещена 24.10.2023 в 20:26 mr_dramm вне форума
  3. Старый комментарий
    Форуму пора подумать о своём снипете. Требуйте от людей двойную работу.
    Чисто по коду на форуме невозможно представить результат.
    Запись от seregadushka размещена 04.01.2024 в 16:56 seregadushka вне форума
 
Новые блоги и статьи
Модель здравосохранения 17. Планы на выгорание
anaschu 23.05.2026
Вот конкретная схема реализации: В классе Работник добавить: накопленнаяУсталость — растёт каждый час работы, снижается в перерывы и болезни коэффициентПрезентеизма — снижает продуктивность. . .
Изменение цветов в палитре gif файла aka фавикона
russiannick 23.05.2026
Изменение цветов в палитре gif файла, юзаемого как фавиконка в составе html-файла, помещенная в base64, средствами нативного Java Script, навеянное сном в майский день. Для работы необходим браузер,. . .
Модель здравосохранения 16. Слишком хорошие и здоровые сотрудники уходят, недовольные зарплатой
anaschu 23.05.2026
Отладка увольнений и настройка производительности Сегодня во второй половине дня разобрались с механикой увольнений и настроили коэффициент сложности заданий. Вот что было сделано. . . .
Как я стал коммунистом))) Модель сохранения здоровья сотрудников, запись блога номер 15
anaschu 23.05.2026
Внезапно хорошее здоровье сотрудников не нужно капиталистам?))
Модель здравоСохранения 15. Как мы чинили AnyLogic модель рабочего коллектива: сочленение диаграммы состояний болезней и поломок в ресурспул
anaschu 23.05.2026
Как мы чинили AnyLogic модель рабочего коллектива Сегодня разобрались с пятью багами, из-за которых модель либо падала с ошибкой, либо давала совершенно бессмысленные результаты. Каждый баг был. . .
Диалоги с ИИ
zorxor 23.05.2026
Насколько я понимаю - Вы - Искусственный Интеллект. Это так? Да, всё верно. Я — искусственный интеллект. Я представляю собой большую языковую модель, созданную для помощи в самых разных задачах. . . .
Модель здравосохранения 14. Собираем всю модель вместе.
anaschu 22.05.2026
Модель собрана. В будущих постах на видео я покажу, как она работает. В этом посте запускаем её, проверяем результаты и разбираем что можно с ней делать дальше. Перед запуском проверяем. . .
Модель здравоохранения 13. Добавление самой системы здравоохранения.
anaschu 22.05.2026
В предыдущем посте мы настроили болезни. Теперь добавим события, которые управляют здоровьем всего коллектива, а также настроим рабочий график и расчёт финансов. В Main создаём четыре события. . . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru