Como descubro qual elemento DOM está em foco?

1309

Gostaria de descobrir, em JavaScript, qual elemento atualmente tem foco. Estive pesquisando o DOM e ainda não encontrei o que preciso. Existe uma maneira de fazer isso e como?

A razão pela qual eu estava procurando isso:

Estou tentando criar teclas como as setas e enternavegar por uma tabela de elementos de entrada. A guia funciona agora, mas digite, e as setas, por padrão, não parecem. Eu tenho a parte de manipulação de chaves configurada, mas agora preciso descobrir como mover o foco nas funções de manipulação de eventos.

Tony Peterson
fonte
2
Aqui está um bookmarklet que console.log o elemento com foco: github.com/lingtalfi/where-is-focus-bookmarklet
ling
Você pode usar o find-focused-elementpacote: npmjs.com/package/find-focused-element
Maxim Zhukov

Respostas:

1533

Use document.activeElement, é suportado em todos os principais navegadores.

Anteriormente, se você estivesse tentando descobrir qual campo do formulário tem foco, não poderia. Para emular a detecção em navegadores antigos, adicione um manipulador de eventos "focus" a todos os campos e registre o último campo focado em uma variável. Adicione um manipulador de "desfoque" para limpar a variável em um evento de desfoque para o último campo focado.

Se você precisar remover o, activeElementpoderá usar o desfoque; document.activeElement.blur(). Mudará o activeElementpara body.

Links Relacionados:

Dave Jarvis
fonte
53
Não tenho certeza sobre o IE, mas o FF e o Safari retornam o elemento BODY.
JW.
10
activeElementna verdade, não retorna o elemento focado. Qualquer elemento pode ter foco. Se um documento tiver 4 'scrolldivs', 0 ou 1 dessas divs poderá ser rolado pelas teclas de seta. Se você clicar em um deles, essa div será focada. Se você clicar fora de tudo, o corpo estará focado. Como você descobre qual scrolldiv está focado? jsfiddle.net/rudiedirkx/bC5ke/show (console de verificação)
Rudie
18
@ Rudie, @ Stewart: Eu desenvolvi seu violino para criar um playground mais elaborado: jsfiddle.net/mklement/72rTF . Você verá que o único navegador principal (a partir do final de 2012) que pode realmente se concentrar nesse tipo divé o Firefox 17, e apenas tabulando -o. Os tipos de elementos pelos quais TODOS os principais navegadores retornam document.activeElementestão restritos a elementos relacionados à entrada . Se nenhum desses elementos tiver o foco, todos os principais navegadores retornarão o bodyelemento - exceto o IE 9, que retorna o htmlelemento.
mklement0
10
Não tenho certeza se isso ajuda, mas você pode fazer um elemento como um div receber o foco do teclado, incluindo o atributo tabindex = "0"
Marco Luglio
4
Qualquer acesso ao document.activeElementdeve ser agrupado de uma maneira try catchque, em algumas circunstâncias, pode gerar uma exceção (não apenas o IE9 AFAIK). Veja bugs.jquery.com/ticket/13393 e bugs.jqueryui.com/ticket/8443
robocat
127

Como dito pela JW, não é possível encontrar o elemento atual focado, pelo menos de maneira independente do navegador. Mas se o seu aplicativo for apenas o IE (alguns são ...), você poderá encontrá-lo da seguinte maneira:

document.activeElement

EDIT: Parece que o IE não tinha tudo errado, afinal, isso faz parte do rascunho do HTML5 e parece ser suportado pela versão mais recente do Chrome, Safari e Firefox, pelo menos.

Wookai
fonte
5
FF3 também. Na verdade, isso faz parte das especificações do HTML5 relacionadas ao "gerenciamento de foco".
Crescent Fresh
2
Ele funciona na versão atual do Chrome e Opera (9.62). Não funciona no Safari 3.2.3 no Mac OS X, mas funciona no Safari 4, que foi lançado ontem :)
Gregers
ainda é o mesmo para o chrome 19: S
Sebas
1
Funciona apenas no chrome (20) / safari (5.1.3) quando você usa o teclado para tabular o elemento. Se você clicar nele, nem o seletor jquery: focus nem o document.activeElement conseguirão retornar o que você clicou (retornando o elemento indefinido e o corpo do documento, respectivamente). PS: Eu não acredito que esse segmento tenha 2 anos e ainda existem problemas de regressão no webkit, junto com aquele em que os links ignorados não funcionam, mas muito trabalho está sendo feito com a adição de css3 experimental. Acho que posso voltar a recomendar o firefox para minha família e amigos.
Amanhecer
^^ document.activeElement é bom quando você clica para focar em uma área de texto ou outra entrada. Se for um link, ele não será focado quando clicado (na página ou obviamente de outra forma).
marksyzm
84

