JavaScript DOM のスニペット集

このページは、JavaScript の DOM のスニペットなどをまとめる予定のページです。

目次

注意

  • コードのライセンスは CC0 (クレジット表示不要、改変可、商用可) です。
  • できるだけ素の IE11 以上で使用できるように調整しています。(他の想定ブラウザは Edge, Chrome, Firefox)

スニペット

matches()

// polyfill (https://developer.mozilla.org/ja/docs/Web/API/Element/matches)
if (!Element.prototype.matches) Element.prototype.matches = Element.prototype.msMatchesSelector;
document.addEventListener('click', function(e){
    if (e.target.matches('.btn, .btn *')) {
        // .btn (.btnそのものか、.btn内の要素) がクリックされたときの処理
    }
});
  • matches() は 対象要素が指定したCSSセレクタに合致するか確認するためのメソッドです。 (jQuery.is() に近い)
  • 上記のような形でイベントリスナ追加をしていると、対象要素が動的に追加されるケースにも対応できて便利です。

closest()

// polyfill (https://developer.mozilla.org/en-US/docs/Web/API/Element/closest)
if (!Element.prototype.closest) {
    Element.prototype.closest = function(s) {
        let el = this;
        do { if (el.matches(s)) return el; el = el.parentElement || el.parentNode; } while (el !== null && el.nodeType === 1);
        return null;
    };
}
  • closest() は、対象要素の親要素を順々に辿って、セレクタにマッチする最初の要素を取得するメソッドです。(jQuery.closest() に近い)

querySelectorAll() + forEach()

ライブラリ無しの環境で行うとき
Array.prototype.forEach.call(document.querySelectorAll('セレクタ'), function(el, i){
    // 処理
});
素の IE11 でも NodeList での forEach() が使えるようにする
// polyfill (https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach)
if (!NodeList.prototype.forEach) NodeList.prototype.forEach = Array.prototype.forEach;
NodeList の forEach() を使う
document.querySelectorAll('セレクタ').forEach(function(el, i){

});
for of (素の IE11 では使用できない)
for (const el of document.querySelectorAll('セレクタ')) {

}
for of (配列に変換 (Array.from() でも可)。素の IE11 では使用できない)
for (const el of [...document.querySelectorAll('セレクタ')]) {

}

resize イベント + 間引き (requestAnimationFrame)

(function(){
    let requestId;
    // リサイズ時
    window.addEventListener('resize', function(){
        cancelAnimationFrame(requestId);
        requestId = requestAnimationFrame(function(){
            // 処理
        });
    });
})();
  • ブラウザウィンドウをリサイズすると resize イベントが大量に発生するため、requestAnimationFrame() を使用して処理を間引きます。

wheel イベント

// ホイールスクロール時
document.addEventListener('wheel', function(e){
    // 下スクロール
    if (e.deltaY > 0) {
    }
    // 上スクロール
    else if (e.deltaY < 0) {
    }
});

タッチでのスクロール無効

document.addEventListener('touchmove', function(e){ e.preventDefault() }, { passive: false });

スワイプ (上下)

let prevY = null;
document.addEventListener('touchstart', function(e){
    prevY = e.touches[0].clientY;
});
document.addEventListener('touchmove', function(e){
    if (prevY == null) return;

    // 下から上へスワイプ
    if (e.touches[0].clientY < prevY) {
        // 何か処理
        prevY = null;
    }
    // 上から下へスワイプ
    else if (e.touches[0].clientY > prevY) {
        // 何か処理
        prevY = null;
    }

    e.preventDefault();
}, { passive: false });

要素が画面上 (ビューポート上) に入っているか確認する

