You Don't Need jQuery

  • 2166
    Like
  • 7
    Comment

この記事はYou Don't Need jQueryの日本語訳と同じ内容です。

先日ひょんなことからYou Don't Need jQueryの日本語訳をさせていただきました。著者のCam Songさんからも快諾をいただけたので1、Qiitaでも公開させていただきます。

なお、本家の英語の説明は継続的にメンテされているので、この記事の情報は古くなっている可能性があります。


追記

この記事は当初は「もうjQueryは必要ない」というタイトルで公開していました。このタイトルに関して不快に思われた方が多いようなので、タイトルを変更させていただきました。

不快な気分になられた方は申し訳ありません。一点、意訳で「もう」を加えて必要以上に煽りっぽくなってしまったのは翻訳の問題ですのでその点に関して原著者には非はない、ということだけ補足させてください。


jQueryは必要ない(You Don't Need jQuery)

フロントエンドの開発環境はめまぐるしく進化していて、最近のブラウザでは十分な質、量のDOM/BOM APIが実装されています。もうDOM操作やイベント処理のためにjQueryを覚える必要はありません。また、ReactやAngularそしてVueなどのフロントエンドライブラリの流行により、DOMを直接操作することはアンチパターンとなりました。jQueryはそれほど重要ではなくなったのです。このプロジェクトは、jQueryでの書き方の代わりとなるネイティブでの書き方(IE10以上)をまとめます。

目次

  1. Translations
  2. セレクタ
  3. CSSとスタイル
  4. DOM操作
  5. Ajax
  6. イベント
  7. ユーティリティ関数
  8. Promise
  9. アニメーション
  10. 選択肢
  11. 対応ブラウザ

Translations

セレクタ

classセレクタ、idセレクタ、属性セレクタのような主要セレクタはdocument.querySelectorもしくはdocument.querySelectorAllで代替できます。

jQueryのセレクタと比べて以下の違いがあります。

  • document.querySelectorはセレクタにマッチする最初のエレメントを返す
  • document.querySelectorAllはセレクタにマッチする全てのエレメントのNodeListを返す。Array.prototype.slice.call(document.querySelectorAll(selector) || []);で配列に変換できる。
  • セレクタにマッチする要素がなかった場合、jQueryは[]を返すが、DOM APIはnullを返す。したがってNull Pointer Exceptionに注意する必要がある。もしくはdocument.querySelectorAll(selector) || []のように||を使ってデフォルト値を指定しておく。

注意:document.querySelectordocument.querySelectorAllはかなり遅いです。もし、パフォーマンスが必要ならdocument.getElementByIddocument.getElementsByClassNamedocument.getElementsByTagNameを使ってください。

  • 1.0 セレクタによる選択
  // jQuery
  $('selector');

  // Native
  document.querySelectorAll('selector');
  • 1.1 クラス名による選択
  // jQuery
  $('.class');

  // Native
  document.querySelectorAll('.class');

  // or
  document.getElementsByClassName('class');
  • 1.2 idによる選択
  // jQuery
  $('#id');

  // Native
  document.querySelector('#id');

  // or
  document.getElementById('id');
  • 1.3 属性による選択
  // jQuery
  $('a[target=_blank]');

  // Native
  document.querySelectorAll('a[target=_blank]');
  • 1.4 子孫要素の選択
  // jQuery
  $el.find('li');

  // Native
  el.querySelectorAll('li');
  • 1.5 兄弟要素の選択

    • 兄弟要素
    // jQuery
    $el.siblings();
    
    // Native
    Array.prototype.filter.call(el.parentNode.children, function(child) {
      return child !== el;
    });
    
    • 直前の兄弟要素
    // jQuery
    $el.prev();
    
    // Native
    el.previousElementSibling;
    
    • 直後の兄弟要素
    // jQuery
    $el.next();
    
    // Native
    el.nextElementSibling;
    
  • 1.6 祖先要素の選択

指定要素からdocument方向に遡って走査し、セレクタにマッチする最初の祖先要素を返します。

  // jQuery
  $el.closest(selector);

  // Native - 最近のブラウザのみ。IEでは動かない。
  el.closest(selector);

  // Native - IE10+
  function closest(el, selector) {
    const matchesSelector = el.matches || el.webkitMatchesSelector || el.mozMatchesSelector || el.msMatchesSelector;

    while (el) {
      if (matchesSelector.call(el, selector)) {
        return el;
      } else {
        el = el.parentElement;
      }
    }
    return null;
  }
  • 1.7 Parents Until

指定要素からセレクタにマッチする祖先要素までdocument方向に遡って走査し、フィルタにマッチする祖先要素を全て取得します。ただし、セレクタで指定された要素は含みません。

  // jQuery
  $el.parentsUntil(selector, filter);

  // Native
  function parentsUntil(el, selector, filter) {
    const result = [];
    const matchesSelector = el.matches || el.webkitMatchesSelector || el.mozMatchesSelector || el.msMatchesSelector;

    // parentから走査を開始する
    el = el.parentElement;
    while (el && !matchesSelector.call(el, selector)) {
      if (!filter) {
        result.push(el);
      } else {
        if (matchesSelector.call(el, filter)) {
          result.push(el);
        }
      }
      el = el.parentElement;
    }
    return result;
  }
  • 1.8 フォーム

    • input/textarea
    // jQuery
    $('#my-input').val();
    
    // Native
    document.querySelector('#my-input').value;
    
    • .radio内でのe.currentTargetのインデックスを返す
    // jQuery
    $(e.currentTarget).index('.radio');
    
    // Native
    Array.prototype.indexOf.call(document.querySelectorAll('.radio'), e.currentTarget);
    
  • 1.9 iframeのコンテンツ

$('iframe').contents()はiframeのcontentDocumentを返します。

  • Iframe contents

    // jQuery
    $iframe.contents();
    
    // Native
    iframe.contentDocument;
    
  • Iframe Query

    // jQuery
    $iframe.contents().find('.css');
    
    // Native
    iframe.contentDocument.querySelectorAll('.css');
    
    • 1.10 bodyを取得する
  // jQuery
  $('body');

  // Native
  document.body;
  • 1.11 属性の設定、取得

    • 属性値を取得する
    // jQuery
    $el.attr('foo');
    
    // Native
    el.getAttribute('foo');
    
    • 属性値を設定する
    // jQuery, DOMを変化させずメモリ上で動作することに注意
    $el.attr('foo', 'bar');
    
    // Native
    el.setAttribute('foo', 'bar');
    
    • data-属性を取得する
    // jQuery
    $el.data('foo');
    
    // Native (`getAttribute`を使う)
    el.getAttribute('data-foo');
    // Native (IE11以上のサポートなら`dataset`を使ってもよい)
    el.dataset['foo'];
    

⬆ back to top

CSSとスタイル

  • 2.1 CSS

    • スタイルを取得する
    // jQuery
    $el.css("color");
    
    // Native
    // NOTE: 既知のバグ デフォルト値が'auto'の場合、値が指定されていなくても'auto'が返る
    const win = el.ownerDocument.defaultView;
    // nullは疑似要素でないことを示している
    win.getComputedStyle(el, null).color;
    
    • スタイルを設定する
    // jQuery
    $el.css({ color: "#ff0011" });
    
    // Native
    el.style.color = '#ff0011';
    
    • スタイルを一括取得、一括設定する

    複数のスタイルを一括で設定したいなら、oui-dom-utilsのsetStyles関数を参考にすると良いでしょう。
    + クラスを追加する

    // jQuery
    $el.addClass(className);
    
    // Native
    el.classList.add(className);
    
    • クラスを削除する
    // jQuery
    $el.removeClass(className);
    
    // Native
    el.classList.remove(className);
    
    • クラスの有無をチェックする
    // jQuery
    $el.hasClass(className);
    
    // Native
    el.classList.contains(className);
    
    • クラスの有無を切り替える
    // jQuery
    $el.toggleClass(className);
    
    // Native
    el.classList.toggle(className);
    
  • 2.2 横幅と高さ

横幅(width)と高さ(height)の書き方はほぼ同じなので、高さ(height)の例のみを示します。

  • ウィンドウの高さ

    // window height
    $(window).height();
    // jQueryのようにスクロールバーを除いた高さ
    window.document.documentElement.clientHeight;
    // スクロールバーを含めるなら
    window.innerHeight;
    
  • ドキュメントの高さ

    // jQuery
    $(document).height();
    
    // Native
    document.documentElement.scrollHeight;
    
  • エレメントの高さ

    // jQuery
    $el.height();
    
    // Native
    function getHeight(el) {
      const styles = window.getComputedStyle(el);
      const height = el.offsetHeight;
      const borderTopWidth = parseFloat(styles.borderTopWidth);
      const borderBottomWidth = parseFloat(styles.borderBottomWidth);
      const paddingTop = parseFloat(styles.paddingTop);
      const paddingBottom = parseFloat(styles.paddingBottom);
      return height - borderBottomWidth - borderTopWidth - paddingTop - paddingBottom;
    }
    // integerで取得(`border-box`の時は`height`が、`content-box`の時は`height + padding + border`が返る)
    el.clientHeight;
    // decimalで取得(`border-box`の時は`height`が、`content-box`の時は`height + padding + border`が返る)
    el.getBoundingClientRect().height;
    
    • 2.3 PositionとOffset
  • Position

    offset parentを起点として、エレメントの座標を取得する。

    // jQuery
    $el.position();
    
    // Native
    { left: el.offsetLeft, top: el.offsetTop }
    
  • Offset

    documentを起点として、エレメントの座標を取得する。

    // jQuery
    $el.offset();
    
    // Native
    function getOffset (el) {
      const box = el.getBoundingClientRect();
    
      return {
        top: box.top + window.pageYOffset - document.documentElement.clientTop,
        left: box.left + window.pageXOffset - document.documentElement.clientLeft
      }
    }
    
    • 2.4 スクロール位置

縦スクロールバーの位置を取得する。

  // jQuery
  $(window).scrollTop();

  // Native
  (document.documentElement && document.documentElement.scrollTop) || document.body.scrollTop;

⬆ back to top

DOM操作

DOMからエレメントを削除する。

  // jQuery
  $el.remove();

  // Native
  el.parentNode.removeChild(el);
  • 3.2 Text

    • テキストを取得する

    子孫エレメントも含めた全テキスト内容を取得する。

    // jQuery
    $el.text();
    
    // Native
    el.textContent;
    
    • テキストを設定する

    エレメントのコンテントを指定されたテキストに設定する。

    // jQuery
    $el.text(string);
    
    // Native
    el.textContent = string;
    
  • 3.3 HTML

    • HTMLを取得する
    // jQuery
    $el.html();
    
    // Native
    el.innerHTML;
    
    • HTMLを設定する
    // jQuery
    $el.html(htmlString);
    
    // Native
    el.innerHTML = htmlString;
    
  • 3.4 Append

最後の子要素としてエレメントを追加する。

  // jQuery
  $el.append("<div id='container'>hello</div>");

  // Native
  el.insertAdjacentHTML("beforeend","<div id='container'>hello</div>");

最初の子要素としてエレメントを追加する。

  // jQuery
  $el.prepend("<div id='container'>hello</div>");

  // Native
  el.insertAdjacentHTML("afterbegin","<div id='container'>hello</div>");
  • 3.6 insertBefore

指定要素の後ろに新しいノードを追加する。

  // jQuery
  $newEl.insertBefore(queryString);

  // Native
  const target = document.querySelector(queryString);
  target.parentNode.insertBefore(newEl, target);
  • 3.7 insertAfter

指定要素の前に新しいノードを追加する。

  // jQuery
  $newEl.insertAfter(queryString);

  // Native
  const target = document.querySelector(queryString);
  target.parentNode.insertBefore(newEl, target.nextSibling);

セレクタにマッチするならtrueを返す。

  // is関数は複数エレメントや関数にも対応するが、matches関数は単一エレメントのみに使える
  $el.is(selector);

  // Native
  el.matches(selector);

エレメントのディープコピーを生成する。

  // jQuery
  $el.clone();

  // Native
  el.cloneNode();

  //  パラメータには`true`が渡され、深い複製を生成します。
  // 浅い複製を生成するには、`false`を渡します。

全ての子ノードを削除する。

  // jQuery
  $el.empty();

  // Native
  el.innerHTML = '';

エレメントを指定のHTMLで囲む。

  // jQuery
  $('.inner').wrap('<div class="wrapper"></div>');

  // Native
  Array.prototype.slice.call(document.querySelectorAll('.inner')).forEach(function(el){
    var wrapper = document.createElement('div');
    wrapper.className = 'wrapper';
    el.parentNode.insertBefore(wrapper, el);
    el.parentNode.removeChild(el);
    wrapper.appendChild(el);
  });

セレクタにマッチしたエレメントの親要素をDOMから削除する。マッチしたエレメント自体は残す。

  // jQuery
  $('.inner').unwrap();

  // Native
  Array.prototype.slice.call(document.querySelectorAll('.inner')).forEach(function(el){
    Array.prototype.slice.call(el.childNodes).forEach(function(child){
      el.parentNode.insertBefore(child, el);
    });
    el.parentNode.removeChild(el);
  });

セレクタにマッチしたエレメントの内容を与えられた内容に置き換える。

  // jQuery
  $('.inner').replaceWith('<div class="outer"></div>');

  // Native
  Array.prototype.slice.call(document.querySelectorAll('.inner')).forEach(function(el){
    var outer = document.createElement('div');
    outer.className = 'outer';
    el.parentNode.insertBefore(outer, el);
    el.parentNode.removeChild(el);
  });

⬆ back to top

Ajax

Fetch APIはXMLHttpRequestを置き換える新たな規格です。ChromeとFirefoxで動きます。レガシーなブラウザでもpolyfillを使えます。

IE9以上ならgithub/fetch、IE8以上ならfetch-ie8、jsonpを利用したいならfetch-jsonpを試してみてください。

  • 4.1 マッチしたエレメントをサーバから取得したHTMLに置き換える。
  // jQuery
  $(selector).load(url, completeCallback)

  // Native
  fetch(url).then(data => data.text()).then(data => {
    document.querySelector(selector).innerHTML = data
  }).then(completeCallback)

⬆ back to top

イベント

名前空間(namespace)と委譲(delegation)を利用した完全な代替手段が必要なら、 https://github.com/oneuijs/oui-dom-events を参照してください。

  • 5.0 ドキュメントが読み込まれたときの動作(DOMContentLoaded)
  // jQuery
  $(document).ready(eventHandler);

  // Native
  // DOMContentLoadedがすでに完了していないか確認する
  if (document.readyState !== 'loading') {
    eventHandler();
  } else {
    document.addEventListener('DOMContentLoaded', eventHandler);
  }
  • 5.1 イベントをバインドする(on)
  // jQuery
  $el.on(eventName, eventHandler);

  // Native
  el.addEventListener(eventName, eventHandler);
  • 5.2 イベントをアンバインドする(off)
  // jQuery
  $el.off(eventName, eventHandler);

  // Native
  el.removeEventListener(eventName, eventHandler);
  • 5.3 イベントを発火させる(trigger)
  // jQuery
  $(el).trigger('custom-event', {key1: 'data'});

  // Native
  if (window.CustomEvent) {
    const event = new CustomEvent('custom-event', {detail: {key1: 'data'}});
  } else {
    const event = document.createEvent('CustomEvent');
    event.initCustomEvent('custom-event', true, true, {key1: 'data'});
  }

  el.dispatchEvent(event);

⬆ back to top

ユーティリティ関数

殆どのユーティリティ関数はネイティブのAPIで置き換えることができます。表記の一貫性やパフォーマンスを重視した他のライブラリを使う選択肢もあります。lodashがおすすめです。

  • 6.1 基本的なユーティリティ関数

    • isArray

配列かどうか判定する。

  // jQuery
  $.isArray(array);

  // Native
  Array.isArray(array);
  • isWindow

windowかどうか判定する。

  // jQuery
  $.isWindow(obj);

  // Native
  function isWindow(obj) {
    return obj != null && obj === obj.window;
  }
  • inArray

配列の中で、指定された値が最初に現れたインデックスを返す。(見つからなければ-1を返す)。

  // jQuery
  $.inArray(item, array);

  // Native
  Array.indexOf(item);
  • isNumeric

数値かどうか判定する。
typeofを使ってください。ライブラリを使う場合、typeofは正確でない場合があります。

  // jQuery
  $.isNumeric(item);

  // Native
  function isNumeric(item) {
    return typeof item === 'number';
  }
  • isFunction

JavaScript関数オブジェクトかどうか判定する。

  // jQuery
  $.isFunction(item);

  // Native
  function isFunction(item) {
    return typeof item === 'function';
  }
  • isEmptyObject

空のオブジェクトである(列挙できる要素がない)か判定する。

  // jQuery
  $.isEmptyObject(obj);

  // Native
  function isEmptyObject(obj) {
    for (let key in obj) {
      return false;
    }
    return true;
  }
  • isPlainObject

{}もしくはnew Objectで生成されたオブジェクトであるか判定する。

  // jQuery
  $.isPlainObject(obj);

  // Native
  function isPlainObject(obj) {
    if (typeof (obj) !== 'object' || obj.nodeType || obj != null && obj === obj.window) {
      return false;
    }

    if (obj.constructor &&
        !{}.hasOwnProperty.call(obj.constructor.prototype, 'isPrototypeOf')) {
      return false;
    }

    return true;
  }
  • extend

二つ以上のオブジェクトをマージする。
object.assignはECMAScript6のAPIですが、polyfillも利用できます。

  // jQuery
  $.extend({}, defaultOpts, opts);

  // Native
  Object.assign({}, defaultOpts, opts);
  • trim

前後の空白を除去する。

  // jQuery
  $.trim(string);

  // Native
  string.trim();
  • map

配列やオブジェクトを新しい配列に変換する。

  // jQuery
  $.map(array, function(value, index) {
  });

  // Native
  array.map(function(value, index) {
  });
  • each

配列やオブジェクトに対して繰り返し処理を行う。

  // jQuery
  $.each(array, function(value, index) {
  });

  // Native
  array.forEach(function(value, index) {
  });
  • grep

フィルター関数に合致したエレメントだけを返す。

  // jQuery
  $.grep(array, function(value, index) {
  });

  // Native
  array.filter(function(value, index) {
  });
  • type

JavaScript「クラス」名を判定します。

  // jQuery
  $.type(obj);

  // Native
  Object.prototype.toString.call(obj).replace(/^\[object (.+)\]$/, '$1').toLowerCase();
  • merge

二つの配列をマージする。

  // jQuery
  $.merge(array1, array2);

  // Native
  // 重複した要素は削除されない
  function merge() {
    return Array.prototype.concat.apply([], arguments)
  }
  • now

現在の時刻を返す。

  // jQuery
  $.now();

  // Native
  Date.now();
  • proxy

関数内で実行されるthisを任意のオブジェクトに変更する。

  // jQuery
  $.proxy(fn, context);

  // Native
  fn.bind(context);
  • makeArray

配列形式のオブジェクトを配列に変換する。

  // jQuery
  $.makeArray(arrayLike);

  // Native
  Array.prototype.slice.call(arrayLike);

  // ES6なら
  Array.from(arrayLike);

ある要素が他の要素の子孫であるか判定する。

  // jQuery
  $.contains(el, child);

  // Native
  el !== child && el.contains(child);
  • 6.3 globaleval

JavaScriptコードをグローバル空間で実行する。

  // jQuery
  $.globaleval(code);

  // Native
  function Globaleval(code) {
    let script = document.createElement('script');
    script.text = code;

    document.head.appendChild(script).parentNode.removeChild(script);
  }

  // evalはcurrentコンテキストで実行される。$.globalevalのコンテキストはグローバルである。
  eval(code);
  • 6.4 parse

    • parseHTML

文字列をDOM nodeの配列として返します。

  // jQuery
  $.parseHTML(htmlString);

  // Native
  function parseHTML(string) {
    const tmp = document.implementation.createHTMLDocument();
    tmp.body.innerHTML = string;
    return tmp.body.children;
  }
  • parseJSON

JSON文字列をJavaScriptに変換します。

  // jQuery
  $.parseJSON(str);

  // Native
  JSON.parse(str);

⬆ back to top

Promise

promiseは非同期処理の最終的な処理結果を表します。jQueryにはpromiseを扱うための独自の方法があります。ネイティブのJavaScriptではPromises/A+規格に則り、薄く、最小限のAPIを実装しています。

  • 7.1 done, fail, always

doneはpromiseが成功(resolved)したとき、fallは失敗(rejected)したとき、alwaysはどちらの場合も呼び出されます。

  // jQuery
  $promise.done(doneCallback).fail(failCallback).always(alwaysCallback)

  // Native
  promise.then(doneCallback, failCallback).then(alwaysCallback, alwaysCallback)

whenは複数のpromiseを扱うときに使います。すべてのpromiseの結果が返ったときに成功となります(失敗が含まれてても成功となります)。

  // jQuery
  $.when($promise1, $promise2).done((promise1Result, promise2Result) => {})

  // Native
  Promise.all([$promise1, $promise2]).then([promise1Result, promise2Result] => {});

Deferredはpromiseを作成する方法の一つです。

  // jQuery
  function asyncFunc() {
    var d = new $.Deferred();
    setTimeout(function() {
      if(true) {
        d.resolve('some_value_compute_asynchronously');
      } else {
        d.reject('failed');
      }
    }, 1000);
    return d.promise();
  }

  // Native
  function asyncFunc() {
    return new Promise((resolve, reject) => {
      setTimeout(function() {
        if (true) {
          resolve('some_value_compute_asynchronously');
        } else {
          reject('failed');
        }
      }, 1000);
    });
  }

  // Deferred way
  function defer() {
    let resolve, reject;
    let promise = new Promise(function() {
      resolve = arguments[0];
      reject = arguments[1];
    });
    return { resolve, reject, promise };
  }
  function asyncFunc() {
    var d = defer();
    setTimeout(function() {
      if(true) {
        d.resolve('some_value_compute_asynchronously');
      } else {
        d.reject('failed');
      }
    }, 1000);
    return d.promise;
  }

⬆ back to top

アニメーション

  • 8.1 show、hide
  // jQuery
  $el.show();
  $el.hide();

  // Native
  // show関数の詳細を見たければ次のURLを参照してください 
  // https://github.com/oneuijs/oui-dom-utils/blob/master/src/index.js#L363
  el.style.display = ''|'inline'|'inline-block'|'inline-table'|'block';
  el.style.display = 'none';

エレメントが表示されていないなら表示し、表示されているなら非表示にします。

  // jQuery
  $el.toggle();

  // Native
  if (el.ownerDocument.defaultView.getComputedStyle(el, null).display === 'none') {
    el.style.display = ''|'inline'|'inline-block'|'inline-table'|'block';
  }
  else {
    el.style.display = 'none';
  }
  • 8.3 fadeIn、fadeOut
  // jQuery
  $el.fadeIn(3000);
  $el.fadeOut(3000);

  // Native
  el.style.transition = 'opacity 3s';
  // fadeIn
  el.style.opacity = '1';
  // fadeOut
  el.style.opacity = '0';

エレメントのopacityを調整してください。

  // jQuery
  $el.fadeTo('slow',0.15);
  // Native
  el.style.transition = 'opacity 3s'; // 'slow'は3秒だということにしている
  el.style.opacity = '0.15';
  • 8.5 fadeToggle

フェードイン・フェードアウトを伴ってエレメントの表示・非表示を切り替えます。

  // jQuery
  $el.fadeToggle();

  // Native
  el.style.transition = 'opacity 3s';
  let { opacity } = el.ownerDocument.defaultView.getComputedStyle(el, null);
  if (opacity === '1') {
    el.style.opacity = '0';
  }
  else {
    el.style.opacity = '1';
  }
  • 8.6 スライドアップ、スライドダウン
  // jQuery
  $el.slideUp();
  $el.slideDown();

  // Native
  let originHeight = '100px';
  el.style.transition = 'height 3s';
  // slideUp
  el.style.height = '0px';
  // slideDown
  el.style.height = originHeight;
  • 8.7 slideToggle

スライドを伴って、エレメントの表示・非表示を切り替えます。

  // jQuery
  $el.slideToggle();

  // Native
  let originHeight = '100px';
  el.style.transition = 'height 3s';
  let { height } = el.ownerDocument.defaultView.getComputedStyle(el, null);
  if (parseInt(height, 10) === 0) {
    el.style.height = originHeight;
  }
  else {
   el.style.height = '0px';
  }

CSSプロパティで定義されたアニメーションを表示します。

  // jQuery
  $el.animate({params}, speed);

  // Native
  el.style.transition = 'all' + speed;
  Object.keys(params).forEach(function(key) {
    el.style[key] = params[key];
  })

選択肢

  • You Might Not Need jQuery - ネイティブのJavaScriptでイベント、エレメント、Ajaxを扱うサンプル集(英語)
  • npm-dom and webmodules - npmで利用できるDOMモジュールを集めたOrganizationです

対応ブラウザ

Chrome Firefox IE Opera Safari
Latest ✔ Latest ✔ 10+ ✔ Latest ✔ 6.1+ ✔

ライセンス

MIT


  1. 「もちろん。MITライセンスなんだから好きにしていいよ」とのこと。かっこいい!