不使用jQuery查找最接近的元素

95

我正在尝试找到最接近的特定标签元素,而不使用jquery。当我单击一个<th>时,我想要访问该表格的<tbody>。有什么建议吗?我读过offset,但并没有真正理解它。我应该只使用:

假设th已经设置为点击的th元素

th.offsetParent.getElementsByTagName('tbody')[0]

1
如果你发现必须开始遍历DOM,这是一个额外的kb归因于jQuery的实例,这将是值得的。 - Kevin Bowersox
1
尝试使用 parentNode:https://developer.mozilla.org/zh-CN/docs/Web/API/Node.parentNode - Ivan Chernykh
2
我认为这是一个非常重要和有效的问题。没有理由进行负面评价。 - user1596138
对于非IE浏览器来说,el.closest('tbody')方法可以用于查找最近的祖先<tbody>元素。以下是更详细的解释和polyfill实现。 - oriadam
13个回答

137

非常简单:

el.closest('tbody')

除IE外,所有浏览器都支持此功能。
更新:Edge现在也支持它了。

无需jQuery。 此外,将jQuery的$(this).closest('tbody')替换为$(this.closest('tbody'))将提高性能,特别是当未找到元素时。

IE的Polyfill:

if (!Element.prototype.matches) Element.prototype.matches = Element.prototype.msMatchesSelector;
if (!Element.prototype.closest) Element.prototype.closest = function (selector) {
    var el = this;
    while (el) {
        if (el.matches(selector)) {
            return el;
        }
        el = el.parentElement;
    }
};
请查看:

请注意,如果未找到元素,则没有return ,在最近的元素未找到时会有效地返回undefined

更多详情请参见: https://developer.mozilla.org/en-US/docs/Web/API/Element/closest


6
为什么这个答案在页面底部?应该在顶部。非常感谢。 - Julien Le Coupanec
最接近的是jQuery。但是OP说不要用jQuery。 - Steve Moretz
2
@stevemoretz。现在最接近原生JavaScript。 - Louise Eggleton
@LouiseEggleton 是的,抱歉。 - Steve Moretz
1
那被称为最佳答案! - Tahazzot

71

虽然来晚了一点,但这样应该可以 解决

function closest(el, selector) {
    var matchesFn;

    // find vendor prefix
    ['matches','webkitMatchesSelector','mozMatchesSelector','msMatchesSelector','oMatchesSelector'].some(function(fn) {
        if (typeof document.body[fn] == 'function') {
            matchesFn = fn;
            return true;
        }
        return false;
    })

    var parent;

    // traverse parents
    while (el) {
        parent = el.parentElement;
        if (parent && parent[matchesFn](selector)) {
            return parent;
        }
        el = parent;
    }

    return null;
}

2
非常好的答案,非常有效。MDN还为element.closest()提供了一个polyfill。Chrome在当前版本中包含了element.matches(),因此不需要前缀。我刚刚将这个功能添加到我正在开发的应用程序Clibu中使用的库中。 - nevf
2
我已经更改了代码,以便它还测试了jQuery.closest()和Element.closest()中的元素elfor (var parent = el; parent !== null && !parent[matchesFn](selector); parent = el.parentElement) { el = parent; } return parent; - nevf
1
这段代码没问题,但是缺少一个分号,并且在全局范围内定义了 parent - Steven Lu
1
可能值得一提的是:https://developer.mozilla.org/en-US/docs/Web/API/Element/closest 最接近的本地方法,虽然支持度有点低:Chrome41,FF35,IE-nope,Opera28,Safari9 - Michiel D
关于这个问题,需要注意的是,当在IE中遍历SVG时,你可能需要使用el.parentNode,否则代码会出错。 - smcka
显示剩余3条评论

22

以下是在不使用 jQuery 的情况下获取最接近指定标签名的元素的方法:

function getClosest(el, tag) {
  // this is necessary since nodeName is always in upper case
  tag = tag.toUpperCase();
  do {
    if (el.nodeName === tag) {
      // tag name is found! let's return it. :)
      return el;
    }
  } while (el = el.parentNode);

  // not found :(
  return null;
}

getClosest(th, 'tbody');

2
我不相信这会起作用。它只检查DOM树。th->thead->table从不考虑兄弟节点。 - hunterc
2
你应该在问题中提到具体的情况。 - Joon
2
看这里。该函数遍历parentNodes以查找使用提供的标签的最近父节点(甚至通过扩展)。这不会沿着文档树“向下”遍历,只会“向上”遍历。 普通用户可能会将“最近”的节点视为最近的兄弟节点。他只是不知道他需要的术语。 - user1596138
1
公平地说,@Jhawins的“最接近”的定义是jQuery所称的“最接近”。这不是一个jQuery问题。我无法证明为什么jQuery认为最接近意味着最接近的祖先,但更合理的定义应该是最接近的元素,无论是父元素、前一个兄弟元素、后一个兄弟元素等等。无论找到目标元素的“最接近”是什么。 - ryandlf
1
@ryandlf 但是如果你在同一“距离”上有父级和兄弟节点呢?jQuery的定义很清楚,它最多只会返回一个匹配项。 - mtone
显示剩余5条评论

9
存在一种标准化的函数可以实现此功能:Element.closest。除IE11外,大多数浏览器都支持它(caniuse.com提供了详细信息)。MDN文档中还包括了一个polyfill,以防需要兼容旧版浏览器。
例如,要找到最接近的`tbody`父元素,可以使用以下代码:
th.closest('tbody');

如果您想自己编写函数-这是我想到的:

