Comment savoir quel élément DOM est ciblé?

1309

Je voudrais savoir, en JavaScript, quel élément a actuellement le focus. J'ai parcouru le DOM et je n'ai pas encore trouvé ce dont j'avais besoin. Y a-t-il un moyen de le faire et comment?

La raison pour laquelle je cherchais ceci:

J'essaie de créer des touches comme les flèches et de enterparcourir une table d'éléments d'entrée. Tab fonctionne maintenant, mais entrez, et les flèches ne le semblent pas par défaut. J'ai mis en place la partie de gestion des clés, mais maintenant je dois comprendre comment déplacer le focus dans les fonctions de gestion des événements.

Tony Peterson
la source
2
Voici un bookmarklet qui console.log l'élément avec le focus: github.com/lingtalfi/where-is-focus-bookmarklet
ling
Vous pouvez utiliser le find-focused-elementpackage: npmjs.com/package/find-focused-element
Maxim Zhukov

Réponses:

1533

Utilisez document.activeElement, il est pris en charge dans tous les principaux navigateurs.

Auparavant, si vous essayiez de savoir quel champ de formulaire était ciblé, vous ne pouviez pas. Pour émuler la détection dans des navigateurs plus anciens, ajoutez un gestionnaire d'événements "focus" à tous les champs et enregistrez le dernier champ focalisé dans une variable. Ajoutez un gestionnaire de "flou" pour effacer la variable lors d'un événement de flou pour le dernier champ focalisé.

Si vous devez supprimer le, activeElementvous pouvez utiliser le flou; document.activeElement.blur(). Il va changer activeElementà body.

Liens connexes:

Dave Jarvis
la source
53
Je ne suis pas sûr d'IE, mais FF et Safari renvoient tous les deux l'élément BODY.
JW.
10
activeElementne renvoie en fait pas l'élément focalisé. Tout élément peut avoir le focus. Si un document a 4 'divisions de défilement', 0 ou 1 de ces divisions peut défiler avec les touches fléchées. Si vous cliquez sur un, ce div est focalisé. Si vous cliquez en dehors de tout, le corps est concentré. Comment savoir sur quel scrolldiv se concentre? jsfiddle.net/rudiedirkx/bC5ke/show (vérifier la console)
Rudie
18
@Rudie, @Stewart: J'ai construit sur votre violon pour créer un terrain de jeu plus élaboré: jsfiddle.net/mklement/72rTF . Vous constaterez que le seul navigateur majeur (à la fin de 2012) qui peut réellement se concentrer sur un tel divnavigateur est Firefox 17, et uniquement en le tabulant . Les types d'éléments renvoyés par TOUS les principaux navigateurs document.activeElementsont limités aux éléments liés à l' entrée . Si aucun élément de ce type n'a le focus, tous les principaux navigateurs renvoient l' bodyélément - à l'exception d'IE 9, qui renvoie l' htmlélément.
mklement0
10
Je ne sais pas si cela aide, mais vous pouvez faire en sorte qu'un élément tel qu'un div reçoive le focus clavier en incluant l'attribut tabindex = "0"
Marco Luglio
4
Tout accès à document.activeElementdoit être encapsulé try catchcar dans certaines circonstances, il peut lever une exception (pas seulement IE9 AFAIK). Voir bugs.jquery.com/ticket/13393 et bugs.jqueryui.com/ticket/8443
robocat
127

Comme l'a dit JW, vous ne pouvez pas trouver l'élément ciblé actuel, au moins d'une manière indépendante du navigateur. Mais si votre application est uniquement IE (certains le sont ...), vous pouvez la trouver de la manière suivante:

document.activeElement

EDIT: Il semble qu'IE n'ait pas tout mal après tout, cela fait partie du brouillon HTML5 et semble être pris en charge par la dernière version de Chrome, Safari et Firefox au moins.

Wookai
la source
5
FF3 aussi. Cela fait en fait partie de la spécification HTML5 autour de la "gestion du focus".
Crescent Fresh
2
Il fonctionne dans la version actuelle de Chrome et Opera (9.62). Ne fonctionne pas dans Safari 3.2.3 sur OS X, mais il fonctionne dans Safari 4 qui a été publié hier :)
Greg
toujours le même pour le chrome 19: S
Sebas
1
Cela ne fonctionne que dans chrome (20) / safari (5.1.3) lorsque vous utilisez le clavier pour taper sur l'élément. Si vous cliquez dessus, ni le sélecteur jquery: focus ni le document.activeElement ne parviennent à renvoyer ce sur quoi vous avez cliqué (renvoyant respectivement l'élément indéfini et le corps du document). PS Je n'arrive pas à croire que ce fil a 2 ans et il y a encore des problèmes de régression sur le webkit, avec celui où les liens de saut ne fonctionnent pas, mais beaucoup de travail est en cours avec l'ajout de css3 expérimental. Je pense que je peux recommencer à recommander Firefox à ma famille et à mes amis.
Dawn
^^ document.activeElement est très bien lorsque vous cliquez pour vous concentrer sur une zone de texte ou une autre entrée. S'il s'agit d'un lien, il ne se focalisera pas lorsque vous cliquez dessus (dans la page ou bien évidemment autrement).
marksyzm
84

