Comment détecter un clic à l'extérieur d'un élément ?
La raison pour laquelle cette question est si populaire et a tant de réponses est qu'elle est faussement complexe. Après presque huit ans et des dizaines de réponses, je suis sincèrement surpris de voir le peu de soin apporté à l'accessibilité.
Je voudrais masquer ces éléments lorsque l'utilisateur clique en dehors de la zone des menus.
C'est une noble cause et c'est le réel question. Le titre de la question - qui est ce à quoi la plupart des réponses semblent tenter de répondre - contient un malheureux faux-fuyant.
Indice : c'est le mot "cliquer" !
Vous ne voulez pas vraiment lier les gestionnaires de clics.
Si vous liez les gestionnaires de clics pour fermer la boîte de dialogue, vous avez déjà échoué. La raison pour laquelle vous avez échoué est que tout le monde ne déclenche pas la fonction click
événements. Les utilisateurs n'utilisant pas de souris pourront échapper à votre dialogue (et votre menu contextuel est sans doute un type de dialogue) en appuyant sur Tab et ils ne seront pas en mesure de lire le contenu de la boîte de dialogue sans déclencher un message d'erreur. click
événement.
Alors reformulons la question.
Comment fermer une boîte de dialogue lorsque l'utilisateur en a fini avec elle ?
C'est l'objectif. Malheureusement, nous devons maintenant lier le userisfinishedwiththedialog
et cette liaison n'est pas si simple.
Alors comment détecter qu'un utilisateur a fini d'utiliser un dialogue ?
focusout
événement
Un bon début est de déterminer si le focus a quitté le dialogue.
Conseil : soyez prudent avec les blur
événement, blur
ne se propage pas si l'événement était lié à la phase de bouillonnement !
La méthode de jQuery focusout
fera parfaitement l'affaire. Si vous ne pouvez pas utiliser jQuery, alors vous pouvez utiliser blur
pendant la phase de capture :
element.addEventListener('blur', ..., true);
// use capture: ^^^^
En outre, pour de nombreuses boîtes de dialogue, vous devrez permettre au conteneur d'obtenir le focus. Ajouter tabindex="-1"
pour permettre à la boîte de dialogue de recevoir le focus de manière dynamique sans interrompre le flux de tabulation.
$('a').on('click', function () {
$(this.hash).toggleClass('active').focus();
});
$('div').on('focusout', function () {
$(this).removeClass('active');
});
div {
display: none;
}
.active {
display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#example">Example</a>
<div id="example" tabindex="-1">
Lorem ipsum <a href="http://example.com">dolor</a> sit amet.
</div>
Si vous jouez avec cette démo pendant plus d'une minute, vous devriez rapidement commencer à voir des problèmes.
La première est que le lien dans la boîte de dialogue n'est pas cliquable. Si vous tentez de cliquer dessus ou d'utiliser la tabulation, la boîte de dialogue se ferme avant que l'interaction n'ait lieu. Cela est dû au fait que la mise au point de l'élément intérieur déclenche une action focusout
avant de déclencher un focusin
l'événement à nouveau.
La solution consiste à mettre en file d'attente le changement d'état dans la boucle d'événement. Cela peut être fait en utilisant setImmediate(...)
ou setTimeout(..., 0)
pour les navigateurs qui ne prennent pas en charge setImmediate
. Une fois mis en file d'attente, il peut être annulé par une nouvelle demande d'accès. focusin
:
$('.submenu').on({
focusout: function (e) {
$(this).data('submenuTimer', setTimeout(function () {
$(this).removeClass('submenu--active');
}.bind(this), 0));
},
focusin: function (e) {
clearTimeout($(this).data('submenuTimer'));
}
});
$('a').on('click', function () {
$(this.hash).toggleClass('active').focus();
});
$('div').on({
focusout: function () {
$(this).data('timer', setTimeout(function () {
$(this).removeClass('active');
}.bind(this), 0));
},
focusin: function () {
clearTimeout($(this).data('timer'));
}
});
div {
display: none;
}
.active {
display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#example">Example</a>
<div id="example" tabindex="-1">
Lorem ipsum <a href="http://example.com">dolor</a> sit amet.
</div>
Le deuxième problème est que la boîte de dialogue ne se ferme pas lorsque l'on appuie à nouveau sur le lien. Cela est dû au fait que la boîte de dialogue perd le focus, ce qui déclenche le comportement de fermeture, après quoi le clic sur le lien déclenche la réouverture de la boîte de dialogue.
Comme pour le problème précédent, il faut gérer l'état du focus. Étant donné que le changement d'état a déjà été mis en file d'attente, il s'agit simplement de gérer les événements de focus sur les déclencheurs de dialogue :
Cela devrait vous sembler familier
$('a').on({
focusout: function () {
$(this.hash).data('timer', setTimeout(function () {
$(this.hash).removeClass('active');
}.bind(this), 0));
},
focusin: function () {
clearTimeout($(this.hash).data('timer'));
}
});
$('a').on('click', function () {
$(this.hash).toggleClass('active').focus();
});
$('div').on({
focusout: function () {
$(this).data('timer', setTimeout(function () {
$(this).removeClass('active');
}.bind(this), 0));
},
focusin: function () {
clearTimeout($(this).data('timer'));
}
});
$('a').on({
focusout: function () {
$(this.hash).data('timer', setTimeout(function () {
$(this.hash).removeClass('active');
}.bind(this), 0));
},
focusin: function () {
clearTimeout($(this.hash).data('timer'));
}
});
div {
display: none;
}
.active {
display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#example">Example</a>
<div id="example" tabindex="-1">
Lorem ipsum <a href="http://example.com">dolor</a> sit amet.
</div>
Esc clé
Si vous pensiez en avoir fini avec la gestion des états du focus, vous pouvez faire plus pour simplifier l'expérience de l'utilisateur.
Il s'agit souvent d'une fonctionnalité "agréable à avoir", mais il est courant, lorsque vous avez une modale ou une fenêtre contextuelle de quelque sorte que ce soit, que l'icône de l'outil de gestion des données soit utilisée. Esc La clef de voûte va le fermer.
keydown: function (e) {
if (e.which === 27) {
$(this).removeClass('active');
e.preventDefault();
}
}
$('a').on('click', function () {
$(this.hash).toggleClass('active').focus();
});
$('div').on({
focusout: function () {
$(this).data('timer', setTimeout(function () {
$(this).removeClass('active');
}.bind(this), 0));
},
focusin: function () {
clearTimeout($(this).data('timer'));
},
keydown: function (e) {
if (e.which === 27) {
$(this).removeClass('active');
e.preventDefault();
}
}
});
$('a').on({
focusout: function () {
$(this.hash).data('timer', setTimeout(function () {
$(this.hash).removeClass('active');
}.bind(this), 0));
},
focusin: function () {
clearTimeout($(this.hash).data('timer'));
}
});
div {
display: none;
}
.active {
display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#example">Example</a>
<div id="example" tabindex="-1">
Lorem ipsum <a href="http://example.com">dolor</a> sit amet.
</div>
Si vous savez que vous avez des éléments focalisables dans la boîte de dialogue, vous n'aurez pas besoin de focaliser la boîte de dialogue directement. Si vous créez un menu, vous pouvez plutôt cibler le premier élément du menu.
click: function (e) {
$(this.hash)
.toggleClass('submenu--active')
.find('a:first')
.focus();
e.preventDefault();
}
$('.menu__link').on({
click: function (e) {
$(this.hash)
.toggleClass('submenu--active')
.find('a:first')
.focus();
e.preventDefault();
},
focusout: function () {
$(this.hash).data('submenuTimer', setTimeout(function () {
$(this.hash).removeClass('submenu--active');
}.bind(this), 0));
},
focusin: function () {
clearTimeout($(this.hash).data('submenuTimer'));
}
});
$('.submenu').on({
focusout: function () {
$(this).data('submenuTimer', setTimeout(function () {
$(this).removeClass('submenu--active');
}.bind(this), 0));
},
focusin: function () {
clearTimeout($(this).data('submenuTimer'));
},
keydown: function (e) {
if (e.which === 27) {
$(this).removeClass('submenu--active');
e.preventDefault();
}
}
});
.menu {
list-style: none;
margin: 0;
padding: 0;
}
.menu:after {
clear: both;
content: '';
display: table;
}
.menu__item {
float: left;
position: relative;
}
.menu__link {
background-color: lightblue;
color: black;
display: block;
padding: 0.5em 1em;
text-decoration: none;
}
.menu__link:hover,
.menu__link:focus {
background-color: black;
color: lightblue;
}
.submenu {
border: 1px solid black;
display: none;
left: 0;
list-style: none;
margin: 0;
padding: 0;
position: absolute;
top: 100%;
}
.submenu--active {
display: block;
}
.submenu__item {
width: 150px;
}
.submenu__link {
background-color: lightblue;
color: black;
display: block;
padding: 0.5em 1em;
text-decoration: none;
}
.submenu__link:hover,
.submenu__link:focus {
background-color: black;
color: lightblue;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="menu">
<li class="menu__item">
<a class="menu__link" href="#menu-1">Menu 1</a>
<ul class="submenu" id="menu-1" tabindex="-1">
<li class="submenu__item"><a class="submenu__link" href="http://example.com/#1">Example 1</a></li>
<li class="submenu__item"><a class="submenu__link" href="http://example.com/#2">Example 2</a></li>
<li class="submenu__item"><a class="submenu__link" href="http://example.com/#3">Example 3</a></li>
<li class="submenu__item"><a class="submenu__link" href="http://example.com/#4">Example 4</a></li>
</ul>
</li>
<li class="menu__item">
<a class="menu__link" href="#menu-2">Menu 2</a>
<ul class="submenu" id="menu-2" tabindex="-1">
<li class="submenu__item"><a class="submenu__link" href="http://example.com/#1">Example 1</a></li>
<li class="submenu__item"><a class="submenu__link" href="http://example.com/#2">Example 2</a></li>
<li class="submenu__item"><a class="submenu__link" href="http://example.com/#3">Example 3</a></li>
<li class="submenu__item"><a class="submenu__link" href="http://example.com/#4">Example 4</a></li>
</ul>
</li>
</ul>
lorem ipsum <a href="http://example.com/">dolor</a> sit amet.
Rôles WAI-ARIA et autres supports d'accessibilité
Cette réponse couvre, nous l'espérons, les bases de la prise en charge de l'accessibilité du clavier et de la souris pour cette fonctionnalité, mais comme elle est déjà assez importante, je vais éviter toute discussion sur les éléments suivants Rôles et attributs WAI-ARIA Cependant, je hautement Nous recommandons aux responsables de la mise en œuvre de se référer à la spécification pour obtenir des détails sur les rôles qu'ils doivent utiliser et tout autre attribut approprié.
47 votes
Voici un exemple de cette stratégie : jsfiddle.net/tedp/aL7Xe/1
21 votes
Comme Tom l'a mentionné, vous voudrez lire css-tricks.com/dangers-stopping-event-propagation avant d'utiliser cette approche. L'outil jsfiddle est cependant assez cool.
3 votes
Obtenir une référence à l'élément puis à event.target, et enfin != ou == les deux, puis exécuter le code en conséquence
0 votes
Essayez d'utiliser
event.path
. http://stackoverflow.com/questions/152975/how-do-i-detect-a-click-outside-an-element/43405204#434052046 votes
Solution Vanilla JS avec
event.target
et sansevent.stopPropagation
.0 votes
Puisque cela n'est pas mentionné partout dans les réponses mais il m'est utile dans ce contexte : lorsque la souris sort de la fenêtre,
event.relatedTarget
estnull
dans lemouseout
et c'est l'élément sur lequel la souris se trouve maintenant, sinon.