gesturechange и зум тачпадом на маках
14.12.2025 • js, dom
Синтетические события
Браузеры могут генерировать события, которых на самом деле нет. До недавнего времени я думал, что такое есть только у <button> с клавиатурными нажатиями. Возьмём, к примеру, кнопку:
<button>
Привет, я кнопка
</button>button.addEventListener('click', () => {
...
});По коду всё понятно: кликнули по кнопке, обработчик сработал. А что будет, если активировать кнопку с клавиатуры (нажать Enter). Тоже сработает обработчик click!
Именно поэтому правильнее делать кнопки тегом <button>, а не каким-либо ещё. Предположим, если мы сделаем такую разметку:
<div role="button" tabindex="0">
Привет, я ненастоящая кнопка
</div>То элемент можно будет зафокусить благодаря tabindex. И даже скринридеры будут читать её как “кнопку”. Однако при нажатии на кнопки клавиатуры ничего происходить не будет. Для решения потребуется подобный код:
button.addEventListener('keydown', event => {
if (event.code === 'Enter' || event.code === 'Space') {
event.preventDefault();
...
}
});Но, согласитесь, гораздо проще обойтись одним только обработчиком click. Синтетические события от браузера призваны улучшить доступность сайтов и являются стандартным механизмом. Браузер создаёт событие, аналогичное обычному клику, со всеми обычными свойствами. И да, isTrusted = true у него есть. Даже pointerType = mouse на месте!
Вернёмся к gesturechange
В какой-то момент я захотел реализовать пинч-зум на тачпаде мака (это такой зум через разведение двух пальцев). Зум через Ctrl+Колесо мыши для Windows/Linux/Маков с мышью уже был реализован, и я ожидал подвох. Памятуя о старом gesturechange, я решил “на удачу” попробовать его. Что же такое этот самый gesturechange? На самом деле, события целых три:
gesturestart
gesturechange
gestureendПоявились они ещё в iOS 2 (тогда ещё iPhone OS 2) в далёком 2008м году. Их назначение — предоставить простой способ реагировать на популярные жесты. Представьте, что вам нужно отследить жест поворота двумя пальцами через тачевые или pointer события. А тут простое апи, которое за 1 строчку кода выдаст вам нужное значение:
node.addEventListener('gesturechange', event => {
console.log(event.rotation);
});Выглядит очень полезно даже для 2025го года. Жаль, эту инициативу не подхватили другие браузеры, сейчас это апи считается устаревшим, и gesturechange так и остался заложником iOS… Или нет?
Попробовав использовать обработчик на маке, я с удивлением обнаружил, что он работает! Итого у меня получится обработчик wheel для колеса мыши, а также обработчик gesturechange для тачпада мака. Однако в процессе тестирования решения вскрылась проблема: временами зум “скакал”, перепрыгивая через значения.
Синтетические события колеса мыши
Разгадка оказалась очень простой: при использовании тачпада срабатывали оба обработчика. И сейчас я говорю не про жест “скролла” на тачпаде, который вполне ожидаемо генерирует события для обработчика wheel, я говорю про пинч-зум!. Ниже приведу часть полей этого события:
{
"type": "wheel",
"ctrlKey": true,
"delta": 0,
"deltaY": -0.01,
"altKey": false,
"shiftKey": false,
"metaKey": false
}Никакого скролла не было, однако браузер сгенерировал события этого самого скролла. Вероятно, разработчики браузера заметили, что на многих сайтах есть подобный перехват для случаев Ctrl+Колесо мыши, и сделали обходной трюк для своих пользователей. Даже поле ctrlKey = true на месте (да, мы всё ещё говорим про мак и нет, в реальности никакая клавиша не была нажата).
Получается, что достаточно одного обработчика событий wheel, даже для тачпада на маках. gesturechange работает не только на айфонах, но его можно было смело выкинуть. Если же мне когда-либо понадобится отследить жесты на телефонах, то я буду делать универсальный код, который заработает на андроидах, поэтому и тогда gesturechange не пригодится. Похоже, зря я сдувал пыль с этого события, и его можно обратно отложить в чулан.