web-dev-qa-db-fra.com

Comment déclencher UNIQUEMENT un événement de clic de parent lorsqu'un enfant est cliqué

L'enfant et le parent sont cliquables (l'enfant peut être un lien ou une div avec des événements de clic jQuery). Lorsque je clique sur l'enfant, comment puis-je déclencher uniquement l'événement de clic parent mais pas l'événement enfant?

21
Mike Li

Phases d'événement DOM

Les événements ont trois phases:

  1. Capture: La première phase est "capture" où les gestionnaires d'événements sont appelés en commençant par <window> Et en descendant par les descendants vers la cible de l'événement.
  2. Cible: La deuxième phase est la phase "cible" lorsque les écouteurs d'événements sur la cible sont appelés.
  3. Bulle: La troisième phase est la "bulle" qui commence avec les gestionnaires écoutant le parent de la cible étant appelés en premier, puis, progressivement, les ancêtres de cette élément.

Les événements ont également une "action par défaut", qui se produit après la phase de propagation. L'action par défaut est l'action définie par le navigateur qui se produit normalement pour les événements du type spécifié sur le type d'élément qui est la cible de l'événement (par exemple, le navigateur naviguant vers le href d'un <a> sur un click, tandis qu'un click sur un autre type d'élément aura une action par défaut différente).

Brouillon des événements DOM niveau a un diagramme qui montre graphiquement comment les événements se propagent à travers le DOM:

Graphical representation of an event dispatched in a DOM tree using the DOM event flow
Copyright de l'image © 2016 World Wide Web Consortium , ( MIT , ERCIM , Keio , Beihang ). http://www.w3.org/Consortium/Legal/2015/doc-license (Utilisation autorisée par la licence )

Pour plus d'informations sur la capture et la propagation, voir: " Qu'est-ce que la propagation et la capture d'événements? "; Le Brouillon des événements DOM niveau ; ou W3C DOM4: événements

Empêcher l'événement d'atteindre l'enfant

Pour ce que vous voulez, pour obtenir l'événement sur le parent avant et empêcher l'événement sur l'enfant, vous devez recevoir l'événement dans la phase de capture. Une fois que vous l'avez reçu dans la phase de capture, vous devez arrêter l'événement de se propager à tous les gestionnaires d'événements sur les éléments situés plus bas dans l'arborescence DOM, ou qui se sont inscrits pour écouter dans la phase de propagation (c'est-à-dire tous les écouteurs sur les éléments/phases qui être visité par l'événement après votre auditeur). Pour ce faire, appelez event.stopPropagation() .

Réception d'événements pendant la phase de capture

Lorsque vous ajoutez l'écouteur avec addEventListener(type, listener[, useCapture]) , vous pouvez avoir l'argument useCapture soit true.

Citant MDN:

[useCapture is] Un booléen qui indique que les événements de ce type seront envoyés à l'écouteur enregistré avant d'être envoyés à n'importe quel EventTarget en dessous dans l'arborescence DOM . Les événements qui remontent dans l'arborescence ne déclencheront pas un écouteur désigné pour utiliser la capture. La propagation et la capture d'événements sont deux façons de propager des événements qui se produisent dans un élément imbriqué dans un autre élément, lorsque les deux éléments ont enregistré un descripteur pour cet événement. Le mode de propagation d'événement détermine l'ordre dans lequel les éléments reçoivent l'événement. Voir les événements DOM niveau 3 et l'ordre des événements JavaScript pour une explication détaillée. S'il n'est pas spécifié, useCapture prend par défaut la valeur false.

Empêcher d'autres gestionnaires d'obtenir l'événement

  • event.preventDefault() est utilisé pour empêcher l'action par défaut (par exemple, empêcher le navigateur de naviguer vers le href d'un <a> sur un click). [Ceci est utilisé dans l'exemple ci-dessous, mais n'a aucun effet réel car il n'y a pas d'action par défaut pour le texte. Il est utilisé ici car la plupart du temps, lorsque vous ajoutez un gestionnaire d'événements de clic, vous souhaitez empêcher l'action par défaut. Ainsi, c'est une bonne idée d'avoir l'habitude de le faire, et de ne pas le faire quand vous savez que vous ne voulez pas.]
  • event.stopPropagation() est utilisé pour empêcher les gestionnaires d'éléments sur plus tard dans l'une des phases d'événement de recevoir l'événement. Cela n'empêche aucun gestionnaire supplémentaire sur l'élément et la phase en cours d'être appelé. Il n'empêche pas l'action par défaut de se produire.
  • event.stopImmediatePropagation() : Les gestionnaires sur le même élément et la même phase sont appelés dans l'ordre dans lequel ils sont ajoutés. En plus d'avoir le même effet que event.stopPropagation(), event.stopImmediatePropagation() empêche tout gestionnaire supplémentaire sur le même élément et la même phase d'événement de recevoir l'événement. Il n'empêche pas l'action par défaut de se produire. Étant donné que l'exigence de cette question est d'empêcher l'événement de se propager aux enfants, nous n'avons pas besoin de l'utiliser, mais nous pourrions le faire au lieu d'utiliser event.stopPropagation(). Notez cependant que les écouteurs sur le même élément sont appelés dans l'ordre dans lequel ils sont ajoutés. Ainsi, event.stopImmediatePropagation() n'empêchera pas l'événement d'être reçu par ces écouteurs sur le même élément et la même phase que votre écouteur qui ont été ajoutés avant votre écouteur.

Exemple

Dans l'exemple suivant, les écouteurs d'événements sont placés sur les éléments parent et enfant <div>. Seul l'écouteur placé sur le parent reçoit l'événement car il reçoit l'événement pendant la phase de capture avant l'enfant et il exécute event.stopPropagation().

var parent=document.getElementById('parent');
var child=document.getElementById('child');
var preventChild=document.getElementById('preventChild');

parent.addEventListener('click',function(event){
    if(preventChild.checked) {
        event.stopPropagation();
    }
    event.preventDefault();
    var targetText;
    if(event.target === parent) {
        targetText='parent';
    }
    if(event.target === child) {
        targetText='child';
    }
    console.log('Click Detected in parent on ' + targetText);
},true);
                      
child.addEventListener('click',function(event){
    console.log('Click Detected in child (bubbling phase)');
});

child.addEventListener('click',function(event){
    console.log('Click Detected in child (capture phase)');
},true);
<input id="preventChild" type="checkbox" checked>Prevent child from getting event</input>
<div id="parent">Parent Text<br/>
  <div id="child" style="margin-left:10px;">Child Text<br/>
  </div>
</div>

jQuery

jQuery ne prend pas en charge l'utilisation de la capture sur les événements. Pour plus d'informations sur les raisons, voir: " Pourquoi le modèle d'événement jQuery ne prend-il pas en charge la capture d'événements et prend-il uniquement en charge la propagation d'événements "

71
Makyen

Une autre option pour cela qui peut être utile dans certaines circonstances lorsque vous savez qu'aucun des éléments enfants n'est interactif consiste à définir pointer-events: none dans votre css ( link ). Je l'applique généralement à tous les éléments enfants de l'élément sur lequel je veux capturer l'interaction. Comme ça:

#parentDiv * {
    pointer-events: none
}

Noter la *, déclarant que la règle s'applique à tous les enfants de parentDiv.

15
Abraham Brookes