Так-с, уважаемые коллеги, всех радостно приветствую! Это небольшая статейка как раз для тех людей, которые хотят по быстрому вот такой функционал:
обнаруживать какой тип взаимодействия с веб приложением у пользователя: touchscreen, мышка, либо же и то, и другое одновременно
обнаруживать какая ориентация на данный момент у пользователя
-
обнаруживать какой тип устройства имеет пользователь: desktop, tab, phone
Звучит, конечно, не сложно, но для этого нужно собирать своего трансформера с разных форумов, я же предлагаю свое решение в готовом и компактном виде :)
Перейдем к главному: логика и код
В общем и целом, мне нужен был такой функционал, который бы при первом заходе пользователя показывает touch это или нет. А далее уже можно подхватить и разные другие события. Делается это все для реактивной адаптивности, т.к. на css сделать это просто невозможно.
Перейдем к коду. Создадим для начала все нужные перечисления (enum) для дальнейшего взаимодействия:
InteractionType.ts - enum, отвечающий за тип взаимодействия - мышка, тач, либо оба сразу
export namespace InteractionType
{
export enum State {
Unknown = 'Unknown',
Mouse = 'Mouse',
Touch = 'Touch',
Both = 'Both'
}
}
OrientationType.ts - enum, отвечающий за тип ориентации - вертикаль, горизонталь, в рамках css это портрет и лэндскейп
export namespace OrientationType
{
export enum State {
Unknown = 'Unknown',
Portrait = 'Portrait',
Landscape = 'Landscape',
}
}
DeviceType.ts - enum, отвечающий за тип устройства - планшет, телефон, либо ПК
export namespace DeviceType
{
export enum State {
Unknown = 'Unknown',
Desktop = 'Desktop',
Phone = 'Phone',
Tab = 'Tab'
}
}
Теперь напишем сам класс, который будем подключать к компонентам. Начнем с самого простого - инициализация и объявление всех полей. Тут все по стандарту, ничего такого, разве что можно обратить внимание на параметр у addEventListener - once, т.к. нам не требуется отслеживать каждый раз что там у пользователя за тип, сработало раз - все, делаем отметку, что есть, например, тачскрин и больше этот обработчик не трогаем.
ВАЖНО!! - переменные должны быть реактивные, иначе у вас ничего не будет работать, можно использовать ref из vue, можно использовать Subject'ы из rxjs, но я советую в рамках vue использовать ref, т.к. vue умный и умеет удалять все ссылки, прослушки автоматически, а в rxjs же subscrib'ы очень непослушные и пока вы вручную не напишете unsubscribe, оно так и будет висеть. Также не забудем про инкапсуляцию и сделаем все переменные приватными, поэтому сделаем для них геттеры.
private p_interactionType: Ref<InteractionType.State>;
private p_orientationType: Ref<OrientationType.State>;
private p_deviceType: Ref<DeviceType.State>;
constructor() {
this.p_interactionType = ref(InteractionType.State.Unknown);
this.p_orientationType = ref(OrientationType.State.Unknown);
this.p_deviceType = ref(DeviceType.State.Unknown);
window.addEventListener("touchstart", () => this.activeTouchState(), { once: true });
window.addEventListener("mousemove", () => this.activeDeskState(), { once: true });
}
public get InteractionType() {
return this.p_interactionType.value;
}
public get OrientationType() {
return this.p_orientationType.value;
}
public get DeviceType() {
return this.p_deviceType.value;
}
Далее, в моем случае, мне нужно было отследить не только при первом действии, но и при заходе на сайт, поэтому я добавил такую функцию для первоначального определения тачскрина:
public defineTouchscreen(): void {
if (window.PointerEvent && "maxTouchPoints" in navigator) {
// if Pointer Events are supported, just check maxTouchPoints
if (navigator.maxTouchPoints > 0) {
this.activeTouchState();
}
} else {
// no Pointer Events...
if (window.matchMedia && window.matchMedia("(any-pointer:coarse)").matches) {
// check for any-pointer:coarse which mostly means touchscreen
this.activeTouchState();
} else if (window.TouchEvent || "ontouchstart" in window) {
// last resort - check for exposed touch events API / event handler
this.activeTouchState();
}
}
}
Теперь добавим функции обработчики для eventListener'ов:
private activeTouchState(): void {
if (this.p_interactionType.value === InteractionType.State.Mouse) {
this.p_interactionType.value = InteractionType.State.Both;
} else {
this.p_interactionType.value = InteractionType.State.Touch;
}
}
private activeDeskState(): void {
if (this.p_interactionType.value === InteractionType.State.Touch) {
this.p_interactionType.value = InteractionType.State.Both;
} else {
this.p_interactionType.value = InteractionType.State.Mouse;
}
}
Думаю, объяснять много тут не надо - просто предусмотрел тут все случаи, когда пользователь с новороченным ноутбуком с тачпадом может кликнуть сначала мышкой, а потом перейти на тачпад. Также и обратно - начал с тачпада, перешел на мышь, в итоге оба взаимодействия.
Теперь функция для ориентации устройства:
private defineDeviceType(): void {
const ua = navigator.userAgent;
if (/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i.test(ua)) {
this.p_deviceType.value = DeviceType.State.Tab;
return;
}
if (
/Mobile|iP(hone|od)|Android|BlackBerry|IEMobile|Kindle|Silk-Accelerated|(hpw|web)OS|Opera M(obi|ini)/.test(
ua
)
) {
this.p_deviceType.value = DeviceType.State.Phone;
return;
}
this.p_deviceType.value = DeviceType.State.Desktop;
}
Единственное, что тут нужно знать, так это то, что navigator.userAgent возвращает строку вида:
userAgent = appCodeName/appVersion number (Platform; Security; OS-or-CPU;
Localization; rv: revision-version-number) product/productSub
Application-Name Application-Name-version
А дальше мы уже можем проводить столько проверок, сколько захотим.
Ну и осталась функция для определения ориентации устройства:
private defineOrientationType(): void {
if (window.matchMedia("(orientation: portrait)").matches) {
this.p_orientationType.value = OrientationType.State.Portrait;
} else if (window.matchMedia("(orientation: landscape)").matches) {
this.p_orientationType.value = OrientationType.State.Landscape;
}
}
На этом код готов) Вообще тут можно много всего сделать, нужно уже самому тыкаться и смотреть, что да как прикручивать. Я его использовал примерно так:
const currentInteractionState = computed(() => deviceDetect.InteractionType);
const hasTouchScreen = computed(() => {
return (
currentInteractionState.value === InteractionType.State.Both ||
currentInteractionState.value === InteractionType.State.Touch
);
});
Все это дело я прикручивал к динамическим классам, от значения computed теперь меняются и стили) Класс!
Полный код как обычно на моем гитхабе
На этом желаю всем удачи! До встречи!
Комментарии (11)
deamondz
18.07.2023 18:40три вопроса:
при чём тут vue?
зачем городить велосипеды? (ну если это только не формате обучения)
если вы предполагаете, что девайсом будут пользоваться мышкой, тачем, мышкой и тачем - зачем это может быть нужно детектировать, если придётся и так и так поддерживать все способы?
GonnaMakeItBrah Автор
18.07.2023 18:40+2Я писал это для использования во vue компонентах, поэтому vue
А есть способы получше? Я с радостью посмотрю на них)
В моем конкретном случае была ситуация, когда кнопки сайдбара доступны на пк при наведении, но на тачскрине наведения нету и приходится кликать, поэтому нужно было предусмотреть обработку тач скрина на начальном этапе и уже в зависимости от этого сделать видимой кнопку сразу без наведения
Dimava
18.07.2023 18:40+2Код выглядит как будто написан лет пять назад.
namespace
иenum
уже давно использовать не рекомендуется, вместо них пришли ES Module и Union types:export type InteractionType = 'unknown' | 'mouse' | 'touch' | 'both'
Vue Class Component уже давно deprecated, Vue 2 заканчивается в декабре 2023. Сейчас все потихоньку переезжают на Vue 3 и Composition API
У класса реактивности нету ("просто создать computed свойство" не сработает - сам инстанс не реактивный)listener
вaddEventListener
не обёрнуты, поэтому код даже не "не сработает" - а сработает, добавивinteractionType
вwindow
Вот хороший пример как надо - https://vueuse.org/core/useScreenOrientation (исходный код - https://github.com/vueuse/vueuse/blob/main/packages/core/useScreenOrientation/index.ts )
И последнее. У репозитория нету лицензии. А значит использовать данный код нельзя вообще.
GonnaMakeItBrah Автор
18.07.2023 18:40О, а это хорошее предложение) Да вот проблемка, я работал в компании где была vue 2, поэтому немного привык к его синтаксису, спасибо за замечания! Насчет лицензии ничего не знаю, я больше в ознакомительных целях пишу статьи
GonnaMakeItBrah Автор
18.07.2023 18:40Действительно, реактивности не было, теперь предусмотрел и реактивность! Компутеды снова начнут работать)
SuperCat911
18.07.2023 18:40Раньше сталкивался с проблемой определения IPad, так как ua врал как мог. Решение на заметку:
/** @returns {boolean} */ function isIPad() { let ua = window.navigator.userAgent.toLowerCase(); return ua.indexOf('ipad') > -1 || ua.indexOf('macintosh') > -1 && navigator.maxTouchPoints && navigator.maxTouchPoints > 2 && navigator.platform !== "iPhone"; }
sanchezzzhak
Надо ли говорить что определение типа устройства по ua это не точно.
Многие планшеты имеют мобильные ua.
Самый главный вопрос зачем тип устройства во frontend?
GonnaMakeItBrah Автор
По ua не точно, но он покрывает большую часть устройств, хотя в любом правиле есть исключения это да, надо будет подумать над универсальным решением. Зачем во фронтенде эта штука? - чтобы адаптив настраивать и вертеть интерфейсом так, как хочется)
carkatau01
Chrome в скором времени начнёт урезать UA и информации в нем будет все меньше
Вместо UA нужно смотреть в сторону Client Hints