CSS

Разница между display:none, visibility:hidden и opacity:0?

display:none vs visibility:hidden vs opacity:0

Все три способа «скрывают» элемент, но ведут себя по-разному. Сравнивать их удобно по четырём осям: занимает ли элемент место, реагирует ли на клики, виден ли скринридеру и можно ли это плавно анимировать.

display:nonevisibility:hiddenopacity: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 - когда нужна плавная анимация появления/исчезания.

Источники