如何找出哪个DOM元素获得了焦点?

1622

我想在JavaScript中找出当前具有焦点的元素。我已经查看了DOM,但仍未找到所需内容。是否可以实现这一点,如何实现?

我寻找这个功能的原因是:

我试图让像箭头和enter这样的按键导航到一个输入元素的表格中。 Tab键现在可以工作,但似乎默认情况下enter键和箭头键无法操作。我已经设置好了按键处理部分,但现在需要在事件处理函数中弄清如何移动焦点。


4
这是一个书签小工具,它可以console.log输出当前具有焦点的元素:https://github.com/lingtalfi/where-is-focus-bookmarklet。 - ling
1
您可以使用 find-focused-element 包:https://www.npmjs.com/package/find-focused-element - Maxim Zhukov
1
对我来说,Ling提到的书签工具并没有起作用。 - GarfieldKlon
6
请参考这里展示了一个很好的document.activeElement的使用示例:https://dev59.com/_G025IYBdhLWcg3wqn0N#64176168 - Andrew
针对您的特定用例,您可以创建一个图表,通过查询该图表来确定基于键盘输入事件应该获得焦点的内容。nextFocus(element, keyboardEvent) // => 返回应接收焦点的元素 - bas080
22个回答

1903

使用 document.activeElement,它在所有主要浏览器中都受支持。

以前,在尝试找出哪个表单字段拥有焦点时,你是无法获取的。为了在旧版浏览器中模拟检测,需要为所有字段添加“focus”事件处理程序,并将最后一个聚焦的字段记录在变量中。为最后一个聚焦的字段添加“blur”处理程序以在模糊事件发生时清除变量。

如果需要移除 activeElement,可以使用 blur;document.activeElement.blur()。它会将 activeElement 更改为 body

相关链接:


69
不确定IE的情况,但FF和Safari都返回BODY元素。 - JW.
16
activeElement 实际上并不返回聚焦的元素。任何元素都可以获得焦点。如果一个文档有 4 个滚动区域 ('scrolldivs'),其中 0 或 1 个区域可以通过箭头键进行滚动。如果您单击其中一个,那么该区域就会获得焦点。如果您在所有区域外单击,则 body 元素获得焦点。如何找出哪个滚动区域具有焦点?http://jsfiddle.net/rudiedirkx/bC5ke/show/ (检查控制台) - Rudie
25
@Rudie, @Stewart: 我在你们的示例基础上建立了一个更复杂的游乐场:http://jsfiddle.net/mklement/72rTF/。你会发现,截至2012年末,唯一能够实际聚焦这样的 div 的主要浏览器是 Firefox 17,并且只能通过“tab”键聚焦到它。所有主要浏览器返回 document.activeElement 的元素类型都限制在与输入相关的元素上。如果没有这样的元素具有焦点,所有主要浏览器都会返回 body 元素 - 唯独 IE 9 返回 html 元素。 - mklement0
16
不确定是否有帮助,但您可以通过包含属性tabindex="0"使诸如div之类的元素接受键盘焦点。 - Marco Luglio
6
在访问 document.activeElement 时,应该用 try catch 包装起来,因为在某些情况下它可能会抛出异常(不仅限于 IE9)。请参阅 http://bugs.jquery.com/ticket/13393 和 http://bugs.jqueryui.com/ticket/8443。 - robocat
显示剩余10条评论

157

正如JW所说,你无法以浏览器无关的方式找到当前聚焦的元素。但如果你的应用程序仅限于IE(有些应用是这样的...),你可以按照以下方式找到它:

document.activeElement

看起来IE并没有彻底错了,这是HTML5草案的一部分,最新版本的Chrome、Safari和Firefox至少支持这一点。


5
FF3也是HTML5规范中“焦点管理”的一部分。 - Crescent Fresh
2
它在当前版本的Chrome和Opera(9.62)上运行正常。但是在OS X的Safari 3.2.3上无法运行,但在昨天发布的Safari 4中可以运行 :) - gregers
1
只有在使用键盘切换到元素时,它才能在Chrome(20)/ Safari(5.1.3)中正常工作。如果您单击它,则无论是jQuery:focus选择器还是document.activeElement都无法成功返回您单击的内容(分别返回未定义和文档主体元素)。PS我简直不敢相信这个线程已经两年了,仍然存在Webkit的回归问题,以及跳过链接不起作用的问题,但是添加实验性CSS3的工作非常多。我想我可能会建议我的家人和朋友回到Firefox。 - Dawn
当您单击以聚焦文本区域或其他输入时,document.activeElement是有效的。如果它是一个链接,则在单击时不会聚焦(在页面内或明显地其他情况下)。 - marksyzm
1
对于大多数元素,它返回“body”。基本上没什么用。 - Cornelius
显示剩余2条评论

97

