Модальное окно на js и css

2025-06-19

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

В этой статье рассмотрим, как реализовать модальное окно с плавным появлением и исчезновением, используя CSS-переходы для свойств opacity и visibility, а также управление свойством display через JavaScript. Такой подход позволяет добиться плавной анимации и при этом полностью скрывать элемент из потока и взаимодействия.

Код Html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<button id="modal-open-btn" type="button">Открыть модальное окно</button>

<div id="modal-overlay" class="overlay" role="dialog" aria-modal="true" aria-labelledby="modal-title"
aria-describedby="modal-desc">
<div class="dialog">
<span class="dialog__close" aria-label="Закрыть модальное окно"></span>
<div class="content-wrapper">
<p id="modal-title" class="content-title">Example Title</p>
<p id="modal-desc" class="content-subtitle">
Lorem ipsum dolor sit amet consectetur adipisicing elit.
</p>
</div>
</div>
</div>

Код CSS

  1. .overlay — это затемнённый фон, который занимает весь экран и центрирует модальное окно.
  2. Изначально .overlay невидим и не реагирует на события, благодаря opacity: 0, visibility: hidden и pointer-events: none.
  3. Переходы (transition) применены к opacity и visibility для плавной анимации появления и скрытия.
  4. При добавлении класса .active меняются свойства:
    • opacity на 1 — элемент становится непрозрачным.
    • visibility на visible — элемент видим для браузера.
    • pointer-events на auto — элемент начинает принимать клики.
    • z-index повышается, чтобы окно было поверх других элементов.
  5. Кнопка закрытия стилизована отдельным фоновым SVG и меняет яркость при наведении/фокусе для удобства пользователя.
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/* Основной контейнер модального окна */
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 6, 0.4);
display: flex;
justify-content: center;
align-items: flex-start;
padding-top: 100px;
opacity: 0;
visibility: hidden;
pointer-events: none;
transition: opacity 0.3s ease, visibility 0.3s ease;
box-sizing: border-box;
z-index: -999999999;
}

/* Активное состояние модального окна */
.overlay.active {
opacity: 1;
visibility: visible;
pointer-events: auto;
z-index: 1000;
}

/* Контент модального окна */
.dialog {
background-color: #fefefe;
width: 400px;
padding: 20px;
margin: 0 15px;
border: 1px solid #888;
position: relative;
box-sizing: border-box;
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

/* Кнопка закрытия */
.dialog__close {
position: absolute;
top: 20px;
right: 20px;
width: 22px;
height: 22px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22' viewBox='0 0 22 22' fill='none'%3E%3Cpath d='M1.57785 20.4223C1.6417 20.4863 1.71752 20.537 1.80098 20.5716C1.88445 20.6062 1.97391 20.624 2.06426 20.624C2.1546 20.624 2.24407 20.6062 2.32753 20.5716C2.41099 20.537 2.48681 20.4863 2.55066 20.4223L11 11.973L19.4528 20.4223C19.5819 20.5513 19.7568 20.6238 19.9393 20.6238C20.1217 20.6238 20.2967 20.5513 20.4257 20.4223C20.5547 20.2933 20.6271 20.1184 20.6271 19.9359C20.6271 19.7535 20.5547 19.5785 20.4257 19.4495L11.9728 11.0001L20.4222 2.54734C20.5512 2.41833 20.6237 2.24337 20.6237 2.06093C20.6237 1.87849 20.5512 1.70353 20.4222 1.57452C20.2932 1.44552 20.1183 1.37305 19.9358 1.37305C19.7534 1.37305 19.5784 1.44552 19.4494 1.57452L11 10.0273L2.54722 1.57796C2.4157 1.46533 2.24653 1.40648 2.0735 1.41316C1.90047 1.41984 1.73634 1.49157 1.6139 1.61401C1.49146 1.73645 1.41973 1.90058 1.41305 2.07361C1.40636 2.24664 1.46522 2.41582 1.57785 2.54734L10.0272 11.0001L1.57785 19.453C1.4498 19.5818 1.37793 19.756 1.37793 19.9376C1.37793 20.1193 1.4498 20.2935 1.57785 20.4223Z' fill='%23E5E5E5'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center;
border: none;
cursor: pointer;
transition: filter 0.2s ease;
}

.dialog__close:hover,
.dialog__close:focus {
filter: brightness(0.7);
outline: none;
}

/* Контейнер для текста */
.content-wrapper {
display: flex;
flex-direction: column;
box-sizing: border-box;
width: 100%;
padding: 0 20px;
}

/* Заголовок */
.content-title {
font-weight: 600;
font-size: 1.2rem;
margin-bottom: 12px;
color: #222;
}

/* Текст под заголовком */
.content-subtitle {
font-size: 1rem;
color: #555;
line-height: 1.4;
}

Код JavaScript

  1. Открытие модального окна:

    • 1.1 Устанавливаем display: flex, чтобы элемент появился в DOM и стал видимым (но с opacity=0).
    • 1.2 Через requestAnimationFrame добавляем класс .active, который запускает CSS-переход к opacity=1.
    • 1.3 Перемещаем фокус на кнопку закрытия для удобства клавиатурных пользователей.
  2. Закрытие модального окна:

    • 2.1 Убираем класс .active, что запускает плавный переход opacity к 0.
    • 2.2 Слушаем событие transitionend для свойства opacity.
    • 2.3 После завершения перехода устанавливаем display: none, чтобы полностью скрыть элемент и убрать из потока.
    • 2.4 Возвращаем фокус на кнопку открытия.
  3. Дополнительно:

    • 3.1 Клик по затемненному фону (области .overlay) закрывает окно.
    • 3.2 Нажатие клавиши Escape закрывает окно, если оно открыто.
    • 3.3 Обработчики событий удаляются после использования, чтобы избежать утечек памяти.
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
(function () {
const overlay = document.getElementById('modal-overlay');
const openBtn = document.getElementById('modal-open-btn');
const closeBtn = overlay.querySelector('.dialog__close');

function openModal() {
overlay.style.display = 'flex'; // Показываем элемент (убираем display: none)
// Ждем следующего кадра, чтобы браузер применил display
requestAnimationFrame(() => {
overlay.classList.add('active'); // Запускаем анимацию появления
closeBtn.focus(); // Перемещаем фокус на кнопку закрытия
});
}

function closeModal() {
overlay.classList.remove('active'); // Запускаем анимацию скрытия
// Ждем окончания перехода opacity, чтобы скрыть элемент полностью
overlay.addEventListener('transitionend', function handler(event) {
if (event.propertyName === 'opacity') {
overlay.style.display = 'none'; // Скрываем элемент из DOM-потока
openBtn.focus(); // Возвращаем фокус на кнопку открытия
overlay.removeEventListener('transitionend', handler); // Убираем обработчик
}
});
}

openBtn.addEventListener('click', openModal);
closeBtn.addEventListener('click', closeModal);

// Закрываем окно при клике по затемненному фону
overlay.addEventListener('click', (event) => {
if (event.target === overlay) {
closeModal();
}
});

// Закрываем окно по нажатию клавиши Escape
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape' && overlay.classList.contains('active')) {
closeModal();
}
});
})();

Итог

Такой подход позволяет:

  • Плавно анимировать появление и скрытие модального окна.
  • Использовать display: none для полного исключения элемента из потока и отключения взаимодействия.
  • Обеспечить доступность и удобство для пользователей клавиатуры.