J'essaie de faire quelque chose de similaire à ce que Google maps intégré faire. Mon composant doit ignorer le simple toucher (permettant à l'utilisateur de faire défiler la page) et pincer à l'extérieur de lui-même (permettant à l'utilisateur de zoomer sur la page), mais doit réagir au double toucher (permettant à l'utilisateur de naviguer à l'intérieur du composant) et interdire toute action par défaut dans ce cas.
Comment empêcher la gestion par défaut des événements tactiles, mais uniquement dans le cas où l'utilisateur interagit avec mon composant avec deux doigts?
Ce que j'ai essayé:
J'ai essayé de capturer onTouchStart
, onTouchMove
et onTouchEnd
. Il s'avère que sur FF Android le premier événement qui se déclenche lors du pincement sur le composant est onTouchStart
avec une seule touche, puis onTouchStart
avec deux touches, puis onTouchMove
. Mais appeler event.preventDefault()
ou event.stopPropagation()
dans onTouchMove
handler n'arrête pas (toujours) le zoom/défilement de la page. Empêcher l'escalade des événements dans le premier appeler à onTouchStart
ça aide - malheureusement à ce moment-là je ne sais pas encore si ça va être multitouch ou pas, donc je ne peux pas l'utiliser.
La deuxième approche consistait à définir touch-action: none
sur document.body
. Cela fonctionne avec Chrome Android, mais je ne pouvais le faire fonctionner qu'avec Firefox Android si je le définissais sur tous les éléments (sauf pour mon composant). c'est faisable, il semble que cela pourrait avoir des effets secondaires indésirables et des problèmes de performances. EDIT: Des tests supplémentaires ont révélé que cela fonctionne pour Chrome uniquement si le CSS est défini avant le début du toucher. En d'autres termes, si j'injecte des styles CSS lorsque je détecte 2 doigts, alors touch-action
est ignoré. Ce n'est donc pas utile sur Chrome.
J'ai également essayé d'ajouter un nouvel écouteur d'événements sur le montage de composants:
document.body.addEventListener("touchmove", ev => {
ev.preventDefault();
ev.stopImmediatePropagation();
}, true);
(et la même chose pour touchstart
). Cela fonctionne dans Firefox Android, mais ne fait rien sur Chrome Android.
Je suis à court d'idées. Existe-t-il un moyen fiable pour tous les navigateurs de réaliser ce que Google a apparemment fait, ou a-t-il utilisé plusieurs hacks et de nombreux tests sur chaque navigateur pour le faire fonctionner? J'apprécierais que quelqu'un signale une erreur dans ma (mes) approche (s) ou propose une nouvelle façon.
TL; DR: { passive: false }
Me manquait lors de l'enregistrement des gestionnaires d'événements.
Le problème que j'ai eu avec preventDefault()
avec Chrome était dû à leur défilement "intervention" (lire: briser le style IE Web). court, car les gestionnaires qui n'appellent pas preventDefault()
peuvent être traités plus rapidement, une nouvelle option a été ajoutée à addEventListener
nommée passive
. Si elle est définie sur true
alors le gestionnaire d'événements promet de ne pas appeler preventDefault
(si c'est le cas, l'appel sera ignoré). Chrome cependant décidé d'aller plus loin et de faire {passive: true}
par défaut (depuis la version 56).
La solution appelle l'écouteur d'événements avec passive
explicitement défini sur false
:
window.addEventListener('touchmove', ev => {
if (weShouldStopDefaultScrollAndZoom) {
ev.preventDefault();
ev.stopImmediatePropagation();
};
}, { passive: false });
Notez que cela a un impact négatif sur les performances.
En remarque, il semble que j'ai mal compris le CSS touch-action
, Mais je ne peux toujours pas l'utiliser car il doit être défini avant le début de la séquence tactile. Mais si ce n'est pas le cas, il est probablement plus performant et semble être pris en charge sur toutes les plates-formes applicables (Safari sur Mac ne le prend pas en charge, mais sur iOS, il le fait). Cet article le dit le mieux:
Pour votre cas, vous souhaiterez probablement marquer votre zone de texte (ou autre) "action tactile: aucune" pour désactiver le défilement/zoom sans désactiver tous les autres comportements.
La propriété CSS doit être définie sur le composant et non sur document
comme je l'ai fait:
<div style="touch-action: none;">
... my component ...
</div>
Dans mon cas, je devrai toujours utiliser les gestionnaires d'événements passive
, mais il est bon de connaître les options ... J'espère que cela aide quelqu'un.
Voici mon idée:
J'ai utilisé un div
avec opacity: 0
pour couvrir le map
avec z-index
> carte z-index
Et je vais détecter dans le covered div
. Si je détecte 2 doigts touché dans ce covered div
, Je vais display: none
this div
pour permettre à l'utilisateur d'utiliser 2 doigts dans cette carte.
Sinon, dans document touchEnd
Je vais récupérer ce covered div
en utilisant display: block
pour nous assurer que nous pouvons faire défiler.
Essayez d'utiliser une instruction if pour voir s'il y a plus d'une touche:
document.body.addEventListener("touchmove", ev => {
if (ev.touches.length > 1) {
ev.preventDefault();
ev.stopImmediatePropagation();
}
}, true);