В чем разница между display: none и visibility: hidden в CSS

Значение none у свойства display скрывает элемент вместе с занимаемым местом. Значение hidden у visibility сохраняет его размеры и положение в раскладке. Оба способа исключают скрытый элемент из обычной навигации скринридера. Выбор также зависит от фокуса, обработки кликов и желаемой анимации.

Сергей Бензенкоавтор материала

Чем отличаются display: none и visibility: hidden

Разница между display none и visibility hidden прежде всего касается раскладки. Свойство display со значением none освобождает пространство для соседних блоков. Скрытие через visibility оставляет на странице пустое место.

Что сравниваемdisplay: nonevisibility: hiddenopacity: 0Атрибут hidden
Место в обычном потокеНе занимаетСохраняетСохраняетНе занимает
Пересчет раскладки при переключенииОбычно нуженСамо свойство не меняет геометриюСамо свойство не меняет геометриюОбычно нужен
Клики и события мыши по элементуНетНет; видимые потомки могут получатьДаНет
Фокус с клавиатурыНетНет; видимые потомки могут получатьДа, если элемент фокусируемыйНет
Обычная навигация скринридераЭлемент исключенСкрытый элемент исключенЭлемент остается доступнымЭлемент исключен
Наследование и потомкиНе наследуется, но скрывает все поддеревоНаследуется; потомок может задать visibleНе наследуется, но прозрачность действует на всю группуНе наследуется, но скрывает все поддерево
Анимация через transitionДискретное переключение со специальными настройкамиПереключение видимости; плавность дает opacityПлавное изменение прозрачностиСам атрибут не анимируется

Таблица описывает обычный hidden, который CSS не переопределил. Она также предполагает, что элемент изначально участвует в потоке. Абсолютно позиционированный блок и до скрытия не резервирует место среди соседей.

Скрытие не удаляет обработчики событий из DOM.

Запрет кликов касается попадания указателя в скрытый элемент. JavaScript по-прежнему может отправить событие программно через dispatchEvent().

Как работает значение none у display

Значение none запрещает браузеру создавать боксы элемента и его потомков. Они исчезают из раскладки и отображаемого дерева рендеринга.

Сам элемент остается в DOM, поэтому JavaScript может найти его и изменить.

Потомка нельзя показать отдельно, пока предок имеет display со значением none. Попытка задать потомку display со значением block ничего не изменит. Браузер не отображает все скрытое поддерево.

<style>
  .row { display: flex; gap: 12px; }
  .box { padding: 20px; background: lightblue; }
  .is-removed { display: none; }
</style>

<div class="row">
  <div class="box">1</div>
  <div class="box is-removed">2</div>
  <div class="box">3</div>
</div>

Сохраните пример в HTML-файл и откройте его в браузере. Блок 3 встанет сразу после блока 1 с заданным промежутком. Удалите класс is-removed, чтобы вернуть средний блок.

Переключение display обычно требует пересчета геометрии затронутых элементов.

Затем браузер обновляет изображение страницы. Масштаб работы зависит от структуры документа, размеров и CSS-ограничений.

Не называйте скрытие через display безусловно медленным способом. Для закрытой панели удаление из раскладки часто соответствует задаче.

Проблемы производительности ищут по измерениям конкретной страницы.

Описание значений и поведения свойства есть в документации MDN по display.

Как работает значение hidden у visibility

Свойство visibility со значением hidden скрывает содержимое, сохраняя геометрию элемента. Если блок занимал 100 пикселей по высоте, это пространство останется. Обычный скрытый элемент не получает фокус и клики указателя.

Свойство visibility наследуется, но потомок может переопределить его значение.

Значение visible снова делает дочерний элемент видимым и доступным для кликов. Это важное отличие от скрытия предка через display со значением none.

<style>
  .reserved {
    min-height: 100px;
    visibility: hidden;
  }
  .reserved__visible { visibility: visible; }
</style>

