knowledge base

マークアップ/フロントエンドエンジニアのWEB制作における備忘録です。平日はWEB屋、休日は社会人劇団の主宰・劇作家をしています。

ワイルドカードで要素を取得すると親要素をさかのぼり続ける

イベントのバブリングに注意

たとえば、ページ内の任意を場所をクリックしたとき、クリックした場所にある要素のタグ名を出力するという事例を想定します。

まずタグ名は次のように取得可能です。

var target  = /* DOMオブジェクト */
var tagName = target.tagName;

DOMオブジェクトにはほかにもinnerHTMLやoffsetTopなど様々な値やメソッドを持っています。

jQueryオブジェクトならば、次のようにして求めます。

var $target  = /* jQueryオブジェクト */
var tagName  = $target.prop('tagName');

上記を踏まえて、コードを書いてゆきます。

HTMLの中にあるすべての要素が対象となるので、JSはこのように書きます。

$('html').find('*').on('click',function(e){
    console.log(this.tagName);
});

※先述しましたが、$(this).get(0).tagName や $(this).prop('tagName') でもタグ名を取得可です。

 

それでは、コンソールを見てみましょう。

f:id:ShinImae:20151216173107j:plain

意図したものとは異なる挙動をしています。

親要素をさかのぼりつづけ、body要素までたどりついてしまったようです。

なぜこのようになっているのでしょうか。

実は今回クリックしたのは、下記HTMLの一番深い「click me」というテキストをもつspanでした。

<body>
  <div class="wrapper">
    <section>
      <article>
        <dl>
          <dt> Lorem ipsum </dt>
          <dd>
            <span> click me </span>
            <span> click me </span>
            <span> click me </span>
          </dd>
        </dl>
      </article>
    </section>
  </div>
</body>

この現象の正体はバブリングといいます。

実はイベントは、当てはまる要素すべてにおいて、親要素へと伝播(propagation)してゆく性質をもっています。

この場合はHTML内のすべての要素が対象だったため、bodyまで伝播していきました。

これをspanだけ出力させるためには、伝播を止めなければいけません。

先ほどのソースに一行だけ追記します。

$('html').find('*').on('click',function(e){
    e.stopPropagation();
    console.log(this.tagName);
});

これで、最も深い階層にある要素(つまりクリックされた要素)のタグ名だけが出力されます。

f:id:ShinImae:20151216173118j:plain

余談

実はaddEventListener()にはuseCaptureという名の第三引数があり、trueを指定するとバブリングを抑止します。

ただ、html要素がもつすべての子要素にアクセスするコードを書くと恐ろしく冗長になるので、ここでは割愛いたします。

ちなみにjQueryに頼ることなく、ワイルドカード的にDOM要素を取得するには下記の方法で可能だそうです。

document.getElementByTagName('*');

ただし、IE8以下ではコメントノードまで含んでしまうため、注意が必要です。