Se você pode usar o jQuery, agora ele suporta: focus, apenas verifique se você está usando a versão 1.6+.

Esta declaração fornecerá o elemento atualmente focado.

$(":focus")

De: como selecionar um elemento que se concentre nele com o jQuery

William Denniss
fonte
4
Isso é bom, mas como o jQuery faz isso? document.activeElement? Eu encontrei o seguinte: return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
Harry Pehkonen 13/10
46

document.activeElementagora faz parte da especificação de rascunho de trabalho do HTML5 , mas ainda pode não ser suportada em alguns navegadores não principais / móveis / mais antigos. Você pode voltar a querySelector(se houver suporte). Também vale a pena mencionar que document.activeElementretornará document.bodyse nenhum elemento estiver focado - mesmo que a janela do navegador não tenha foco.

O código a seguir resolverá esse problema e voltará a querySelectoroferecer um suporte um pouco melhor.

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

Outro aspecto a ser observado é a diferença de desempenho entre esses dois métodos. Consultar o documento com seletores sempre será muito mais lento que acessar a activeElementpropriedade. Veja este teste do jsperf.com .

Andy E
fonte
21

Por si só, document.activeElementainda pode retornar um elemento se o documento não estiver focado (e, portanto, nada no documento estiver focado!)

Você pode querer esse comportamento, ou ele pode não importar (por exemplo, dentro de um keydownevento), mas se precisar saber que algo está realmente focado, você pode verificar adicionalmente document.hasFocus().

A seguir, você fornece o elemento focado, se houver um, ou então null.

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

Para verificar se um elemento específico tem foco, é mais simples:

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

Para verificar se alguma coisa está focada, é mais complexo novamente:

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

Robustez Nota : no código em que ele faz a verificação document.bodye document.documentElement, isso ocorre porque alguns navegadores retornam um desses ou nullquando nada está focado.

Não explica se o <body>(ou talvez <html>) tinha um tabIndexatributo e, portanto, poderia realmente ser focado . Se você estiver escrevendo uma biblioteca ou algo assim e quiser que ela seja robusta, provavelmente deve lidar com isso de alguma forma.


Aqui está uma ( pesados airquotes) versão "one-liner" de conseguir o elemento focalizado, que é conceitualmente mais complicado porque você tem que saber sobre curto-circuito, e você sabe, é óbvio que não se encaixa em uma linha, supondo que você quer que seja legível.
Eu não vou recomendar este. Mas se você é um 1337 hax0r, idk ... está lá.
Você também pode remover a || nullpeça se não se importar falseem receber alguns casos. (Você ainda pode obter nullse document.activeElementfor null):

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

Para verificar se um elemento específico está focado, como alternativa, você pode usar eventos, mas dessa maneira requer configuração (e potencialmente desmontagem) e, o que é mais importante, assume um estado inicial :

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

Você pode corrigir a suposição de estado inicial usando a maneira não-evento, mas também pode usá-la.

1j01
fonte
15

document.activeElementpode usar como padrão o <body>elemento se nenhum elemento focalizável estiver em foco. Além disso, se um elemento estiver focado e a janela do navegador estiver embaçada, activeElementcontinuará mantendo o elemento focado.

Se qualquer um destes dois comportamentos não são desejáveis, considere uma abordagem baseada em CSS: document.querySelector( ':focus' ).

Nate Whittaker
fonte
Legal, sim, no meu caso, sua abordagem fazia absolutamente sentido. Posso definir meus elementos focalizáveis ​​com 'tabindex = "- 1"', se nenhum deles tiver foco (digamos, algum texto ou imagem que não me importe com) o documento.querySelector (': focus') retorna nulo.
Manfred
Veja minha resposta para evitar o uso querySelector: stackoverflow.com/a/40873560/2624876
1j01 29/11
10

Gostei da abordagem usada por Joel S, mas também adoro a simplicidade de document.activeElement. Eu usei o jQuery e combinei os dois. Navegadores mais antigos que não suportam document.activeElementusarão jQuery.data()para armazenar o valor de 'hasFocus'. Navegadores mais recentes usarão document.activeElement. Presumo que document.activeElementterá melhor desempenho.

(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
fonte
3
Isso poderia ser substituído por @William Denniss's $("*:focus")?
Pylinux 30/07/2015
Suponho que sim. Escrevi isso há muito tempo e nunca tive um motivo para revisitar uma solução melhor agora, cinco anos depois. Experimente! Eu poderia fazer o mesmo. Eu menos plugin no nosso site! :)
Jason
10

Um pequeno ajudante que usei para esses fins no 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);

Dessa forma, você pode verificar se o elemento está focado, el.hasFocus()desde que startFocusTracking()tenha sido chamado no elemento especificado.