function findClosestParent (startElement, fn) {
  var parent = startElement.parentElement;
  if (!parent) return undefined;
  return fn(parent) ? parent : findClosestParent(parent, fn);
}

为了找到最近的父元素,你可以按照以下方式使用标签名称:
findClosestParent(x, element => return element.tagName === "SECTION");

6
function closest(el, sel) {
    if (el != null)
        return el.matches(sel) ? el 
            : (el.querySelector(sel) 
                || closest(el.parentNode, sel));
}

这个解决方案使用了HTML5规范中的一些新特性,如果在老旧/不兼容的浏览器(即:Internet Explorer)上使用,将需要一个polyfill。

Element.prototype.matches = (Element.prototype.matches || Element.prototype.mozMatchesSelector 
    || Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector 
    || Element.prototype.webkitMatchesSelector || Element.prototype.webkitMatchesSelector);

它总是跳到第一个表格的顶部,而不是最近的表格...请参见此链接http://jsfiddle.net/guuy5kof/ - Balachandran
好发现!已修复,请看这里http://jsfiddle.net/c2pqc2x0/ 请像老板一样给我点赞 :) - Matthew James Davis
是的,我会做...你检查了你的fiddle结果吗?它显示错误..matches未定义。 - Balachandran
对我来说很好用,你在用哪个浏览器?这在旧版浏览器中不支持。 - Matthew James Davis
嘿,兄弟,清除那个踩的评价,你觉得怎么样? - Matthew James Davis

3
这里是我正在使用的简单函数:-
function closest(el, selector) {
    var matches = el.webkitMatchesSelector ? 'webkitMatchesSelector' : (el.msMatchesSelector ? 'msMatchesSelector' : 'matches');

    while (el.parentElement) {
        if (el[matches](selector)) return el;

        el = el.parentElement;
    }

    return null;
}

3

概述:

为了查找特定的祖先,我们可以使用以下方法:

Element.closest();

该函数以CSS选择器字符串作为参数。然后返回与传递给参数的CSS选择器匹配的当前元素(或元素本身)的最近祖先。如果没有祖先,则返回null

示例:

const child = document.querySelector('.child');
// select the child

console.dir(child.closest('.parent').className);
// check if there is any ancestor called parent
<div class="parent">
  <div></div>
  <div>
    <div></div>
    <div class="child"></div>
  </div>
</div>


3

扩展@SalmanPK的答案:

它将允许使用节点作为选择器,特别是在处理诸如鼠标悬停之类的事件时非常有用。

function closest(el, selector) {
    if (typeof selector === 'string') {
        matches = el.webkitMatchesSelector ? 'webkitMatchesSelector' : (el.msMatchesSelector ? 'msMatchesSelector' : 'matches');
        while (el.parentElement) {
            if (el[matches](selector)) {
                return el
            };
            el = el.parentElement;
        }
    } else {
        while (el.parentElement) {
            if (el === selector) {
                return el
            };
            el = el.parentElement;
        }
    }

    return null;
}

1
获取包含类、ID、数据属性或标签的最近DOM元素。包括该元素本身。支持至IE6。
var getClosest = function (elem, selector) {

    var firstChar = selector.charAt(0);

    // Get closest match
    for ( ; elem && elem !== document; elem = elem.parentNode ) {

        // If selector is a class
        if ( firstChar === '.' ) {
            if ( elem.classList.contains( selector.substr(1) ) ) {
                return elem;
            }
        }

        // If selector is an ID
        if ( firstChar === '#' ) {
            if ( elem.id === selector.substr(1) ) {
                return elem;
            }
        } 

        // If selector is a data attribute
        if ( firstChar === '[' ) {
            if ( elem.hasAttribute( selector.substr(1, selector.length - 2) ) ) {
                return elem;
            }
        }

        // If selector is a tag
        if ( elem.tagName.toLowerCase() === selector ) {
            return elem;
        }

    }

    return false;

};

var elem = document.querySelector('#some-element');
var closest = getClosest(elem, '.some-class');
var closestLink = getClosest(elem, 'a');
var closestExcludingElement = getClosest(elem.parentNode, '.some-class');

为什么不使用switch语句来代替所有的IF条件语句,针对firstChar呢? - Rafael Herscovici
为什么使用模糊的“for”循环? - Rafael Herscovici

1
找到最近的Elements子节点。
closest:function(el, selector,userMatchFn) {
var matchesFn;

// find vendor prefix
['matches','webkitMatchesSelector','mozMatchesSelector','msMatchesSelector','oMatchesSelector'].some(function(fn) {
    if (typeof document.body[fn] == 'function') {
        matchesFn = fn;
        return true;
    }
    return false;
});
function findInChilds(el){
    if(!el) return false;
    if(el && el[matchesFn] && el[matchesFn](selector)

    && userMatchFn(el) ) return [el];
    var resultAsArr=[];
    if(el.childNodes && el.childNodes.length){
        for(var i=0;i< el.childNodes.length;i++)
        {
             var child=el.childNodes[i];
             var resultForChild=findInChilds(child);
            if(resultForChild instanceof Array){
                for(var j=0;j<resultForChild.length;j++)
                {
                    resultAsArr.push(resultForChild[j]);
                }
            } 
        }

    }
    return resultAsArr.length?resultAsArr: false;
}

var parent;
if(!userMatchFn || arguments.length==2) userMatchFn=function(){return true;}
while (el) {
    parent = el.parentElement;
    result=findInChilds(parent);
    if (result)     return result;

    el = parent;
}

return null;

}


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