Як показати зображення при наведенні на посилання: CSS hover, focus і мобільні пристрої

Зображення при наведенні на посилання

Ефект, за якого поруч із посиланням з’являється зображення, можна реалізувати без JavaScript — через CSS-позиціювання та стани :hover і :focus. Такий прийом підходить для необов’язкового прев’ю: наприклад, мініатюри товару, ілюстрації терміна або додаткової підказки.

Але сучасний варіант має працювати не лише з мишею. Клавіатурний фокус, сенсорні екрани, межі viewport і доступність важливі не менше, ніж сам ефект появи. Нижче — робочий приклад і кілька способів зробити його безпечнішим для реального сайту.

Базова розмітка прев’ю

Найзручніше пов’язати текст посилання та зображення одним контейнером. Зображення спочатку візуально приховане, а потім показується під час взаємодії.

Наочний інтерактивний приклад. Наведіть курсор на посилання нижче. Зображення з’явиться безпосередньо під ним. Під час навігації клавіатурою той самий ефект спрацює після отримання посиланням фокусу.

Це робоча демонстрація того самого принципу, який розбирається далі. Для реального проєкту CSS краще винести в stylesheet шаблона, а не зберігати всередині статті.

<span class="preview-link">
  <a href="/example">Переглянути приклад</a>
  <img src="/images/example.webp"
       alt="Приклад результату"
       class="preview-link__image">
</span>

Якщо картинка є лише додатковим прев’ю вмісту посилання, її alt має бути коротким і не дублювати довгий текст без потреби. Якщо зображення суто декоративне, допустимий порожній alt="".

Показ зображення через hover і focus

Сам ефект можна побудувати на opacity і visibility. На відміну від постійного перемикання display: none, такий підхід дозволяє додати плавний перехід.

.preview-link {
  position: relative;
  display: inline-block;
}

.preview-link__image {
  position: absolute;
  z-index: 10;
  left: 0;
  top: calc(100% + .5rem);
  width: 240px;
  max-width: 70vw;
  height: auto;

  opacity: 0;
  visibility: hidden;
  transform: translateY(.25rem);
  transition: opacity .18s ease, transform .18s ease;
}

