※スマホ対応はしてません。

DOMを使って、jQueryなしでいろいろやってみよう。コピペでどうぞ。(DOMおれおれAdvent Calendar 2015 – 24日目)

カテゴリー: JavaScript

DOMおれおれAdvent Calendar 2015 – 24日目

最終日です。

2015-12-24

jQueryでやってる事をjQueryなしでやるとしたらどうするのってやつです。

$(selector)document.querySelectorAll(selector)

これが一番基本ですね。

(さらに…)

DOMでjQuery風ライブラリーを自作するための基本戦略です。(DOMおれおれAdvent Calendar 2015 – 23日目)

カテゴリー: JavaScript

DOMおれおれAdvent Calendar 2015 – 23日目

2015-12-23

jQueryをまるまる使う程じゃないけどちょっとjQuery的な機能が欲しいなという場面はときどきあるかと思うんですが、そういうときに「jQuery風の機能」を自作するための実装例を幾つか挙げてみたいと思います。

セレクターから検索して、各要素を操作

こんな風に使いたい場合。

var $el = $('#foo .bar [boo=123]');
$el.css({ color:'red' });

以下の二つの機能が必要です。

  • セレクターから要素を取得し、確保しておく
  • 確保した要素全てに対して処理をする

他にも諸々の機能が必要かと思いますが、それらの基盤となるのが本機能ですね。

セレクターから検索して要素を確保

これで配列風に扱えるようになります。

// インターフェイス。これを使う。
function $(selector) {
  return new MyQuery(selector);
}

// コンストラクター。実態。
function MyQuery(selector) {
  var nodes = document.querySelectorAll(selector);

  // 配列風に各要素を保持
  for (var i=0, l=nodes.length; i<l; i++) {
    this[i] = nodes[i];
  }

  // 配列風に要素数を保持
  this.length = nodes.length;
}
var $links = $('a');
console.log($links.length, $links[0]);

といっても何も機能がないので大して嬉しくないですね。ここからです。

各要素に対して一括操作

forEach() なものを実装します。というか配列が持つ関数をそのまま使っちゃって大丈夫です。

MyQuery.prototype.forEach = Array.prototype.forEach;

あら簡単。(IE 9+。IE 8の場合は自前で実装します。) これだけでもだいぶ使えると思います。

var $links = $('a');
$links.forEach(function(el, index) {
  console.log(el, index);
});

ちなみにjQueryは forEach() じゃなくて each() というAPIで、コールバック関数に与えられる引数の順序が違います。けどまあいいよね。

API設計の基盤思想

jQueryは使い心地が良いのですが、以下の共通仕様が大きく貢献しているものと思います。

  • メソッドチェインのため、操作後オブジェクト自身を返す
  • 対象の有無による分岐を省くため、検索結果が0件の状態でも各機能でエラーにしない

というわけで、これらに気を付けながら諸々実装していきます。

イベント監視

割とよく使う気がしますので、これからいきましょう。 on()addEventListener() で実装できます。

MyQuery.prototype.on = function(type, listener) {
  this.forEach(function(el) {
    el.addEventListener(type, listener);
  });
  return this;
};

簡単だ!

拡張しやすくする

jQueryのメソッドは $.fn に登録する事で自由に追加できるようになっています。真似ておきましょう。

$.fn = MyQuery.prototype;

できたよ

全然APIないですが、基本的にはこんなもんでしょうか。

// インターフェイス。これを使う。
function $(selector) {
  return new MyQuery(selector);
}

// コンストラクター。実態。
function MyQuery(selector) {
  var nodes = document.querySelectorAll(selector);

  // 配列風に各要素を保持
  for (var i=0, l=nodes.length; i<l; i++) {
    this[i] = nodes[i];
  }

  // 配列風に要素数を保持
  this.length = nodes.length;
}

$.fn = MyQuery.prototype;
$.fn.forEach = Array.prototype.forEach;

$.fn.on = function(type, listener) {
  this.forEach(function(el) {
    el.addEventListener(type, listener);
  });
  return this;
};

30行にも満たないコードですが、これでこんな風に使えます。

$('#el').on('click', function(event) {
  alert('!');
});

ちょっとサンプルコード書きたいけどjquery.jsを探してくるの面倒だなーみたいなときに便利かもしれません。良い感じに雛型が整備できるなら良いけれど。

というのライブラリーを作ってます

よければご利用ください。

jQueryのtrigger()みたいに、イベントを作ったり発火させたりしてみよう。(DOMおれおれAdvent Calendar 2015 – 22日目)

カテゴリー: JavaScript

DOMおれおれAdvent Calendar 2015 – 22日目

2015-12-22