<div class="reserved">
  Этот текст скрыт.
  <button class="reserved__visible" type="button">
    Эта кнопка видна
  </button>
</div>
<p>Текст после зарезервированного места.</p>

Кнопка в примере останется видимой внутри скрытого родителя. Пользователь сможет добраться до нее клавишей Tab.

Не используйте скрытого предка как гарантию отключения всего интерфейса.

Изменение visibility само по себе не перестраивает геометрию соседей. Браузер все равно обновляет видимость и отрисовку затронутого содержимого.

Значение collapse имеет отдельный смысл для строк и колонок таблицы. Браузер убирает занимаемое ими пространство, сохраняя участие ячеек в расчете размеров других строк и колонок. Для обычного блока collapse ведет себя как hidden. Не подменяйте им универсальное удаление блока из потока.

Особенности таблиц и наследования описаны в справке MDN по visibility.

Нулевая opacity, атрибут hidden и visually-hidden

Иногда интерфейсу нужно скрыть только изображение элемента или только его визуальную подпись. Для таких задач display: none и visibility: hidden подходят не всегда. Выбирайте способ по тому, кто должен сохранить доступ к содержимому.

Нулевая opacity сохраняет интерактивность

Значение 0 у opacity делает элемент полностью прозрачным, но сохраняет его размеры. Невидимая кнопка продолжает получать клики и фокус с клавиатуры. Скринридер также продолжает воспринимать ее содержимое.

Свойство pointer-events со значением none отключает попадание указателя, но сохраняет клавиатурный фокус. Для временного отключения всего поддерева используйте inert. Этот атрибут также исключает поддерево из дерева доступности.

Прозрачность родителя применяется к группе вместе с потомками.

Значение 1 у дочернего opacity не сделает содержимое прозрачного родителя видимым. Подробности есть в описании opacity на MDN.

hidden задает скрытое состояние в HTML

Обычный атрибут hidden сообщает браузеру, что элемент сейчас не нужно показывать. По результату это соответствует display: none. Место пропадает, потомки тоже скрываются.

CSS может переопределить такое скрытие, например правилом .panel { display: block; }. Поэтому проверяйте итоговое значение display, если атрибут будто перестал работать. Для показа из JavaScript удаляйте атрибут или присваивайте element.hidden = false.

Не записывайте hidden="false" для показа элемента.

Такая запись не отменяет его скрытое состояние. Поведение атрибута описано в документации MDN по hidden.

visually-hidden оставляет текст скринридеру

Класс visually-hidden, также известный как sr-only, визуально прячет текст. При этом текст остается доступным для вспомогательных технологий. Это подходящий вариант для скрытой подписи поля формы.

<style>
  .visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
</style>

<label class="visually-hidden" for="search">Поиск по сайту</label>
<input id="search" type="search">

В примере поле получает подпись через связь for и id. Не добавляйте к подписи hidden или aria-hidden="true". Для визуального скрытия здесь достаточно класса.

Этот сниппет предназначен для текста, а не для скрытия интерактивных кнопок.

Если скрываете ссылку пропуска навигации, показывайте ее при фокусе. Пользователь клавиатуры должен видеть, какой элемент сейчас выбран.

Анимация появления и скрытия

У display нет промежуточного значения между none и block.

Поэтому обычный переход без специальных настроек не создает плавного исчезновения. Плавность удобно задавать через opacity, дополняя ее переключением visibility.

Переход через opacity и visibility

Создайте файл visibility-demo.html и вставьте в него пример целиком. Кнопка переключает класс is-open через JavaScript. Панель сохраняет свое место даже после закрытия.

<!doctype html>
<html lang="ru">
<meta charset="utf-8">
<title>Переключение видимости</title>
<style>
  .panel {
    padding: 16px;
    background: #eef4ff;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity 200ms ease, visibility 0s linear 200ms;
  }
  .panel.is-open {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transition-delay: 0s;
  }
  @media (prefers-reduced-motion: reduce) {
    .panel { transition: none; }
  }