const viewportWidth = document.documentElement.clientWidth; // ビューポート幅 (スクロールバー除く)
const viewportHeight = document.documentElement.clientHeight; // ビューポート高さ (スクロールバー除く)
const rect = el.getBoundingClientRect(); // 要素の位置 (ビューポート上)
const yOK = (0 <= rect.top && rect.top <= viewportHeight) || (0 <= rect.bottom && rect.bottom <= viewportHeight); // 上辺または下辺がビューポート内
const xOK = (0 <= rect.left && rect.left <= viewportWidth) || (0 <= rect.right && rect.right <= viewportWidth); // 左辺または右辺がビューポート内

matchMedia()

幅のチェック (1回)

if (window.matchMedia('(min-width: 680px)').matches) {
    // 幅680px以上
}
else {

}

幅のチェック (イベント)

const handle = function(list) {
    if (list.matches) {
        // 幅680px以上
    }
    else {
        // 幅680px未満
    }
};
const list = window.matchMedia('(min-width: 680px)');
list.addListener(handle); // 幅変更時確認
handle(list); // 初回確認

外部リンクを新しいタブで開く

if (!Element.prototype.matches) Element.prototype.matches = Element.prototype.msMatchesSelector;
document.addEventListener('click', function(e){
    if (e.target.matches('a[href^="http"]:not([target])')) {
        e.target.relList.add('noopener');
        e.target.target = '_blank';
    }
});

SVGElement で classList を使用可能にする (IE11)

// SVGElement で classList を使用可能にする (IE11)
if (!('classList' in SVGElement)) {
    const classList = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'classList');
    if (classList) Object.defineProperty(SVGElement.prototype, 'classList', classList);
}
  • IE11より古いブラウザは想定していません。

ユーティリティ関数

XPath 検索 ($x)

/**
 * 指定した XPath にマッチした要素を配列で取得します。(例: $x('descendant::comment()'))
 * @param {string} path XPath
 * @param {Node} startNode ルート要素 (未指定の場合 Document)
 * @return {Node[]} 結果ノード配列
 */
