52 votes

Comment obtenir les coordonnées du MouseEvent pour un élément qui a une transformation CSS3 ?

Je veux détecter a MouseEvent a eu lieu, en coordonnées relatives à l'élément cliqué . Pourquoi ? Parce que je veux ajouter un élément enfant positionné de manière absolue à l'emplacement cliqué.

Je sais comment le détecter lorsqu'il n'existe aucune transformation CSS3 (voir la description ci-dessous). Cependant, lorsque j'ajoute une transformation CSS3, mon algorithme se casse la figure et je ne sais pas comment le réparer.

Je n'utilise aucune bibliothèque JavaScript, et je veux comprendre comment les choses fonctionnent en JavaScript pur. Alors, s'il vous plaît, ne répondez pas par "utilisez simplement jQuery".

Au fait, je veux une solution qui fonctionne pour tous les MouseEvents, pas seulement pour le "click". Cela n'a pas d'importance, car je pense que tous les événements souris partagent les mêmes propriétés, donc la même solution devrait fonctionner pour tous.


Informations générales

Selon Spécification DOM niveau 2 , a MouseEvent possède quelques propriétés liées à l'obtention des coordonnées de l'événement :

  • screenX et screenY retourne les coordonnées de l'écran (l'origine est le coin supérieur gauche de l'écran de l'utilisateur)
  • clientX et clientY retourne les coordonnées relatives à la fenêtre du document.

Ainsi, afin de trouver la position de la MouseEvent par rapport au contenu de l'élément cliqué, je dois faire ce calcul :

ev.clientX - this.getBoundingClientRect().left - this.clientLeft + this.scrollLeft
  • ev.clientX est la coordonnée relative à la fenêtre du document
  • this.getBoundingClientRect().left est la position de l'élément par rapport à la fenêtre du document.
  • this.clientLeft est la quantité de bordure (et de barre de défilement) entre la limite de l'élément et les coordonnées intérieures.
  • this.scrollLeft est la quantité de défilement à l'intérieur de l'élément

getBoundingClientRect() , clientLeft et scrollLeft sont spécifiés à Module de visualisation du CSSOM .

Expérimenter sans CSS Transform (ça marche)

Confus ? Essayez le morceau suivant de JavaScript et HTML . En cliquant, un point rouge devrait apparaître à l'endroit exact où le clic s'est produit. Cette version est "assez simple" et fonctionne comme prévu.

function click_handler(ev) {
    var rect = this.getBoundingClientRect();
    var left = ev.clientX - rect.left - this.clientLeft + this.scrollLeft;
    var top = ev.clientY - rect.top - this.clientTop + this.scrollTop;

    var dot = document.createElement('div');
    dot.setAttribute('style', 'position:absolute; width: 2px; height: 2px; top: '+top+'px; left: '+left+'px; background: red;');
    this.appendChild(dot);
}

document.getElementById("experiment").addEventListener('click', click_handler, false);

<div id="experiment" style="border: 5px inset #AAA; background: #CCC; height: 400px; position: relative; overflow: auto;">
    <div style="width: 900px; height: 2px;"></div> 
    <div style="height: 900px; width: 2px;"></div>
</div>

Expérimenter l'ajout d'une transformation CSS (cela échoue)

Maintenant, essayez d'ajouter un CSS transform :

#experiment {
    transform: scale(0.5);
    -moz-transform: scale(0.5);
    -o-transform: scale(0.5);
    -webkit-transform: scale(0.5);
    /* Note that this is a very simple transformation. */
    /* Remember to also think about more complex ones, as described below. */
}

L'algorithme ne connaît pas les transformations, et calcule donc une position erronée. De plus, les résultats sont différents entre Firefox 3.6 et Chrome 12. Opera 11.50 se comporte exactement comme Chrome.