Si vous pouvez utiliser jQuery, il prend désormais en charge: focus, assurez-vous simplement que vous utilisez la version 1.6+.

Cette déclaration vous donnera l'élément actuellement ciblé.

$(":focus")

De: Comment sélectionner un élément qui se concentre dessus avec jQuery

William Denniss
la source
4
C'est bien, mais comment jQuery le fait-il? document.activeElement? J'ai trouvé ceci: return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
Harry Pehkonen
46

document.activeElementfait maintenant partie de la spécification de brouillon HTML5 , mais elle n'est peut-être pas encore prise en charge dans certains navigateurs non majeurs / mobiles / plus anciens. Vous pouvez revenir à querySelector(si cela est pris en charge). Il convient également de mentionner que document.activeElementcela reviendra document.bodysi aucun élément n'est focalisé - même si la fenêtre du navigateur n'a pas le focus.

Le code suivant contournera ce problème et se rabattra sur querySelectorun meilleur support.

var focused = document.activeElement;
if (!focused || focused == document.body)
    focused = null;
else if (document.querySelector)
    focused = document.querySelector(":focus");

Une autre chose à noter est la différence de performances entre ces deux méthodes. L'interrogation du document avec des sélecteurs sera toujours beaucoup plus lente que l'accès à la activeElementpropriété. Voir ce test jsperf.com .

Andy E
la source
21

En soi, document.activeElementpeut toujours renvoyer un élément si le document n'est pas focalisé (et donc rien dans le document n'est focalisé!)

Vous voudrez peut -être ce comportement, ou il peut ne pas avoir d'importance (par exemple dans un keydownévénement), mais si vous avez besoin de savoir que quelque chose est réellement concentré, vous pouvez également vérifier document.hasFocus().

Ce qui suit vous donnera l'élément focalisé s'il y en a un, ou bien null.

var focused_element = null;
if (
    document.hasFocus() &&
    document.activeElement !== document.body &&
    document.activeElement !== document.documentElement
) {
    focused_element = document.activeElement;
}

Pour vérifier si un élément spécifique a le focus, c'est plus simple:

var input_focused = document.activeElement === input && document.hasFocus();

Pour vérifier si quelque chose est ciblé, c'est encore plus complexe:

var anything_is_focused = (
    document.hasFocus() &&
    document.activeElement !== null &&
    document.activeElement !== document.body &&
    document.activeElement !== document.documentElement
);

Remarque sur la robustesse : dans le code dans lequel il vérifie document.bodyet document.documentElement, c'est parce que certains navigateurs retournent l'un d'eux ou nulllorsque rien n'est focalisé.

Il ne prend pas en compte si le <body>(ou peut-être <html>) avait un tabIndexattribut et pouvait donc être réellement concentré . Si vous écrivez une bibliothèque ou quelque chose et que vous voulez qu'elle soit robuste, vous devriez probablement gérer cela d'une manière ou d'une autre.


Voici une version ( one-liner) ( lourd airquotes) pour obtenir l'élément focalisé, ce qui est conceptuellement plus compliqué parce que vous devez connaître les courts-circuits, et vous savez, cela ne tient évidemment pas sur une seule ligne, en supposant que vous veulent qu'il soit lisible.
Je ne recommanderai pas celui-ci. Mais si vous êtes un 1337 hax0r, idk ... il est là.
Vous pouvez également retirer la || nullpièce si cela ne vous dérange pas d'obtenir falsedans certains cas. (Vous pouvez toujours obtenir nullsi document.activeElementest null):

var focused_element = (
    document.hasFocus() &&
    document.activeElement !== document.body &&
    document.activeElement !== document.documentElement &&
    document.activeElement
) || null;

Pour vérifier si un élément spécifique est ciblé, vous pouvez également utiliser des événements, mais cette méthode nécessite une configuration (et éventuellement un démontage) et, surtout, suppose un état initial :

var input_focused = false;
input.addEventListener("focus", function() {
    input_focused = true;
});
input.addEventListener("blur", function() {
    input_focused = false;
});