</style>

<button id="toggle" type="button"
        aria-controls="panel" aria-expanded="false">
  Показать или скрыть
</button>
<div id="panel" class="panel" inert>
  Содержимое панели.
  <button type="button">Кнопка внутри</button>
</div>

<script>
  const toggle = document.querySelector('#toggle');
  const panel = document.querySelector('#panel');

  toggle.addEventListener('click', () => {
    const open = !panel.classList.contains('is-open');
    if (!open && panel.contains(document.activeElement)) {
      toggle.focus();
    }
    panel.inert = !open;
    panel.classList.toggle('is-open', open);
    toggle.setAttribute('aria-expanded', String(open));
  });
</script>
</html>

Откройте файл в браузере и нажмите кнопку переключения. Панель плавно появится за 200 миллисекунд. Повторное нажатие запустит исчезновение, не сдвигая соседние элементы.

Задержка visibility сохраняет изображение панели на время затухания.

Атрибут inert отключает ее содержимое сразу после начала закрытия. При открытии скрипт снимает это ограничение.

Нажмите Tab после открытия, чтобы перевести фокус на внутреннюю кнопку. Закройте панель и повторите проверку. Скрытая кнопка должна пропускаться при навигации с клавиатуры. Для возврата к исходному состоянию нажмите переключатель еще раз.

Дискретный переход display с allow-discrete

Другой подход сочетает значение allow-discrete у transition-behavior и правило @starting-style. Первый механизм разрешает дискретный переход display. Второй задает начальную прозрачность для появления из скрытого состояния.

В предыдущем файле замените весь блок CSS следующим кодом. HTML и JavaScript оставьте прежними. В браузере с поддержкой этих механизмов панель исчезнет плавно, затем освободит место.

.panel {
  display: none;
  opacity: 0;
  padding: 16px;
  background: #eef4ff;
  pointer-events: none;
  transition: opacity 200ms ease, display 200ms;
  transition-behavior: allow-discrete;
}

.panel.is-open {
  display: block;
  opacity: 1;
  pointer-events: auto;
}