6
这很好,但jQuery是如何实现的?document.activeElement是什么?我找到了这个:return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); - Harry Pehkonen
1
@HarryPehkonen 看起来常见的 document.querySelector( ':focus' ) 很好用,而且被 jQuery 使用。 - V.Volkov

52

document.activeElement现在是HTML5工作草案规范的一部分,但某些非主流/移动/旧版浏览器可能尚未支持。如果支持,可以退回到querySelector。值得一提的是,即使浏览器窗口没有焦点,document.activeElement也会返回document.body,如果没有元素被聚焦,则会出现这种情况。

以下代码将解决此问题并退回到querySelector,提供更好的支持。

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

需要注意的是这两种方法之间的性能差异。使用选择器查询文档将始终比访问activeElement属性慢得多。请参见此jsperf.com test


44

单独使用document.activeElement时,即使文档未获取焦点(因此文档中没有任何元素获得焦点!),它仍然可以返回一个元素。

您可能希望保留该行为,或者可能无关紧要(例如在keydown事件中),但是如果您需要知道确切的焦点位置,还可以检查document.hasFocus()

以下代码将给出当前获得焦点的元素(如果有),否则返回null

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

要检查一个具体的元素是否拥有焦点,更简单:

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

要检查是否有任何东西被聚焦,又变得更加复杂了:

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

鲁棒性注释:在代码中检查document.bodydocument.documentElement,是因为一些浏览器在没有聚焦时返回其中之一或null

这并没有考虑<body>(或者<html>)有一个tabIndex属性,因此实际上可以被聚焦。如果你正在编写一个库或其他东西,并希望它具有鲁棒性,那么你应该想办法处理这个问题。


下面是一个(笨重的引号)“单行”版的获取聚焦元素的版本,它在概念上更加复杂,因为你需要知道短路规则,而且你肯定无法将其放在一行上以使其可读。
我不会推荐使用这个版本。但是如果你是一个1337 hax0r,我也不知道...... 它在那里。
如果您不介意在某些情况下获得false,则可以删除|| null部分。(如果document.activeElementnull,你仍然可以获得null):

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

要检查特定元素是否具有焦点,你可以使用事件进行替代,但这种方法需要设置(和可能的拆卸),而且重要的是,它假设了一个初始状态:

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

您可以通过使用非事件方式来修复初始状态假设,但那样的话您最好直接使用它。


22

document.activeElement 可能会在没有焦点元素时默认为 <body> 元素。此外,如果某个元素被聚焦并且浏览器窗口失去焦点,则 activeElement 将继续保持该元素。

如果这两种行为都不符合要求,请考虑使用基于 CSS 的方法:document.querySelector(':focus')


很酷,是的,在我的情况下,你的方法绝对有意义。如果没有任何一个可聚焦元素(比如一些文本或图片,我不关心它们),我可以使用'tabindex="-1"'来设置它们,这样document.querySelector(':focus')就会返回null。 - Manfred
请查看我的答案,避免使用 querySelector:https://dev59.com/K3RB5IYBdhLWcg3w1Kle#40873560 - 1j01

15

我发现以下代码片段在确定当前拥有焦点的元素时非常有用。将以下内容复制到您浏览器的控制台中,每秒钟它都会打印出当前拥有焦点的元素的详细信息。

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

随意修改console.log,将其日志记录为其他内容,以帮助您定位确切的元素,如果整个元素的打印不能帮助您确定该元素,请这样做。


1
太好了...实际上这应该是被接受的答案。 - shuklendu

12

我喜欢Joel S使用的方法,但我也喜欢document.activeElement的简单性。我使用了jQuery并将两者结合起来。不支持document.activeElement的旧版浏览器将使用jQuery.data()存储'hasFocus'的值。较新的浏览器将使用document.activeElement。我��为document.activeElement的性能会更好。

(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);

3
这能否被 @William Denniss 的 $("*:focus") 替换? - Pylinux
我想这是可能的。我很久以前写了这个,现在已经过去5年了,从未有理由重新考虑更好的解决方案。试试看吧!我也可能会这样做。我们网站上少了一个插件! :) - Jason

12
我在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);

如果给定元素调用了startFocusTracking(),那么您可以使用el.hasFocus()来检查元素是否具有焦点。


9
JQuery目前支持:focus伪类。如果在JQuery文档中查找,可以在“选择器”下检查,其中会指向W3C CSS docs。我已经在Chrome、FF和IE 7+上进行了测试。请注意,在IE中要使其工作,必须在html页面上存在<!DOCTYPE...。以下是一个示例,假设您已经给具有焦点的元素分配了一个id:
$(":focus").each(function() {
  alert($(this).attr("id") + " has focus!");
});

1
你应该(总是?)使用this.id而不是$(this).attr('id'),或者至少(当你已经有了jQuery对象时)使用$(this)[0].id。在这个级别上,原生JavaScript更快、更高效。在这种情况下可能不会注意到,但在整个系统范围内,你会注意到差异。 - Martijn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接