В чем разница между display: none и visibility: hidden в CSS
Значение none у свойства display скрывает элемент вместе с занимаемым местом. Значение hidden у visibility сохраняет его размеры и положение в раскладке. Оба способа исключают скрытый элемент из обычной навигации скринридера. Выбор также зависит от фокуса, обработки кликов и желаемой анимации.
Чем отличаются display: none и visibility: hidden
Разница между display none и visibility hidden прежде всего касается раскладки. Свойство display со значением none освобождает пространство для соседних блоков. Скрытие через visibility оставляет на странице пустое место.
| Что сравниваем | display: none | visibility: hidden | opacity: 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.
- Откройте пример в браузере и вызовите инструменты разработчика клавишей F12. В панели Elements выберите скрываемый блок по его разметке.
- Откройте Computed и найдите свойства
display,visibilityиopacity. Итоговые значения покажут, какие правила действительно применились. - Временно задайте выбранному блоку значение
noneуdisplayчерез редактор CSS-правил. Соседние блоки займут освободившееся место. Отключите добавленное правило, чтобы вернуть прежнюю раскладку. - Верните исходное значение
displayи задайтеvisibilityзначениеhidden. Изображение блока исчезнет, но положение соседей сохранится. - Откройте вкладку Accessibility для выбранного элемента. Скрытие через
displayилиvisibilityисключает элемент из дерева доступности. Нулеваяopacityоставляет его в этом дереве. - Вернитесь на страницу и пройдите элементы клавишей 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.
Комментарии
Про отличие visibility: hidden и display: none - понятно.
А visibility: hidden чем отличается от opacity:0 ? Или это получается одно и тоже.
Тут есть отличие в том что opacity можно менять плавно, может пригодиться для анимаций. А visibility: hidden будет меняться резко .
opcaity и visibility — дико разные вещи. При opacity элемент всё еще на экране, просто вы его не видите и например, если там будут ссылки, то можно будет даже нажать на них, например, случайно. А вот при visibility: hidden, элемент не активен и всё, что у него внутри.
Спасибо, все четко и никаких вопросов больше не возникает.