jQueryなら $el.trigger('foo') とするだけで簡単なんだけど、DOMでやるとちょっと面倒くさいです。

独自イベントを作る

CustomEventというコンストラクターを使います。第一引数にイベント名を与えます。

var type = 'foo';
var myEvent = new CustomEvent(type);

console.log(myEvent, myEvent.type);

基本的にこれで良いのだけど、IEはエラーになります。しかもCustomEventコンストラクターはあるのに使っちゃ駄目っていう仕様です。(だから有無だけじゃ判断できない。) IEてそういうところあるよね……。

というわけで

仕方ないからIEでも動くやり方にします。

var type = 'foo';
var myEvent = document.createEvent('CustomEvent');
myEvent.initCustomEvent(type, false, false, null);

console.log(myEvent, myEvent.type);

IEでも動くんならこれでいいんじゃ?

と思う方もおられるかと思いますが、なんでもこっちのやり方は廃止の予定だそうで。 new CustomEvent() の方を使ってね、だそうです。

イベントに情報を持たせる

CustomEventの場合、 event.detail で参照できる情報を設定する事ができます。

var type = 'foo';
var detail = { sushi:'tuna' };

var event1 = document.createEvent('CustomEvent');
event1.initCustomEvent(type, false, false, detail);
console.log(event1.detail.sushi);  // => "tuna"

var event2 = new CustomEvent(type, { detail:detail });
console.log(event2.detail.sushi);  // => "tuna"

本物?のイベントの見分け方

ユーザー操作からウェブブラウザーが生成した本物のイベントは、 isTrusted プロパティが true になります。JavaScriptから生成した場合はこれが false になるので、ここを見ると「あ、こいつ騙りだ!」と分かります。

クリックイベントを作る

引数が多くて割と面倒くさい。

var event = document.createEvent('MouseEvents');
event.initMouseEvent('click',
  true,    // bubbles
  true,    // cancelable
  window,  // view
  null,    // detail
  0,       // screenX
  0,       // screenY
  0,       // clientX
  0,       // clientY
  false,   // ctrlKey
  false,   // altKey
  false,   // shiftKey
  false,   // metaKey
  0,       // button
  null     // relatedTarget
);

IEを捨てられるなら new MouseEvent('click') だけで事足ります。情報を与える必要がある場合は第二引数にオブジェクトを指定します。

var event = new MouseEvent('click', {
  bubbles: true,
  button: 1
});

イベントを発火させる

dispatchEvent() でイベントを発火させる事ができます。

// eventは作成済みとします

var el = document.querySelector('#el');

el.addEventListener('click', function(event) {
  console.log(event.type);
});

el.dispatchEvent(event);

上記の例の通り、イベントは普通に addEventListener() で監視できます。ちなみに監視はあくまでイベント名を対象に行うので、MouseEventでもCustomEventでも、 type"click" であれば反応します。

ちなみにjQuery

jQueryはjQuery.Eventという独自のコンストラクターを用意して、DOMのイベントとは無関係にやってるみたいです。なので本来の event にあるはずのプロパティがなかったりする。

元のイベントは event.originalEvent に格納されるので、適宜そちらを利用します。携帯端末のタップ関係とかこっち使うね。

参考

イベント監視で教えてもらえる情報いろいろ。(DOMおれおれAdvent Calendar 2015 – 21日目)

カテゴリー: JavaScript

DOMおれおれAdvent Calendar 2015 – 21日目

2015-12-21

イベント監視のコールバックでもらえるeventオブジェクト、色々使えます。

target でイベント発生要素

あと currentTarget で監視対象要素、 relatedTarget で関連要素。

過去記事をどうぞ。

type でイベントの種類

例えば click イベントを監視していたら event.type"click" になります。複数のイベント監視を同じコールバック関数で行う場合に利用できます。

var listener = function(event) {
  console.log(event.type);
};
el.addEventListener('focus', listener);
el.addEventListener('click', listener);

マウス操作とタッチ操作をある程度同一視しつつ一部違う処理、なんてときに便利かもしれません。あとは諸々のイベントを監視してログ取ってるときとか。

timeStamp

イベント発生日時の数字です。

だいたい Date.now() と同じくらいになるかと思います。 new Date(event.timeStamp) してやるとDateオブジェクトになります。

Firefoxでバグってる

なんか変な数字が返ってきます。

『11年くらいバグってる』そうです。まじかよ。

内容が変わるっぽい

Dateと同じミリ秒単位の数字が設定されてたんですが、最近この値をページ読み込み時を起点としたマイクロ秒単位の値になりそうな流れです。

defaultPrevented でキャンセル済みか確認

preventDefault() されると true になります。