Joel S
fonte
7

O JQuery suporta a :focuspseudo-classe a partir da atual. Se você está procurando na documentação do JQuery, verifique em "Seletores", onde ele aponta os documentos CSS do W3C . Eu testei com Chrome, FF e IE 7+. Observe que, para que ele funcione no IE, <!DOCTYPE...deve existir na página html. Aqui está um exemplo, assumindo que você atribuiu um ID ao elemento que tem o foco:

$(":focus").each(function() {
  alert($(this).attr("id") + " has focus!");
});
DeezCashews
fonte
1
Você deve (sempre?) Usar em this.idvez de $(this).attr('id'), ou pelo menos (quando você já possui seu objeto jQuery) $(this)[0].id. Javascript nativo neste nível é MUITO mais rápido e mais eficiente. Pode não ser perceptível neste caso, mas em todo o sistema você notará uma diferença.
27515 Martijn
7

Se você deseja obter um objeto que é instância Element, você deve usar document.activeElement, mas se você deseja obter um objeto que é instância Text, deve usar document.getSelection().focusNode.

Espero que ajude.

rplaurindo
fonte
Melhor de que maneira?
1j01 29/11/16
Abra o inspetor do navegador, clique em qualquer lugar da página, passe por ele document.getSelection().focusNode.parentElemente toque em Enter. Depois disso, passe document.activeElemente faça o mesmo. ;)
rplaurindo
Com esta caixa de comentário focado, document.activeElementdá o <textarea>passo que document.getSelection().focusNodedá o <td>que contém o <textarea>(e document.getSelection().focusNode.parentElementdá o <tr>que contém a <td>)
1j01
Desculpe minhas desculpas. Não expliquei bem. Se você deseja obter um objeto que é instância Element, é necessário usá-lo document.activeElement, mas se deseja obter um objeto que é instância Text, é necessário usá-lo document.getSelection().focusNode. Por favor, teste novamente. Espero ter ajudado.
rplaurindo
2
A pergunta está perguntando para qual elemento atualmente tem foco. E focusNodetambém não é garantido que seja um nó de texto.
1j01
6

Existem problemas em potencial com o uso de document.activeElement. Considerar:

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

Se o usuário se concentrar em uma div interna, document.activeElement ainda fará referência à div externa. Você não pode usar document.activeElement para determinar qual das divs internas tem foco.

A seguinte função contorna isso e retorna o nó focado:

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

Se você preferir obter o elemento focado, use:

function active_element(){
  var anchor = window.getSelection().anchorNode;
  if(anchor.nodeType == 3){
        return anchor.parentNode;
  }else if(anchor.nodeType == 1){
        return anchor;
  }
}
Nathan K
fonte
3
Isso não é realmente um problema document.activeElement: os <div>elementos internos realmente não podem receber o foco, como você pode ver visualmente, definindo a :focuspseudo-classe como algo visível (exemplo: jsfiddle.net/4gasa1t2/1 ). O que você está falando é qual dos <div>s internos contém a seleção ou o sinal de intercalação, que é uma questão separada.
Tim
6

Eu achei o seguinte snippet útil ao tentar determinar qual elemento atualmente tem foco. Copie o seguinte no console do seu navegador e, a cada segundo, ele imprimirá os detalhes do elemento atual em foco.

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

Sinta-se à vontade para modificar o console.logitem para fazer logout de algo diferente para ajudá-lo a identificar o elemento exato se a impressão de todo o elemento não o ajudar a identificar o elemento.

vegemite4me
fonte
5

Lendo outras respostas e tentando a mim mesmo, parece document.activeElementque você fornecerá o elemento necessário na maioria dos navegadores.

Se você possui um navegador que não suporta document.activeElement, se você tem jQuery por perto, deve poder preenchê-lo em todos os eventos de foco com algo muito simples como este (não testado, pois eu não tenho um navegador que atenda a esses critérios à mão) ):

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
fonte
5

Se você estiver usando jQuery, poderá usá-lo para descobrir se um elemento está ativo:

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

Com o dojo, você pode usar dijit.getFocus ()

Daniel Hartmann
fonte
3

Basta colocar isso aqui para dar a solução que eu acabei encontrando.

Criei uma propriedade chamada document.activeInputArea e usei o complemento HotKeys do jQuery para capturar eventos de teclado para teclas de seta, tab e enter, e criei um manipulador de eventos para clicar nos elementos de entrada.

Depois, eu ajustava o activeInputArea toda vez que o foco era alterado, para que eu pudesse usar essa propriedade para descobrir onde estava.

É fácil estragar tudo, porque se você tem um erro no sistema e o foco não está onde você pensa, é muito difícil restaurar o foco correto.

Tony Peterson
fonte