Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Різниця між visibility: hidden та display: none». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**`display: none`** видаляє елемент з потоку документа: місця немає, сусідні елементи переміщуються. **`visibility: hidden`** приховує візуально, але зберігає місце, тому нічого не зсувається. ```css .gone { display: none; } /* без місця, запускає reflow */ .invisible { visibility: hidden; } /* місце збережено, лише repaint */ ``` **Правило:** потрібна стабільність макету? `visibility: hidden`. Прибрати повністю? `display: none`.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**`display: none`** повністю видаляє елемент з потоку документа. **`visibility: hidden`** приховує його, але зберігає місце в макеті. ## Теорія ### TL;DR - `display: none` - як прибрати стілець з кімнати: інші меблі зрушуються на його місце - `visibility: hidden` - як накрити стілець невидимою тканиною: місце залишається порожнім - Головна різниця: чи займає елемент місце після того, як його приховано - Потрібна стабільність макету? `visibility: hidden`. Прибрати повністю? `display: none` - `visibility` можна анімувати разом з `opacity`; `display` не переходить взагалі ### Швидкий приклад ```html <!-- 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** ```css .menu { transition: opacity 0.3s; /* це працює */ display: none; /* display не анімується */ } ``` `display` - дискретна властивість. Специфікація CSS Transitions не дозволяє переходити її, тому будь-який перехід на елементі з `display: none` ігнорується. Рішення - `opacity` + `visibility`: ```css .menu { opacity: 1; visibility: visible; transition: opacity 0.3s, visibility 0.3s; } .menu.hidden { opacity: 0; visibility: hidden; } ``` **Забути про наслідування visibility** ```css .parent { visibility: hidden; } /* всі дочірні елементи теж ховаються */ .child { visibility: visible; } /* це ПРАЦЮЄ - нащадок стає видимим */ ``` На відміну від `display: none`, `visibility` можна перевизначити на нащадку. Якщо встановити `visibility: visible` на конкретному дочірньому елементі, він відображатиметься, навіть якщо батько прихований. Це зручно, коли треба показати один елемент всередині прихованої групи. **Залишати фокусовані елементи всередині visibility: hidden** Обидві властивості приховують контент від скрін-рідерів. Але `visibility: hidden` не видаляє елементи з tab-порядку. Прихована кнопка всередині `visibility: hidden` контейнера все одно доступна з клавіатури. `display: none` прибирає її з tab-порядку повністю. Для полів форм і кнопок, які треба повністю приховати, `display: none` безпечніший варіант. **Flexbox не стискується** ```css .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-рядку ```html <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 ```jsx 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` - при першому наведенні з'явиться помітне мерехтіння.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.