.preview-link:hover .preview-link__image,
.preview-link:focus-within .preview-link__image {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

:focus-within спрацьовує, коли фокус знаходиться на вкладеному посиланні. Завдяки цьому прев’ю доступне не лише миші, а й під час переходу сторінкою клавішею Tab.

Як не дати зображенню вийти за межі екрана

Абсолютно позиційоване прев’ю може обрізатися біля правого краю контейнера або екрана. Просте max-width: 70vw обмежує розмір, але не вирішує всіх варіантів позиціювання.

Для звичайної статті можна заздалегідь вибрати напрям відкриття, наприклад показувати зображення праворуч або ліворуч від посилання. Якщо блок має розумно перебудовуватися біля кожного краю вікна, це вже завдання popover/tooltip-компонента з JavaScript і розрахунком позиції.

Також перевірте, чи не має батьківський контейнер overflow: hidden: у такому разі спливне зображення може обрізатися незалежно від високого z-index.

Що робити на мобільних пристроях

Багато сенсорних пристроїв не мають зручного постійного стану hover. CSS media feature hover дозволяє визначити, чи здатний основний механізм введення нормально наводитися на елементи.

@media (hover: hover) {
  .preview-link:hover .preview-link__image {
    opacity: 1;
    visibility: visible;
  }
}

У мобільній версії краще не робити важливий контент залежним від наведення. Якщо прев’ю справді потрібне користувачеві, практичніше показати мініатюру постійно, відкривати її натисканням окремої кнопки або використати компонент, розрахований на touch-введення.

Чому одного hover недостатньо.

Ефект наведення — додатковий візуальний канал, а не універсальний спосіб взаємодії. Якщо зображення містить інформацію, без якої не можна зрозуміти посилання або виконати дію, цю інформацію потрібно продублювати доступним способом.

Під час проєктування такого блока корисно перевірити кілька сценаріїв:

  • користувач працює мишею й бачить :hover;
  • користувач переходить за посиланнями клавішею Tab;
  • сторінку відкрито на смартфоні або планшеті;
  • зображення не завантажилося;
  • користувач збільшив масштаб сторінки;
  • контейнер знаходиться біля краю екрана.

Якщо хоча б в одному сценарії зникає ключовий зміст, hover-прев’ю потрібно вважати декоративним доповненням і перебудувати основний інтерфейс.

Спливний текст замість зображення.

Для короткого пояснювального підпису можна використати схожу механіку. Однак не варто імітувати системний tooltip величезним прихованим блоком, якщо завдання вирішується звичайним текстом поруч з елементом.

<span class="hint-link">
  <a href="/term">Термін</a>
  <span class="hint-link__text">Коротке пояснення</span>
</span>

Якщо підказка інтерактивна, містить кнопки або має керуватися Escape/кліком, краще використовувати повноцінний popover-компонент із коректною логікою фокусу, а не лише CSS.

Варіант із transform замість різкої появи.

Невелика анімація може зробити появу м’якшою, але не повинна затримувати доступ до вмісту. Для ефекту достатньо змінити прозорість і зміщення на кілька пікселів.

.preview-link__image {
  opacity: 0;
  transform: translateY(4px);
}

.preview-link:hover .preview-link__image,
.preview-link:focus-within .preview-link__image {
  opacity: 1;
  transform: translateY(0);
}

Анімувати великі переміщення або довгі затримки не варто: користувач має одразу розуміти, що сталося після наведення або фокусу.

Коли краще використовувати JavaScript

CSS підходить для простого необов’язкового прев’ю. JavaScript виправданий, якщо потрібно відкривати блок натисканням, закривати клавішею Escape, обчислювати позицію відносно viewport, завантажувати зображення за потреби, керувати кількома станами або забезпечити повноцінну поведінку popover.

Не потрібно підключати бібліотеку лише заради однієї картинки на hover, але й намагатися реалізувати складний інтерактивний компонент виключно CSS теж не завжди розумно. Вибір залежить від вимог до поведінки та доступності.

Приклад: прев’ю зображення збоку від посилання.

У довгому довіднику іноді зручніше показувати картинку не під посиланням, а поруч із ним. Для цього змінюється лише позиціювання, а принцип залишається тим самим:

.preview-link__image--side {
  top: 50%;
  left: calc(100% + 1rem);
  transform: translateY(-50%);
}

.preview-link:hover .preview-link__image--side,
.preview-link:focus-within .preview-link__image--side {
  opacity: 1;
  visibility: visible;
}

Такий варіант підходить лише за достатнього вільного місця. У вузькій колонці зображення може вийти за viewport, тому для адаптивної версії варто повертати його вниз або взагалі вимикати спливне позиціювання.

Не завантажуйте величезний оригінал заради маленького прев’ю.

Якщо користувач побачить картинку шириною 240–320 пікселів, немає сенсу щоразу передавати оригінал на кілька мегабайт. Для прев’ю краще підготувати оптимізовану версію WebP/AVIF відповідного розміру, вказати реальні width і height або зберегти правильне співвідношення сторін. Це зменшує обсяг завантаження та ризик стрибків розмітки.

Для зображення, яке спочатку приховане й може взагалі не знадобитися користувачеві, можна розглянути loading="lazy". Однак фактична поведінка завантаження залежить від браузера та положення елемента, тому оптимізація розміру файла все одно важливіша за надію на lazy loading.

Що робити, якщо прев’ю перекриває сусідні елементи.

z-index має сенс лише в межах правил накладання та stacking context. Якщо батьківський елемент створює власний контекст або обрізає вміст через overflow: hidden, збільшення z-index до величезного числа може нічого не змінити. Спочатку потрібно перевірити структуру контейнерів у DevTools, а вже потім змінювати рівні накладання.

У картках і таблицях часто безпечніше розміщувати прев’ю всередині спільного контейнера без обрізання або використовувати окремий popover-шар, який додається ближче до кореня документа.

Прев’ю не повинно заважати переходу за самим посиланням.

Якщо велика картинка з’являється просто під курсором і перекриває сусідні посилання, користувач може випадково втратити hover-стан або натиснути не туди. Намагайтеся залишати невеликий проміжок і продумувати область появи. Для суто інформаційної картинки можна використати pointer-events: none, щоб вона не перехоплювала мишу:

.preview-link__image {
  pointer-events: none;
}

Але це рішення не підходить, якщо всередині спливного блока є кнопки, посилання або інші інтерактивні елементи. У такому разі потрібен повноцінний компонент із керуванням фокусом і подіями.

Підсумок

Для простого зображення при наведенні достатньо контейнера з position: relative, абсолютно позиційованої картинки та станів :hover + :focus-within. Водночас важливо пам’ятати про touch-пристрої, межі viewport і клавіатурну навігацію.

Ця стаття входить до об’єднаного розділу HTML/CSS, тому що сам ефект будується на спільній роботі HTML-розмітки та CSS-станів.

Add comment

Знайшли помилку або неточність?

Выделите фрагмент текста и нажмите Ctrl + Enter. Также можно воспользоваться кнопкой ниже.

Знайшли помилку або неточність?

Виділений фрагмент буде додано до повідомлення автоматично. За потреби додайте пояснення.

Форма захищена від автоматичних надсилань

Дані використовуються лише для перевірки та виправлення матеріалу.