el.addEventListener('click', function(event) {
  console.log(event.defaultPrevented);  // => false
  event.preventDefault();
  console.log(event.defaultPrevented);  // => true
});

eventPhase でイベント発火の段階を知る

状態 定数
未発火 Event.NONE
キャプチャ中 Event.CAPTURING_PHASE
対象要素 Event.AT_TARGET
浮上中 Event.BUBBLING_PHASE

「キャプチャ中」てのはあれです、 addEventListener() の第三引数で true を指定した時のあれです。

あとIEは Event.NONE を持ってないみたい。

その他

だいたいよくわかってない。

  • bubbles … 浮上し得るかどうか。コールバック関数に与えられるものは全部 true ? 新規作成したオブジェクトだと false
  • cancelablepreventDefault() でキャンセルできるか。したか、じゃない。
  • isTrusted … 利用者操作からのイベントであるか。JavaScriptが作成したEventオブジェクト等だと false

参考

イベント監視中にできる事ふたつ。(DOMおれおれAdvent Calendar 2015 – 20日目)

カテゴリー: JavaScript

DOMおれおれAdvent Calendar 2015 – 20日目

2015-12-20

イベント監視のコールバックでもらえるeventオブジェクト、色々使えます。

UIのデフォルト動作を止める

何と言っても event.preventDefault() が一番良く使われるかと思います。リンクで実装されてるけど見た目も動作もボタンていうね。(できるなら <button> で書いてほしい……。)

<a id="the-button" href="#">ここをクリック!</a>
var elButton = document.querySelector('a#the-button');
elButton.addEventListener('click', function(event) {
  // リンクの画面遷移をキャンセル
  event.preventDefault();

  // 何かボタンを押した際の処理……
});

ウェブページが持つUIは利用者の操作を受け付けて諸々の「デフォルト」動作を行うわけですが、それをキャンセルするのがこのメソッドです。(キャンセルっていう言い方で良いのかな。) 他にもこんなものをキャンセルできます。

  • チェックボックスのチェック
    • ただし change はキャンセルできない。もう値が変わった後だから
    • click はキャンセルできる。なおイベント発火時点の el.checked は更新後の値になるので、キャンセル後に戻る
  • フォームの送信
    • 入力内容を検証して、駄目ならキャンセルして「名前を入力してください」とか
    • 単純に入力完了として受け取って、結果をAjaxで送ったりとか
  • マウス操作全般
    • mousedown をキャンセルすると色々できなくなる
    • ドラッグ操作を実装するときキャンセルしないと、画面中の文字列が選択されたりとかする
    • 同じくフリック操作の実装時に。スクロールしちゃう
    • 昔は操作禁止とかでも使ったような。今ならCSSの pointer-events:none で事足りる

イベントの伝播を停止

こちらはそんなに多用する事はないと思うんだけど、時々使ったりします。

<div id="wrapper">
  <button id="button">Push!</button>
</div>
var elWrapper = document.querySelector('#wrapper');
elWrapper.addEventListener('click', function(event) {
  alert('Wrapper!');
});

var elButton = elWrapper.querySelector('#button');
elButton.addEventListener('click', function(event) {
  event.stopPropagation();
  alert('Button!');
});

普通にボタンをクリックすると “Button!” の次に “Wrapper!” とメッセージが表示されるんだけど、 event.stopPropagation() で伝播を止めているので、ラッパーの方は click イベントは発火しません。

イベントの伝播

イベントは「発火」した後、上位の要素へ順に伝わっていきます。今回の例ではボタンからラッパーの <div> へ、その後は <body><html> そして document へと昇ってゆきます。

それを止めるのが stopPropagation() で、実行すると今回のイベントは上位要素(ノード)へ通知されなくなります。

ちなみに “propagation” で「伝播(でんぱ)」の意味です。

ちなみにちなみに下方への伝達の仕組みもあるんだけど、あまり利用する機会ないので気にしなくて良いかなと思います。

他のイベントリスナーに通知しない

しれっと三つめ。 stopPropagation() は上位要素への伝播を止める機能でしたが、同じ要素で複数のコールバック関数を登録している際、他のコールバック関数の実行もまとめて止めるものがあります。

stopImmediatePropagation() です。

とはいえまあ、既に実行された分は止めようがないですね。コールバック関数は登録順に呼び出されるので、自分より後ろに並んでる関数は実行されなくなります。

環境

preventDefault() は各環境で問題ありません。

stopPropagation() 及び stopImmediatePropagation() は IE 9+です。IE 8の場合はコールバック関数で return false する事で伝播を止める事ができますが、この場合デフォルト動作のキャンセルも同時に行われてしまいます。

参考