DOM Traversal の出番

({
    observe: function (doc) {
        this.iterator = doc.createNodeIterator(doc, NodeFilter.SHOW_ELEMENT, this, true);
        doc.addEventListener('keydown', this, false);
    },

    acceptNode: function (node) {
        if ('H1 H2 H3 H4 H5 H6'.indexOf(node.tagName) >= 0) {
            return NodeFilter.FILTER_ACCEPT;
        }
        return NodeFilter.FILTER_SKIP;
    },

    handleEvent: function (evt) {
        var CTRL = evt.ctrlKey, SHIFT = evt.shiftKey, ALT = evt.altKey,
            META = evt.metaKey, code = evt.keyCode, node;

        if (code === 40 && ALT && !CTRL && !SHIFT && !META) {
            if ((node = this.iterator.nextNode())) {
                node.scrollIntoView();
                evt.preventDefault();
            }
        } else if (code === 38 && ALT && !CTRL && !SHIFT && !META) {
            if ((node = this.iterator.previousNode())) {
                node.scrollIntoView();
                evt.preventDefault();
            }
        }
    }
}).observe(document);

acceptNode は CSS セレクタから作れるようにして、イベントとアクションの対応を関数内に書かないようにしよう(イベントセレクタ? イベントフィルタ? のようなものを作る)。

メモ:

  • Opera 12.02 では「Alt + 修飾キー以外のキー」という組み合わせでは keydown イベントが発生しない。
  • Chrome 21 では NodeFilter::acceptNode の this 値が window になっている(今回のコードでは問題にならない)。

// そういえばJavaScriptリファレンス 第6版の索引だけ立ち読みしたら、TreeWalker や NodeIterator が載っていないようだった。IE もバージョン 9 でようやく使えるようになったのに。