function $x(path, startNode) {
    const nodes = [];
    const result = document.evaluate(path, startNode || document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
    for (let i = 0, len = result.snapshotLength; i < len; i++) nodes.push(result.snapshotItem(i));
    return nodes;
}

Chrome のコンソールで使用できる $x のエミュレートです。

DOM 要素の生成

/**
 * DOM 要素を簡易的に生成します。( 例: newElement('a', { href: '#' }, 'リンクです') )
 * @param {string}          tagName  タグ名
 * @param {Object}          props    プロパティ・属性
 * @param {(Node|string)[]} contents 内容
 * @returns {Element} 生成要素
 */
const newElement = function(tagName, props, contents){
    const el = document.createElement(tagName);
    if (props) { // プロパティ・属性 (基本はプロパティ、一部は属性などで扱います)
        Object.keys(props).forEach(function(k){
            if (/^data-/.test(k)) el.setAttribute(k, props[k]); // data-*
            else if (k == 'class') el.className = props[k]; // class → className
            else el[k] = props[k];
        });
    }
    if (contents) { // 内容 (Node、テキスト、Nodeやテキストが含まれた配列)
        const contentsArray = Array.isArray(contents) ? contents : [contents];
        contentsArray.forEach(function(content){
            const node = content instanceof Node ? content : document.createTextNode(content);
            el.appendChild(node);
        });
    }
    return el;
};

DOM 要素の生成 (HTML文字列から)

/**
 * HTML文字列から新しい要素を生成します。(例: newElementFromString('<div class="box">test</div>'))
 * ※ <div> を使った簡易的なものです。<div> 内で許可されない要素は生成できません。(DOMParser 等を使用してください)
 * @param {string} htmlString HTML文字列
 * @returns Element
 */
function newElementFromString(htmlString) {
    const tmpl = document.createElement('div');
    tmpl.innerHTML = htmlString;
    return tmpl.firstElementChild;
}

次の要素

/**
 * 対象要素の兄弟要素の中で指定セレクタにマッチする次の要素を取得します。(jQuery の next(selector) に近いものです)
 * @param {Element}   el       対象要素
 * @param {string}    selector 指定セレクタ
 * @returns {Element}          見つかった要素 (存在しない場合 null)
 */
const nextElementSibling = function(el, selector) {
    let next = el.nextElementSibling;
    while (next) { if (next.matches(selector)) return next; next = next.nextElementSibling; };
    return null;
};

前の要素

/**
 * 対象要素の兄弟要素の中で指定セレクタにマッチする前の要素を取得します。(jQuery の prev(selector) に近いものです)
 * @param {Element}   el       対象要素
 * @param {string}    selector 指定セレクタ
 * @returns {Element}          見つかった要素 (存在しない場合 null)
 */
const previousElementSibling = function(el, selector) {
    let prev = el.previousElementSibling;
    while (prev) { if (prev.matches(selector)) return prev; prev = prev.previousElementSibling; };
    return null;
};

イベント発火 (簡易)

/**
 * 指定イベントを発火させる簡易的な関数です。(例: trigger(document.body, 'click'))
 * 
 * @param {Object}  target       イベントターゲット
 * @param {string}  name         イベント名 (例: 'click')
 * @param {boolean} [bubbles]    イベントがバブルするか
 * @param {boolean} [cancelable] イベントがキャンセル可能か
 * @param {Object}  [detail]     カスタムイベント時の追加データ
 */
const trigger = function(target, name /*, bubbles = true, cancelable = true, detial = null */) {
    const bubbles = 2 in arguments ? arguments[2] : true; // IE11 ではデフォルト引数が使用できない
    const cancelable = 3 in arguments ? arguments[3] : true;
    const detail = 4 in arguments ? arguments[4] : true;
    const intarface = detail ? 'CustomEvent' : 'Event';
    const event = document.createEvent(intarface); // IE11用にイベントコンストラクタを使用せずにインスタンスを生成します。

    if (intarface == 'CustomEvent') event.initCustomEvent(name, bubbles, cancelable, detail);
    else event.initEvent(name, bubbles, cancelable);
    target.dispatchEvent(event);
};

ダウンロード

/**
 * 指定した URL のファイルをダウンロードします。
 * @param url {string} URL
 * @param filename {string} ファイル名
 */
const downloadUrl = function(url, filename) {
    // IE11 は download 属性に対応していないためそのまま開きますが、
    // どうしてもダウンロードにさせたい場合は下記のように Ajax → Blob → Blob 保存 とします。(CORS 制約でエラーになる可能性あり)
    const ie11ForceDownload = true;
    if (ie11ForceDownload && navigator.msSaveOrOpenBlob && !/Edge/.test(navigator.userAgent)) {
        var xhr = new XMLHttpRequest();
        xhr.open('GET', url, true);
        xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
        xhr.responseType = 'blob';
        xhr.onload = function(e) {
            if (this.status === 200) navigator.msSaveOrOpenBlob(this.response, filename);
        };
        xhr.send();
        return;
    }
    const a = document.createElement('a');
    a.href = url;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
};
/**
 * テキストをダウンロードします。(downloadUrl() が必要です)
 * @param content {string} 内容
 * @param filename {string} ファイル名
 */
const downloadText = function(content, filename) {
    const blob = new Blob([content]);
    // IE, Edge
    if (navigator.msSaveOrOpenBlob) {
        navigator.msSaveOrOpenBlob(blob, filename);
    }
    // その他
    else {
        const url = URL.createObjectURL(blob);
        downloadUrl(url, filename);
        URL.revokeObjectURL(url);
    }
};
/**
 * Blob をダウンロードします。(downloadUrl() が必要です)
 * @param blob {Blob} Blob オブジェクト
 * @param filename {string} ファイル名
 */
const downloadBlob = function(blob, filename) {
    // IE, Edge
    if (navigator.msSaveOrOpenBlob) {
        navigator.msSaveOrOpenBlob(blob, filename);
    }
    // その他
    else {
        const url = URL.createObjectURL(blob);
        downloadUrl(url, filename);
        URL.revokeObjectURL(url);
    }
};