Різниця між visibility: hidden та display: none
display: none повністю видаляє елемент з потоку документа. visibility: hidden приховує його, але зберігає місце в макеті.
Теорія
TL;DR
display: none- як прибрати стілець з кімнати: інші меблі зрушуються на його місцеvisibility: hidden- як накрити стілець невидимою тканиною: місце залишається порожнім- Головна різниця: чи займає елемент місце після того, як його приховано
- Потрібна стабільність макету?
visibility: hidden. Прибрати повністю?display: none visibilityможна анімувати разом зopacity;displayне переходить взагалі
Швидкий приклад
<!-- display: none - Box 3 зсувається вгору -->
<div class="container">
<div class="box">Box 1</div>
<div class="box" style="display: none;">Box 2</div>
<div class="box">Box 3</div>
</div>
<!-- visibility: hidden - Box 3 залишається на місці -->
<div class="container">
<div class="box">Box 1</div>
<div class="box" style="visibility: hidden;">Box 2</div>
<div class="box">Box 3</div>
</div>У першому контейнері Box 3 зсувається туди, де був Box 2. У другому - стоїть точно на місці.
Головна різниця
display: none каже браузеру пропустити елемент під час побудови макету. Жодної блочної моделі не створюється: ні ширини, ні висоти, ні позиції. Сусідні елементи перераховують позиції так, ніби цього елемента не існує. visibility: hidden все одно проходить повний цикл побудови макету, отримує розраховані розміри, займає місце в потоці, але нічого не малює. Перемикання display запускає повний reflow. Перемикання visibility - лише repaint, що помітно дешевше.
Коли що використовувати
- Без зсуву макету (підказки, hover-попапи, вирівняні дропдауни):
visibility: hidden - Повне видалення з потоку (модальні вікна, умовні секції, динамічні списки):
display: none - Плавна анімація появи: комбінуй
visibility: hiddenзopacity: 0 - Видалення для скрін-рідерів і tab-порядку:
display: noneразом зaria-hidden="true"
Таблиця порівняння
| Аспект | display: none | visibility: hidden |
|---|---|---|
| Місце в макеті | Ні (інші переміщуються) | Так (місце зберігається) |
| Блочна модель | Не створюється | Створюється |
| Анімується | Ні | Так (з opacity) |
| Скрін-рідер | Пропускається | Пропускається |
| Наслідується дітьми | Ні | Так (можна перевизначити) |
| Запускає reflow | Так | Ні (лише repaint) |
| Коли використовувати | Модалки, collapse-меню, умовний рендер | Підказки, hover-попапи, вирівнювання дропдаунів |
Як браузер це обробляє
Движок Blink у Chrome пропускає елементи з display: none на фазі побудови макету. Вони не входять до контексту форматування, тому сусідні елементи розраховують позиції без них. Елементи з visibility: hidden проходять через макет нормально і отримують розраховані розміри. Фаза малювання просто пропускає їх відображення. Саме звідси різниця у reflow: одна властивість впливає на макет, інша - лише на малювання.
Типові помилки
Спроба анімувати display
.menu {
transition: opacity 0.3s; /* це працює */
display: none; /* display не анімується */
}display - дискретна властивість. Специфікація CSS Transitions не дозволяє переходити її, тому будь-який перехід на елементі з display: none ігнорується. Рішення - opacity + visibility:
.menu {
opacity: 1;
visibility: visible;
transition: opacity 0.3s, visibility 0.3s;
}
.menu.hidden {
opacity: 0;
visibility: hidden;
}Забути про наслідування visibility
.parent { visibility: hidden; } /* всі дочірні елементи теж ховаються */
.child { visibility: visible; } /* це ПРАЦЮЄ - нащадок стає видимим */На відміну від display: none, visibility можна перевизначити на нащадку. Якщо встановити visibility: visible на конкретному дочірньому елементі, він відображатиметься, навіть якщо батько прихований. Це зручно, коли треба показати один елемент всередині прихованої групи.
Залишати фокусовані елементи всередині visibility: hidden
Обидві властивості приховують контент від скрін-рідерів. Але visibility: hidden не видаляє елементи з tab-порядку. Прихована кнопка всередині visibility: hidden контейнера все одно доступна з клавіатури. display: none прибирає її з tab-порядку повністю. Для полів форм і кнопок, які треба повністю приховати, display: none безпечніший варіант.
Flexbox не стискується
.flex-container { display: flex; }
.flex-child { flex: 1; }
.flex-child.hidden { visibility: hidden; } /* займає свою частку flex-простору */З visibility: hidden прихований елемент зберігає flex-простір, а сусідні не розширюються. З display: none сусідні розтягуються, щоб заповнити звільнений простір. Це плутає розробників при роботі з рівними колонками частіше, ніж здається.
Де зустрічається
- React:
display: noneчерез умовний рендер ({isOpen && <Modal />}) для повного прибирання - Tailwind: клас
hidden=display: none; класinvisible=visibility: hidden - Bootstrap: accordion collapse використовує
display: noneдля вмісту панелей - Vue:
v-showвстановлюєdisplay: none;v-ifповністю прибирає елемент з DOM - Бібліотеки підказок:
visibility: hidden+opacity: 0щоб уникнути зсуву макету при першому показі
Питання на співбесіді
Q: Що запускає reflow: перемикання display чи visibility?
A: Перемикання display запускає повний reflow. Перемикання visibility - лише repaint. Тому visibility дешевше для частих анімацій.
Q: Можна анімувати перехід між display: none і display: block?
A: Ні. display - дискретна властивість, її не можна переходити. Використовуй opacity і visibility разом.
Q: Чи може дочірній елемент перевизначити visibility: hidden батька?
A: Так. visibility: visible на нащадку зробить його видимим, навіть якщо батько прихований. З display: none так не вийде.
Q: У flex-контейнері: що станеться із сусідами, якщо один елемент отримає visibility: hidden?
A: Вони залишаться на місці. Прихований елемент зберігає flex-простір. З display: none сусіди б розтяглися.
Q: Що краще для підказки, яка часто перемикається?
A: visibility: hidden з opacity. Лише repaint при кожному перемиканні, без перерахунку макету. Саме цю комбінацію найчастіше зустрічаєш у виробничому коді для тултіпів - перехід залишається плавним навіть при швидких перемиканнях.
Приклади
Різниця у flex-рядку
<style>
.row { display: flex; gap: 10px; margin-bottom: 20px; }
.box {
width: 100px; height: 100px;
background: lightblue;
display: flex; align-items: center; justify-content: center;
}
</style>
<!-- display: none - проміжку немає -->
<div class="row">
<div class="box">1</div>
<div class="box" style="display: none;">2</div>
<div class="box">3</div>
</div>
<!-- Результат: [1][3] -->
<!-- visibility: hidden - проміжок залишається -->
<div class="row">
<div class="box">1</div>
<div class="box" style="visibility: hidden;">2</div>
<div class="box">3</div>
</div>
<!-- Результат: [1][ ][3] -->Box 3 стоїть на різних позиціях у кожному рядку. Саме ця різниця визначає, яку властивість обрати.
Плавна підказка з visibility + opacity
function Tooltip({ children, text }) {
const [visible, setVisible] = useState(false);
return (
<div style={{ position: 'relative', display: 'inline-block' }}>
<button
onMouseEnter={() => setVisible(true)}
onMouseLeave={() => setVisible(false)}
>
{children}
</button>
<div style={{
position: 'absolute',
top: '110%',
left: 0,
padding: '4px 8px',
background: '#222',
color: '#fff',
borderRadius: 4,
whiteSpace: 'nowrap',
visibility: visible ? 'visible' : 'hidden',
opacity: visible ? 1 : 0,
transition: 'opacity 0.2s, visibility 0.2s',
}}>
{text}
</div>
</div>
);
}
// visibility: hidden зберігає розміри - позиція підказки стабільна при кожному показі
// display: none перераховував би позицію заново, що дає мерехтіння при першому наведенніПідказка стоїть точно на місці при кожному показі, бо її розміри завжди розраховані, просто не намальовані. Якщо змінити на display: none - при першому наведенні з'явиться помітне мерехтіння.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.