display:none vs visibility:hidden vs opacity:0
Все три способа «скрывают» элемент, но ведут себя по-разному. Сравнивать их удобно по четырём осям: занимает ли элемент место, реагирует ли на клики, виден ли скринридеру и можно ли это плавно анимировать.
| display:none | visibility:hidden | opacity:0 | |
|---|---|---|---|
| Место в layout | не занимает | занимает | занимает |
| Клики / события | нет | нет | да |
| Screen reader | скрыт | скрыт | виден |
| Плавная анимация | нет | нет (переключается резко) | да |
display:none
Элемент полностью выпадает из потока: места не занимает, не кликается, недоступен скринридеру, его потомки тоже скрыты. Переключение none ↔ block вызывает reflow (пересчёт раскладки). Анимировать переход нельзя - элемент просто исчезает мгновенно.
visibility:hidden
Элемент невидим, но место в layout сохраняется, на клики не реагирует и скринридер его не читает. Особенность: внутри скрытого контейнера можно вернуть видимость отдельному потомку через visibility: visible. Применяется редко - в основном когда важно сохранить занимаемое пространство.
opacity:0
Элемент полностью прозрачен, но остаётся полноценным: занимает место, реагирует на клики и доступен скринридеру. Поэтому для настоящего скрытия к нему добавляют pointer-events: none (убрать клики) и при необходимости aria-hidden="true". Зато opacity отлично анимируется через transition.
Анимация: почему display:none «не работает» с transition
transition анимирует только свойства с промежуточными значениями. У display их нет - это дискретное свойство, оно переключается мгновенно, поэтому fade-out с display: none просто обрывается без плавности.
Классический приём для плавного появления/исчезания - анимировать opacity, а visibility переключать с задержкой, чтобы элемент исчезал из взаимодействия после окончания анимации:
.tooltip {
opacity: 0;
visibility: hidden;
/* opacity плавно, а visibility ждёт конца анимации (0.2s) перед скрытием */
transition: opacity 0.2s, visibility 0s 0.2s;
}
.tooltip.active {
opacity: 1;
visibility: visible;
transition: opacity 0.2s, visibility 0s; /* появляется сразу видимым */
}
Здесь visibility нужен, чтобы после затухания элемент перестал ловить клики и читаться скринридером, не вызывая reflow как display.
Современные браузеры умеют анимировать и сам
displayчерезtransition-behavior: allow-discreteвместе с@starting-style, но поддержку стоит проверять.
Что выбрать
- display: none - когда элемент нужно полностью убрать (и из потока, и для a11y).
- visibility: hidden - скрыть, но сохранить занимаемое место (редкий случай).
- opacity: 0 + pointer-events: none - когда нужна плавная анимация появления/исчезания.