Vous pouvez corriger l'hypothèse de l'état initial en utilisant la méthode sans événement, mais vous pourriez aussi bien l'utiliser à la place.

1j01
la source
15

document.activeElementpeut par défaut être l' <body>élément si aucun élément focalisable n'est au point. De plus, si un élément est mis au point et que la fenêtre du navigateur est floue, activeElementil continuera de contenir l'élément mis au point.

Si l' une de ces deux comportements ne sont pas souhaitables, envisager une approche basée sur CSS: document.querySelector( ':focus' ).

Nate Whittaker
la source
Cool, oui dans mon cas, votre approche était absolument logique. Je peux définir mes éléments focalisables avec 'tabindex = "- 1"', si aucun d'entre eux n'a le focus (disons, du texte ou une image, que je ne me soucie pas) le document.querySelector (': focus') renvoie nul.
Manfred
Voir ma réponse pour éviter d'utiliser querySelector: stackoverflow.com/a/40873560/2624876
1j01
10

J'ai aimé l'approche utilisée par Joel S, mais j'aime aussi la simplicité de document.activeElement. J'ai utilisé jQuery et combiné les deux. Les navigateurs plus anciens qui ne prennent pas en charge document.activeElementutiliseront jQuery.data()pour stocker la valeur de «hasFocus». Les nouveaux navigateurs utiliseront document.activeElement. Je suppose que document.activeElementcela aura de meilleures performances.

(function($) {
var settings;
$.fn.focusTracker = function(options) {
    settings = $.extend({}, $.focusTracker.defaults, options);

    if (!document.activeElement) {
        this.each(function() {
            var $this = $(this).data('hasFocus', false);

            $this.focus(function(event) {
                $this.data('hasFocus', true);
            });
            $this.blur(function(event) {
                $this.data('hasFocus', false);
            });
        });
    }
    return this;
};

$.fn.hasFocus = function() {
    if (this.length === 0) { return false; }
    if (document.activeElement) {
        return this.get(0) === document.activeElement;
    }
    return this.data('hasFocus');
};

$.focusTracker = {
    defaults: {
        context: 'body'
    },
    focusedElement: function(context) {
        var focused;
        if (!context) { context = settings.context; }
        if (document.activeElement) {
            if ($(document.activeElement).closest(context).length > 0) {
                focused = document.activeElement;
            }
        } else {
            $(':visible:enabled', context).each(function() {
                if ($(this).data('hasFocus')) {
                    focused = this;
                    return false;
                }
            });
        }
        return $(focused);
    }
};
})(jQuery);
Jason
la source
3
Cela pourrait-il être remplacé par celui de @William Denniss $("*:focus")?
Pylinux
Je suppose que oui. Je l'ai écrit il y a longtemps et je n'ai jamais eu de raison de revoir une meilleure solution maintenant que c'est 5 ans plus tard. Essaye le! Je pourrais juste faire la même chose. J'ai moins de plugin sur notre site! :)
Jason
10

Un petit assistant que j'ai utilisé à ces fins dans Mootools:

FocusTracker = {
    startFocusTracking: function() {
       this.store('hasFocus', false);
       this.addEvent('focus', function() { this.store('hasFocus', true); });
       this.addEvent('blur', function() { this.store('hasFocus', false); });
    },

    hasFocus: function() {
       return this.retrieve('hasFocus');
    }
}

Element.implement(FocusTracker);

De cette façon, vous pouvez vérifier si l'élément a le focus à el.hasFocus()condition d' startFocusTracking()avoir été appelé sur l'élément donné.

Joel S
la source
7

JQuery prend en charge la :focuspseudo-classe en cours. Si vous le recherchez dans la documentation JQuery, vérifiez sous "Sélecteurs" où il vous pointe vers les documents CSS du W3C . J'ai testé avec Chrome, FF et IE 7+. Notez que pour qu'il fonctionne dans IE, il <!DOCTYPE...doit exister sur la page html. Voici un exemple en supposant que vous avez attribué un identifiant à l'élément qui a le focus:

$(":focus").each(function() {
  alert($(this).attr("id") + " has focus!");
});
DeezCashews
la source
1
Vous devez (toujours?) Utiliser à la this.idplace de $(this).attr('id'), ou du moins (lorsque vous avez déjà votre objet jQuery) $(this)[0].id. Javascript natif à ce niveau est bien plus rapide et plus efficace. Cela pourrait ne pas être visible dans ce cas, mais à l'échelle du système, vous remarquerez une différence.
Martijn
7

Si vous voulez obtenir un objet qui est une instance de Element, vous devez utiliser document.activeElement, mais si vous voulez obtenir un objet qui est une instance de Text, vous devez utiliser document.getSelection().focusNode.

