elementFromPoint() という API があった

2018年の初記事です。今年も宜しくお願い致します。

さて、まだまだ DOM 操作には知らないことが沢山…。


document.elementFromPoint() という API があった。ページ左上からの X・Y 座標値を与えると、その位置にある要素を取得できるというモノだ。

検証用のデモページを作った。

マウスカーソルを任意の要素に重ねると、その要素を取得して CSS セレクタ表現をポインタ付近に表示する。

作り方

予め CSS で html 要素と body 要素を画面いっぱいに広げておく。必須ではないが、コンテンツが少ないと body 要素の下の html 要素が見えてしまうので、body 要素を広げて置いておくと良いかと。

html,
body {
  margin: 0;
  padding: 0; /* 基本は margin だけで十分だけど */
  width: 100%;
  height: 100%;
}

サンプルページで作ったコードは以下のとおり。

document.addEventListener('DOMContentLoaded', (loadEvent) => {
  // ポインタ要素を生成しておく
  const pointer = document.createElement('div');
  // position: fixed を使うと完全にフローして画面外にはみ出てもスクロールバーが表示されたりしなくなる
  pointer.setAttribute('style', 'display: inline-block; position: fixed; top: -1000px; border: 1px solid #ccc; border-radius: 4px; padding: 10px; background: #fff; opacity: .7;');
  // スクロールバーは html 要素が出すので body 要素配下にいるポインタ要素はスクロールバーの下に隠れる
  document.body.appendChild(pointer);
  
  // 直前にポイントした要素を控えておく
  let previousElement;
  
  // 引数の要素の CSS セレクタ表現を作る
  const getQuerySelector = (elem) => {
    const tagName = elem.tagName.toLowerCase();
    let querySelector = tagName;
    if(elem.id.trim()) {
      querySelector += '#' + elem.id.trim();
    }
    if(elem.className.trim()) {
      querySelector += '.' + Array.apply(null, elem.classList).join('.');
    }
    return querySelector;
  };
  
  // ポインタの位置にある要素を取得し CSS セレクタ表現を表示する
  const getElementFromPoint = (event) => {
    // ポインタの位置を設定する
    pointer.style.top = (event.y + 10) + 'px';
    pointer.style.left = (event.x + 5) + 'px';
    
    // 指定の座標位置にある要素を取得する
    const elementFromPoint = document.elementFromPoint(event.x, event.y);
    // 要素がないか直前の要素と同じなら中止
    if(!elementFromPoint || elementFromPoint === previousElement) {
      return;
    }
    // 直前の要素として控えておく
    previousElement = elementFromPoint;
    
    // 当該要素の CSS セレクタ表現を作る
    let parentElement = elementFromPoint;
    let tagName = elementFromPoint.tagName.toLowerCase();
    let querySelector = getQuerySelector(elementFromPoint);
    // 親要素に遡って CSS セレクタ表現を作る
    while(tagName !== 'html') {
      parentElement = parentElement.parentElement;
      tagName = parentElement.tagName.toLowerCase();
      querySelector = getQuerySelector(parentElement) + ' > ' + querySelector;
    }
    
    // ポインタ要素に表示する
    pointer.textContent = querySelector;
  }
  
  document.addEventListener('mousemove', getElementFromPoint); // マウス操作中
  document.addEventListener('click'    , getElementFromPoint); // マウスボタン押下時
});

マウスカーソルに追随するポインタとか、選択した要素の CSS セレクタ表現を作る関数とか、余計な処理が混じっているので、肝心の箇所だけ抜き出してみる。

// クリックした座標位置の要素を取得する
document.addEventListener('click', (event) => {
  const clickedElement = document.elementFromPoint(event.x, event.y);
});

なんとコレだけ。

要素が絶対配置とかで重なっている時は、一番上に表示されている要素が取得できるようだ。

Dragula というドラッグ処理を扱えるライブラリで見かけた。