Обнаружение сенсорного ввода для touchscreen или hover для мыши

Ещё совсем недавно информации о размерах экрана было достаточно для предположения, например, что мобильные устройства будут использовать сенсорный ввод для touchscreen, а пользователи устройств с экраном большего размера, вероятнее всего, используют для управления курсором мышь. Разнообразие устройств с помощью которых можно просматривать веб-сайт не позволяет полагаться только на размеры области просмотра (viewport), как на фактор, определяющий выбор CSS и отдельное поведение страницы для touch-устройств.

Когда-то можно было спокойно обходиться только получением размеров экрана:

.some-component {
  /* Устройства с сенсорным вводом */
}

@media screen and (min-width: 1024px) {
  .some-component {
    /* Вероятно тут будет работать hover */
    /* Наверное, это устройство с мышью */
  }
}

Сегодня это уже не очень помогает. У хорошего планшета разрешение экрана может быть сильно выше, чем у недорогого ноутбука. А кто-то может подключить свой планшет в качестве дополнительного монитора и таким образом получить возможность использовать наведение курсора (hover) на элементы веб-страницы с помощью мыши.

Медиа-запрос из примера выше не несёт никакой полезной информации о способах ввода и управления курсором. Поэтому для определения, как пользователь просматривает сайт и какой тип устройства ввода использует, следует анализировать что-нибудь кроме размеров экрана. Для этого можно воспользоваться некоторыми новыми медиа-запросами.

Level 5 Media Queries

Спецификация CSS Level 5 Media Queries, помимо уже знакомых для определения размеров области просмотра, предоставляет набор новых медиа-запросов.

Один из них — hover feature поможет определить, есть ли у пользователя возможности наведения основного указателя на элементы страницы с помощью мыши.

Возможные для применения этого медиа-запроса значения: hover (для устройства с мышью) или none (например, для планшета или смартфона с сенсорным вводом).

Медиа-запрос hover можно использовать следующим образом:

.some-component {
  /* Устройства с сенсорным вводом */
}

@media (hover: hover) {
  .some-component {
    /* Тут будет работать hover */
    /* Это устройство с мышью */
  }
}

Функции hover и pointer на самом деле являются частью спецификации Level 4 Media Queries, но лишь недавно стали поддерживаться большинством браузеров.

See this code hover media query on x.xhtml.ru.

Это всё хорошо работает в большинстве браузеров, но в некоторых версиях Android есть функция, при которой долгое нажатие имитирует наведение (hover) и тогда этот медиа-запрос вернёт true. Чтобы предоставить этим пользователям те же стили, что и для других сенсорных устройств с touchscreen, следует обратиться к другой медиа-функции.

Pointer

Функция pointer проверяет, есть ли на устройстве указатель и точность указательного устройства. Возможные значения: coarse (грубое для, например, пальца на сенсорном экране), fine (точное, например, мышь) или none (отсутствие, устройство без указателя).

Добавление проверки pointer в медиа-запрос успешно обнаруживает сенсорный ввод и на Android-устройствах:

.some-component {
  /* Устройства с сенсорным вводом, в т.ч. Android */
}

@media (hover: hover) and (pointer: fine) {
  .some-component {
    /* Тут будет работать hover и pointer===fine */
    /* Это устройство с мышью */
  }
}

See this code Hover and pointer media query on x.xhtml.ru.

any-hover и any-pointer

Предыдущие методы использовали для определения основные устройства ввода. Когда недостаточно определять сенсорный ввод только по основному устройству ввода, можно использовать в медиа-запросах функции any-hover и any-pointer, которые проверяют любой тип ввода.

Таким образом, если используется устройство, которое реагирует как на мышь, так и на сенсорный ввод, any-hover: hover будет возвращать true. Cпецификация включает несколько примеров того, как это (а также более сложные комбинации) можно использовать.

Пример для javascript

Рассмотрим пример, когда при наведении на одинаковые картинки-ссылки нужно показывать всплывающие подсказки к ним. В обычных условиях у пользователей сенсорных устройств не будет шансов увидеть эту подсказку. Кликнув по изображению, пользователь сразу перейдет по URL-адресу ссылки и это может вызвать неприятные ощущения, поскольку ему заранее неизвестно, на какую страницу ведёт ссылка. В таком случае для устройств с touchscreen можно прервать щелчок и показать кнопку, которую пользователь сможет нажать, чтобы перейти к соответствующему URL-адресу.

Этот же медиа-запрос для обнаружения сенсорного ввода можно использовать и в JS с помощью matchMedia:

const list = document.querySelector('[data-list]')
const isHoverableDevice = window.matchMedia(
  '(hover: hover) and (pointer: fine)'
)
const blockLink = document.querySelector('[data-button-link]')

/* сперва скроем blockLink */
blockLink.hidden = true

list.addEventListener('click', (e) => {
  /* ничего не делаем, если target не ссылка, устройство не умеет hover */
  if (!e.target.dataset.link || isHoverableDevice.matches) return

  /* Если это сенсорный экран, перехватываем клик
  показываем ссылку
  */
  e.preventDefault()
  blockLink.hidden = false
  blockLink.innerText = `Visit ${e.target.dataset.link}’s page`
  blockLink.setAttribute('href', e.target.href)
})

Вот пример:

See this code Touch/hover tooltip on x.xhtml.ru.

Доступность

В зависимости от пользовательского интерфейса, возможно, понадобится задействовать вспомогательные технологии, используя ARIA-атрибуты для объявления кнопки, когда происходит изменение или перемещение фокуса на кнопку. Этот пример из MDN демонстрирует, как использовать живые ARIA-регионы для объявления динамических изменений элемента.

Поддержка в браузерах

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