J'espère que ça aide.

rplaurindo
la source
Mieux de quelle manière?
1j01
Ouvrez l'inspecteur de votre navigateur, cliquez sur n'importe quel endroit de la page, après cela document.getSelection().focusNode.parentElementet appuyez sur Entrée. Après cela, passez document.activeElementet faites-le. ;)
rplaurindo
Avec cette zone de commentaire focalisée, document.activeElementdonne le <textarea>alors document.getSelection().focusNodedonne le <td>qui contient le <textarea>(et document.getSelection().focusNode.parentElementdonne le <tr>contenant le <td>)
1j01
Désolé, mes excuses. Je ne l'ai pas bien expliqué. Si vous voulez obtenir un objet qui est une instance de Element, vous devez utiliser document.activeElement, mais si vous voulez obtenir un objet qui est une instance de Text, vous devez utiliser document.getSelection().focusNode. Veuillez le tester à nouveau. J'espère avoir aidé.
rplaurindo
2
La question consiste à demander quel élément est actuellement ciblé. Et le focusNoden'est pas non plus garanti d'être un nœud de texte.
1j01
6

Il existe des problèmes potentiels avec l'utilisation de document.activeElement. Considérer:

<div contentEditable="true">
  <div>Some text</div>
  <div>Some text</div>
  <div>Some text</div>
</div>

Si l'utilisateur se concentre sur un div interne, document.activeElement fait toujours référence au div externe. Vous ne pouvez pas utiliser document.activeElement pour déterminer lequel des div internes a le focus.

La fonction suivante contourne cela et renvoie le nœud focalisé:

function active_node(){
  return window.getSelection().anchorNode;
}

Si vous préférez obtenir l'élément ciblé, utilisez:

function active_element(){
  var anchor = window.getSelection().anchorNode;
  if(anchor.nodeType == 3){
        return anchor.parentNode;
  }else if(anchor.nodeType == 1){
        return anchor;
  }
}
Nathan K
la source
3
Ce n'est pas vraiment un problème avec document.activeElement: les <div>éléments internes ne peuvent pas réellement recevoir le focus, comme vous pouvez le voir visuellement en définissant la :focuspseudo-classe sur quelque chose de visible (exemple: jsfiddle.net/4gasa1t2/1 ). Ce dont vous parlez, c'est lequel des intérieurs <div>contient la sélection ou le curseur, qui est un problème distinct.
Tim Down
6

J'ai trouvé l'extrait de code suivant utile pour essayer de déterminer quel élément a actuellement le focus. Copiez ce qui suit dans la console de votre navigateur, et chaque seconde il imprimera les détails de l'élément courant qui a le focus.

setInterval(function() { console.log(document.querySelector(":focus")); }, 1000);

N'hésitez pas à modifier le console.logpour vous déconnecter de quelque chose de différent afin de vous aider à localiser l'élément exact si l'impression de l'élément entier ne vous aide pas à localiser l'élément.

vegemite4me
la source
5

En lisant d'autres réponses et en essayant moi-même, cela document.activeElementvous donnera l'élément dont vous avez besoin dans la plupart des navigateurs.

Si vous avez un navigateur qui ne prend pas en charge document.activeElement si vous avez jQuery autour, vous devriez pouvoir le remplir sur tous les événements de focus avec quelque chose de très simple comme ceci (non testé car je n'ai pas de navigateur répondant à ces critères à portée de main ):

if (typeof document.activeElement === 'undefined') { // Check browser doesn't do it anyway
  $('*').live('focus', function () { // Attach to all focus events using .live()
    document.activeElement = this; // Set activeElement to the element that has been focussed
  });
}
rjmunro
la source
5

Si vous utilisez jQuery, vous pouvez l'utiliser pour savoir si un élément est actif:

$("input#id").is(":active");
Arne
la source
4

Avec dojo, vous pouvez utiliser dijit.getFocus ()

Daniel Hartmann
la source
3

Je mets juste cela ici pour donner la solution que j'ai finalement trouvée.

J'ai créé une propriété appelée document.activeInputArea, et utilisé l'addon HotKeys de jQuery pour intercepter les événements de clavier pour les touches fléchées, tabulation et entrée, et j'ai créé un gestionnaire d'événements pour cliquer sur les éléments d'entrée.

Ensuite, j'ai ajusté activeInputArea chaque fois que le focus changeait, afin que je puisse utiliser cette propriété pour savoir où j'étais.

Cependant, il est facile de gâcher cela, car si vous avez un bogue dans le système et que la mise au point n'est pas là où vous pensez, il est très difficile de restaurer la mise au point correcte.

Tony Peterson
la source