Создаем модальное окно с плавным появлением и скрытием
В этой статье рассмотрим, как реализовать модальное окно с плавным появлением и исчезновением, используя 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
.overlay — это затемнённый фон, который занимает весь экран и центрирует модальное окно.
Изначально .overlay невидим и не реагирует на события, благодаря opacity: 0, visibility: hidden и pointer-events: none.
Переходы (transition) применены к opacity и visibility для плавной анимации появления и скрытия.
При добавлении класса .active меняются свойства:
opacity на 1 — элемент становится непрозрачным.
visibility на visible — элемент видим для браузера.
pointer-events на auto — элемент начинает принимать клики.
z-index повышается, чтобы окно было поверх других элементов.
Кнопка закрытия стилизована отдельным фоновым 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 Устанавливаем display: flex, чтобы элемент появился в DOM и стал видимым (но с opacity=0).
1.2 Через requestAnimationFrame добавляем класс .active, который запускает CSS-переход к opacity=1.
1.3 Перемещаем фокус на кнопку закрытия для удобства клавиатурных пользователей.
Закрытие модального окна:
2.1 Убираем класс .active, что запускает плавный переход opacity к 0.
2.2 Слушаем событие transitionend для свойства opacity.
2.3 После завершения перехода устанавливаем display: none, чтобы полностью скрыть элемент и убрать из потока.
2.4 Возвращаем фокус на кнопку открытия.
Дополнительно:
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' ; requestAnimationFrame (() => { overlay.classList .add ('active' ); closeBtn.focus (); }); } function closeModal ( ) { overlay.classList .remove ('active' ); overlay.addEventListener ('transitionend' , function handler (event ) { if (event.propertyName === 'opacity' ) { overlay.style .display = 'none' ; openBtn.focus (); overlay.removeEventListener ('transitionend' , handler); } }); } openBtn.addEventListener ('click' , openModal); closeBtn.addEventListener ('click' , closeModal); overlay.addEventListener ('click' , (event ) => { if (event.target === overlay) { closeModal (); } }); document .addEventListener ('keydown' , (event ) => { if (event.key === 'Escape' && overlay.classList .contains ('active' )) { closeModal (); } }); })();
Итог
Такой подход позволяет:
Плавно анимировать появление и скрытие модального окна.
Использовать display: none для полного исключения элемента из потока и отключения взаимодействия.
Обеспечить доступность и удобство для пользователей клавиатуры.