Closest ancestor matching selector using native DO

2019-01-14 12:34发布

Is anybody working on a jQuery.closest() equivalent in the DOM api?

Looks like the Selectors Level 2 draft adds matches() equivalent to jQuery.is(), so native closest should be much easier to write. Has adding closest() to Selectors come up?

6条回答
一纸荒年 Trace。
2楼-- · 2019-01-14 13:05

A little recursion will do the trick.

// get nearest parent element matching selector
var closest = (function() {
    var matchesSelector = el.matches || el.webkitMatchesSelector || el.mozMatchesSelector || el.msMatchesSelector;

    return function closest(el, selector) {
        return !el ? null :
        matchesSelector.call(el, selector) ? el : closest(el.parentElement, selector);
    };
})();
查看更多
手持菜刀,她持情操
3楼-- · 2019-01-14 13:11

Using element.closest() we can find Closest ancestor matching selector. This method takes selectors list as parameter and returns the closest ancestor. As per Rob's Comment this API will be available from chrome 41 and FF 35.

As explained in whatwg specs https://dom.spec.whatwg.org/#dom-element-closest

Example: The below HTML will show alert message "true"

<html>
    <body>
        <foo>
            <bar>
                <a id="a">
                    <b id="b">
                        <c id="c"></c>
                    </b>
                </a>
            </bar>
         </foo>
    <script>
        var a = document.getElementById('a');
        var b = document.getElementById('b');
        var c = document.getElementById('c');
        alert(c.closest("a, b")==b);
    </script>
    </body>
</html>
查看更多
倾城 Initia
4楼-- · 2019-01-14 13:14

Building off of Alnitak's answer. Here's the working current implementation with matchesSelector which is now matches in the DOM spec.

// get nearest parent element matching selector
function closest(el, selector) {
    var matchesSelector = el.matches || el.webkitMatchesSelector || el.mozMatchesSelector || el.msMatchesSelector;

    while (el) {
        if (matchesSelector.call(el, selector)) {
            break;
        }
        el = el.parentElement;
    }
    return el;
}

Browser support is great: http://caniuse.com/matchesselector

查看更多
劫难
5楼-- · 2019-01-14 13:14

This sounds like it ought to be pretty easy, given the matches function, although that's not widely supported yet:

function closest(elem, selector) {
    while (elem) {
        if (elem.matches(selector)) {
            return elem;
        } else {
            elem = elem.parentElement;
        }
    }
    return null;
}

The trouble is, the matches function isn't properly supported. As it's still a relatively new API it's available as webkitMatchesSelector in Chrome and Safari, and mozMatchesSelector in Firefox.

查看更多
萌系小妹纸
6楼-- · 2019-01-14 13:17

Seems like Chrome 40 will bring a native element.closest() method (http://blog.chromium.org/2014/12/chrome-40-beta-powerful-offline-and.html) specified here: https://dom.spec.whatwg.org/#dom-element-closest

查看更多
Summer. ? 凉城
7楼-- · 2019-01-14 13:18

Element.closest()

its support

Implementing such function with Element.matches() seems not optimal in terms of performance, cause apparently matches() will make a call to querySelectorAll() every time you test a parent, while only one call is sufficient for the job.

Here's a polyfill for closest() on MDN. Note a single call to querySelectorAll()

if (window.Element && !Element.prototype.closest) {
  Element.prototype.closest = 
  function(s) {
      var matches = (this.document || this.ownerDocument).querySelectorAll(s),
          i,
          el = this;
      do {
          i = matches.length;
          while (--i >= 0 && matches.item(i) !== el) {};
      } while ((i < 0) && (el = el.parentElement)); 
      return el;
  };
}

But bear in mind that function implemented like this will not work properly on unattached tree (detached from document.documentElement root)

//Element.prototype.closestTest = function(s){...as seen above...};

var detachedRoot = document.createElement("footer");
var child = detachedRoot.appendChild(document.createElement("div"));
detachedRoot.parentElement; //null

child.closestTest("footer"); //null

document.documentElement.append(detachedRoot);
child.closestTest("footer"); //<footer>   

Though closest() that is implemented in Firefox 51.0.1 seems to work fine with detached tree

document.documentElement.removeChild(detachedRoot);
child.closestTest("footer"); //null
child.closest("footer"); //<footer>
查看更多
登录 后发表回答