Dans cet exemple, la seule transformation était la mise à l'échelle, je pouvais donc multiplier le facteur d'échelle pour calculer la bonne coordonnée. Cependant, si nous pensons aux transformations arbitraires (mise à l'échelle, rotation, inclinaison, translation, matrice), et même aux transformations imbriquées (un élément transformé à l'intérieur d'un autre élément transformé), alors nous avons vraiment besoin d'un meilleur moyen de calculer les coordonnées.

7 votes

S'il vous plaît, ne répondez pas par "utilisez simplement jQuery" - je vous donnerais une note supérieure rien que pour ça...

0 votes

Il me semble que si vous aviez un moyen d'obtenir la matrice de transformation 3D qui décrit la transformation originale, vous seriez en mesure d'obtenir les coordonnées en leur faisant subir la même transformation, n'est-ce pas ? Qu'en est-il getComputedStyle(someElement).getPropertyValue('transform') ? Il semble renvoyer exactement une telle matrice. Je ne dis pas que les calculs matriciels sont faciles, mais ils sont précis et rapides...

0 votes

@StijndeWitt Il y a une façon officielle de procéder dans les navigateurs, voir ma réponse.

16voto

AshHeskes Points 1271

Le comportement que vous rencontrez est correct, et votre algorithme n'est pas en panne. Tout d'abord, les transformations CSS3 sont conçues pour ne pas interférer avec le modèle de boîte.

Pour essayer d'expliquer...

Lorsque vous appliquez une transformation CSS3 sur un élément, celui-ci adopte une sorte de positionnement relatif. En ce sens que les éléments environnants ne sont pas affectés par l'élément transformé.

Par exemple, imaginez trois div's dans une rangée horizontale. Si vous appliquez une transformation d'échelle pour diminuer la taille de la div centrale. Les div's environnantes ne se déplaceront pas vers l'intérieur pour occuper l'espace qu'occupait l'élément transformé.

exemple : http://jsfiddle.net/AshMokhberi/bWwkC/

Ainsi, dans le modèle de boîte, l'élément ne change pas réellement de taille. Seule la taille de son rendu change.

Vous devez également garder à l'esprit que vous appliquez une transformation d'échelle, de sorte que la taille "réelle" de votre élément est en fait la même que sa taille d'origine. Vous ne modifiez que sa taille perçue.

Pour expliquer

Imaginez que vous créez un div d'une largeur de 1000px et que vous le réduisez à la moitié de sa taille. La taille interne de la division est toujours de 1000px, et non de 500px.

La position de vos points est donc correcte par rapport à la taille "réelle" de la division.

J'ai modifié votre exemple pour illustrer.

Instructions

  1. Cliquez sur la division et gardez votre souris dans la même position.
  2. Trouvez le point dans la mauvaise position.
  3. Appuyez sur Q, la division deviendra de la bonne taille.
  4. Déplacez votre souris pour trouver le point dans la position correcte de l'endroit où vous avez cliqué.

http://jsfiddle.net/AshMokhberi/EwQLX/

Ainsi, pour que les coordonnées des clics de souris correspondent à l'emplacement visible de la division, vous devez comprendre que la souris renvoie des coordonnées basées sur la fenêtre, et que les décalages de votre division sont également basés sur sa taille "réelle".

Comme la taille de votre objet est relative à la fenêtre, la seule solution est de mettre à l'échelle les coordonnées du décalage par la même valeur d'échelle que votre division.

Cependant, cela peut devenir délicat en fonction de l'endroit où vous définissez la propriété Transform-origin de votre div. Car cela va affecter les décalages.

Voir ici.

http://jsfiddle.net/AshMokhberi/KmDxj/

J'espère que cela vous aidera.

2 votes

C'est une bonne explication, mais elle ne répond pas vraiment à la question. Elle explique très bien POURQUOI le comportement du navigateur est correct, mais ne répond pas à la question de savoir comment obtenir le comportement souhaité (en considérant des transformations arbitraires).

0 votes

Je sais que je peux mettre à l'échelle les coordonnées dans mon algorithme ; si c'était si simple, je l'aurais fait. Ce que vous n'avez peut-être pas remarqué, c'est que cela devient beaucoup plus délicat lorsque vous mélangez rotation, inclinaison, translation ou une matrice arbitraire. Cela devient encore plus délicat si nous avons un élément transformé à l'intérieur d'un autre élément transformé. C'est sur ce point que porte ma question. Et je n'aime pas l'idée de réimplémenter toutes les transformations en JavaScript.

1 votes

Vous avez raison, ça va devenir très délicat. Malheureusement, il n'y a pas d'autre moyen de le faire, à moins qu'ils ne modifient la spécification des transformations css3. La spécification pour les transformations css3 est encore à l'état de projet, et je pense que vous soulevez un bon point. J'envisagerais de rejoindre la liste de diffusion du projet de travail et de faire part de vos préoccupations. Détails ici w3.org/TR/css3-2d-transforms il est probablement utile de rejoindre la liste de diffusion 3d transforms, car cela s'applique aux deux parties de la spécification. w3.org/TR/css3-3d-transforms

9voto

4esn0k Points 113

Si l'élément est un conteneur et s'il est positionné de manière absolue ou relative, vous pouvez placer l'élément à l'intérieur de celui-ci, le positionner relativement au parent et largeur = 1px, hauteur = 1px, et le déplacer à l'intérieur du conteneur, et après chaque déplacement, utilisez document.elementFromPoint(event.clientX, event.clientY) =)))).

Vous pouvez utiliser la recherche binaire pour le rendre plus rapide. ça a l'air terrible, mais ça marche

http://jsfiddle.net/3VT5N/3/ - démo

0 votes

Une autre solution consiste à placer 3 divs dans les coins de cet élément, puis à trouver la matrice de transformation ... mais cela ne fonctionne que pour les éléments conteneurisables positionnés.

2 votes

Pourriez-vous essayer d'améliorer votre anglais dans votre réponse ? Je n'ai pu la comprendre que parce que j'ai lu attentivement le code source de votre démo.

1 votes

Je l'ai légèrement modifié pour obtenir une augmentation des performances de 20 à 40 %. Avant, cela prenait 50-70 ms sur mon site de transformations 3d, maintenant cela prend 30-45 ms. jsfiddle.net/3VT5N/49 est la version mise à jour. J'ai également changé les noms des propriétés dans l'objet retourné, de gauche et haut à x et y, et il retourne les coordonnées des pixels au lieu des pourcentages. J'ai également supprimé la fonction toString sans raison valable et j'ai ajouté une propriété time à l'objet retourné qui vous indique le nombre de millisecondes que l'opération a pris. Sur mon ordinateur, le nouvel exemple prend environ 20 ms, soit 1/50e de seconde. Auparavant, il fallait 40 ms.

3voto

Garrett Points 1534

Pour obtenir les coordonnées d'un MouseEvent par rapport à l'élément cliqué, utilisez offsetX / layerX .

Avez-vous essayé d'utiliser ev.layerX ou ev.offsetX ?

var offsetX = (typeof ev.offsetX == "number") ? ev.offsetX : ev.layerX || 0;

Voir aussi :

0 votes

Je suppose que vous avez manqué la partie où je parle d'éléments TRANSFORMÉS (en utilisant des transformations CSS 2D ou 3D).

1 votes

Non, je l'ai lu. J'ai essayé cette approche, en utilisant les transformations, juste avant de poster, et cela fonctionne dans les quelques navigateurs que j'ai ici. Si vous allez sur : songthaimassage.com/massage et essayer document.images[1].onclick = function(ev) { var offsetX = (typeof ev.offsetX == "number") ? ev.offsetX : ev.layerX || 0; console.log(offsetX); }; Ensuite, cliquez sur le bord gauche de la première image de la galerie, puis, après le zoom, cliquez près du bord gauche (vous devrez déplacer votre souris), vous devriez voir un résultat proche de 0.

0 votes

C'est fantastique et cela m'a aidé à résoudre un problème que j'avais à cause des transformations 3D où pageX et clientX nécessitaient d'autres calculs importants pour qu'ils soient corrects ! Merci.

3voto

4esn0k Points 295

De même, pour Webkit webkitConvertPointFromPageToNode peut être utilisée :

var div = document.createElement('div'), scale, point;
div.style.cssText = 'position:absolute;left:-1000px;top:-1000px';
document.body.appendChild(div);
scale = webkitConvertPointFromNodeToPage(div, new WebKitPoint(0, 0));
div.parentNode.removeChild(div);
scale.x = -scale.x / 1000;
scale.y = -scale.y / 1000;
point = webkitConvertPointFromPageToNode(element, new WebKitPoint(event.pageX * scale.x, event.pageY * scale.y));
point.x = point.x / scale.x;
point.y = point.y / scale.x;

1 votes

Avez-vous un lien vers la documentation de cette fonction ?

2 votes

Cette fonction est dépréciée. Donc, écrivons des tonnes de code pour détecter les coordonnées locales d'un événement souris ou tactile sur un élément 3D en rotation, par exemple un canevas, pour : regarder si ce point est transparent ? Ou peindre quelque chose à cet endroit... Omg. Stupide stupide stupide.

3voto

4esn0k Points 1

Une autre façon est de placer 3 divs dans les coins de cet élément, puis de trouver la matrice de transformation ... mais cela ne fonctionne que pour les éléments conteneurisables positionnés positionnés - 4esn0k

démo : http://jsfiddle.net/dAwfF/3/

0 votes

Il semble que votre utilisateur ne soit pas enregistré dans Stackoverflow, de sorte que vos deux réponses semblent provenir d'utilisateurs différents. Je pense que vous devriez vous connecter/enregistrer, afin que toutes vos réponses soient correctement liées à votre utilisateur.

1 votes

L'utilisation de 3 DIVs dans les coins fonctionnera pour toute transformation CSS 2D arbitraire. C'est très bien. Mais qu'en est-il des transformations en 3D ?

0 votes

Oui, la 3d ne fonctionnera pas avec cette méthode, bien qu'avec cette méthode nous puissions trouver la position en dehors de l'élément

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X