Так-с, уважаемые коллеги, всех радостно приветствую! Это небольшая статейка как раз для тех людей, которые хотят по быстрому вот такой функционал:
обнаруживать какой тип взаимодействия с веб приложением у пользователя: 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 теперь меняются и стили) Класс!
Полный код как обычно на моем гитхабе
На этом желаю всем удачи! До встречи!