@starting-style {
  .panel.is-open { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .panel { transition: none; }
}

При открытии display переключается в видимое состояние в начале перехода. При закрытии значение none применяется в конце. Промежуточную картинку создает изменение прозрачности от 0 до 1 или обратно.

Если браузер не поддерживает эти механизмы, панель переключится без полной анимации. Само открытие и закрытие останется рабочим. Для возврата к варианту с сохранением места восстановите предыдущий CSS.

Синтаксис разобран в документации MDN по transition-behavior и @starting-style.

Какой способ скрытия выбрать

Сначала определите, должно ли скрытое содержимое оставаться доступным пользователю. Затем решите, нужно ли сохранять место в раскладке. Эти два вопроса помогают выбрать, как скрыть элемент в CSS.

  • Адаптивная верстка. Задайте display значение none внутри медиа-запроса для ненужного на этой ширине блока. Не скрывайте единственный доступ к важному действию без замены.
  • Выпадающая панель. Для закрытого состояния подходят hidden или значение none у display. Для плавного затухания используйте прозрачность с управлением видимостью и интерактивностью.
  • Модальное окно. Одного CSS-скрытия недостаточно. Модалке также нужны перевод фокуса внутрь, блокировка фонового интерфейса и возврат фокуса после закрытия. Пример переключаемой панели не заменяет полноценную модалку.
  • Табы. Скрывайте неактивные панели через hidden или значение none у display. Выбранной вкладке с ролью tab задавайте aria-selected="true", остальным – aria-selected="false". Не оставляйте фокус внутри скрываемой панели.
  • Аккордеон. Скрывайте закрытые панели через hidden или значение none у display. Кнопкам раскрытия обновляйте aria-expanded. Если фокус находится внутри закрываемой панели, верните его на кнопку раскрытия.
  • Подпись для скринридера. Применяйте visually-hidden к тексту, который не должен отображаться. Сохраняйте связь подписи с соответствующим элементом управления.
  • Резерв под загружаемый контент. Задайте visibility значение hidden, если размеры блока уже заданы. Если содержимое позже увеличит высоту, задайте подходящий размер контейнера заранее. Одного скрытия недостаточно для предотвращения сдвига соседей.

Как проверить скрытие в DevTools

Инструменты разработчика помогают отделить отсутствие изображения от отсутствия места. Для проверки доступности смотрите отдельное дерево, а не только DOM.

  1. Откройте пример в браузере и вызовите инструменты разработчика клавишей F12. В панели Elements выберите скрываемый блок по его разметке.
  2. Откройте Computed и найдите свойства display, visibility и opacity. Итоговые значения покажут, какие правила действительно применились.
  3. Временно задайте выбранному блоку значение none у display через редактор CSS-правил. Соседние блоки займут освободившееся место. Отключите добавленное правило, чтобы вернуть прежнюю раскладку.
  4. Верните исходное значение display и задайте visibility значение hidden. Изображение блока исчезнет, но положение соседей сохранится.
  5. Откройте вкладку Accessibility для выбранного элемента. Скрытие через display или visibility исключает элемент из дерева доступности. Нулевая opacity оставляет его в этом дереве.
  6. Вернитесь на страницу и пройдите элементы клавишей Tab. Прозрачная кнопка продолжит получать фокус, а скрытая через display или visibility будет пропущена.

Перед сравнением уберите дополнительные ограничения, например inert из анимационного примера. Иначе вы проверите совместное действие нескольких механизмов.

Перезагрузка страницы отменит временные правки из DevTools.

Исключение из дерева доступности не означает, что скрытый текст никогда не участвует в доступном имени.

Связанный с полем HTML-элемент label может задавать его имя даже при значении none у display. Скрытый текст, явно связанный через aria-labelledby или aria-describedby, также может участвовать в имени или описании другого элемента. Поэтому проверяйте имя самого поля или кнопки во вкладке Accessibility.

Ошибки при скрытии элементов в CSS

Визуально одинаковый результат может скрывать разные проблемы взаимодействия. Проверьте эти четыре ситуации при отладке интерфейса.

  • Прозрачный блок перехватывает клики. При нулевой opacity блок остается поверх страницы и получает события указателя. Скрывайте его через visibility или отключайте взаимодействие через inert. Одно значение none у pointer-events не решает вопрос клавиатурного фокуса.
  • Подпись скрыли, но связь с полем не проверили. Само значение none у display для label не означает потерю имени поля. Связанный label может участвовать в его вычислении и при скрытии. Проверьте совпадение for и id, затем доступное имя поля в Accessibility. Для визуально скрытой подписи используйте visually-hidden.
  • Элемент виден несмотря на hidden. Найдите итоговое значение display в Computed. Правило со значением block, flex или grid могло переопределить скрытие. Уберите конфликтующее правило либо явно согласуйте CSS со скрытым состоянием.
  • Потомок виден внутри скрытого родителя. Найдите дочернее правило, которое задает visibility значение visible. Оно переопределяет унаследованное скрытие. Если закрыть нужно все поддерево, задайте родителю display со значением none.

Комментарии

Оставьте свой комментарий
M
mk3mk

Про отличие visibility: hidden и display: none - понятно.
А visibility: hidden чем отличается от opacity:0 ? Или это получается одно и тоже.
Тут есть отличие в том что opacity можно менять плавно, может пригодиться для анимаций. А visibility: hidden будет меняться резко .

ВИ
Виктор И.
➞ mk3mk

opcaity и visibility — дико разные вещи. При opacity элемент всё еще на экране, просто вы его не видите и например, если там будут ссылки, то можно будет даже нажать на них, например, случайно. А вот при visibility: hidden, элемент не активен и всё, что у него внутри.

КЧ
Корней Ч.

Спасибо, все четко и никаких вопросов больше не возникает.