pjax第3回 「pjaxでのイベント処理」

10053 4

連載モノとなっております。
以前のものをご覧になっていない方はそちらからご覧になると、より理解が深まると思います。

今回はpjax後のイベント処理について、もっと詳しく書かせていただきます。

pjaxの使用でつまずきやすいポイントはやはりページ遷移後にイベントが意図した通りに動作しないというところでしょう。

そのトラブルシューティングはもちろんですが、そもそも「そういったエラーが発生しにくいコーディングを行う」という視点からお話しさせていただければと思います。



皆様は普段サイトを作成する際、各ページ毎にjsファイルを作成されるでしょうか?
また、JavaScriptのコーディングの際、どのようなコード設計をするでしょう?

pjaxサイトでは、サイト全体を一つのアプリケーションと考え、その処理の全体像を一連の流れと考えながらコードを設計する必要があります。

こう書くと非常に難しいことを行わなければならないと感じるかもしれません。
ですがご安心を。皆様が難しいことを考えなくて済むように設計したのが、前回紹介したhtmlの設計とJavaScriptのコードです。
また、pjaxサイトを構築することは自分のコードの粗を教えてくれたり、効率の良いJavaScriptのコーディングやプログラムの処理方法を考える機会になり、自身の技術を高めてくれることでしょう。さらにpjaxサイト構築の考え方はアプリケーション製作に応用がききます。きっと、ご自身のスキルアップにも貢献してくれることでしょう。

JavaScriptイベント管理

サイト全体の処理を一連の流れと考える、とはどういうことでしょう?
簡単に言えば、再読込みを必要とせず動き続ける処理の流れつくるということです。

その為にイベントの管理を行います。

ポイントは

  • JavaScriptの実行タイミングを理解する
  • DOMに紐付けられたイベントは、そのDOMがページ上に存在する限り存在します
  • JavaScriptの各処理が終了(ページロード完了時)してから追加されたDOMには追加されたタイミングでイベントを紐付けなければなりません
  • イベントは「上書き」を期待しない

という4点です。

JavaScriptプログラムのそれぞれの実行はページロード時にすべて完了しています。
通常のサイトではページを移動するごとに毎度一からJavaScriptなどを実行し直しますが、pjaxでは入れ替えたエリア以外に付与されたイベントはそのまま残っており、入れ替えたエリア内の要素へのイベントは真っさらな状態です。

これは非同期読み込みや要素の追加後に対する処理すべてで言えることですが、
JavaScriptは主にDOMContentLoadedloadイベントに紐付けて実行します。

これは「ページロード直後に開始・実行完了」を意味します。非同期で読み込んで来た要素はこの実行タイミングの外(実行完了後)にあります

その為、非同期で読み込んで来た(追加した)要素には「同様に非同期で(promise等で)」その読み込み完了後に処理を行わなければなりません。 前回紹介した「各プラグインでの呼び出し方例」のイベント(pjax:end , pjax:load , transitionCompletedなど)はそれぞれのプラグインでの非同期読み込み完了時にコールバックされるイベントです。
基本としてこれらのイベントからコールバックさせればその処理はpjax後に必ず行われます。

Ajax等でも.then()などでサーバーからのレスポンスが返ってきてから処理を行いますね?それと同じです。

同様に、windowやdocumentといった上位ノード、pjaxエリア外に付与したイベントやクラス、属性値などは自分で処理をしない限り延々と残り続けるため、必要に応じて削除していかなければイベントの重複や表示崩れなどの原因となります。
逆に言い換えれば、JavaScriptの設計の段階で、これらを意識してコーティングしていくことでエラーの発生を無くすことができます。

前回の例を元に説明すると

  • ページロード時の最初の一回 => init()
  • pjaxエリア外に付与するイベント => init()
  • 各ページそれぞれの時のみ使うイベント => PageEvents.foo()
  • pjaxエリア内だが複数のページで使うイベント => PageEvents.commonFunc()
  • pjax遷移後のイベントからPageCheck()をコールバック
  • pjaxエリア外の属性値、クラスリフレッシュ => pjax遷移前のイベントからコールバック

このルールを元にすればシンプルかつ、間違えないのではないでしょうか。

イベント付与とイベント削除

イベントの付与に関してはどんな製作においても必ず使ってらっしゃるでしょう。
ではイベントの削除は?

多くの方が使用したことがないのではないでしょうか?
通常のサイトであればページを移動すればすべてリフレッシュされるので当然でしょう。

しかし、pjaxサイトではほぼ必須です。

方法は簡単で
jQueryでは.off()
Nativeでは主にremoveEventListener()
を使用します。

use jQuery

jQueryを使っていると.on().off()という非常に便利なメソッドが使えるため、イベントの削除も簡単に行えます。特に.on()ではイベントに対して直感的に名前空間を利用でき、名前空間名でのイベント管理もできるという利点があります。

// add Event
$('selector').on( eventType, selector(※省略可), fn );

$('selector1').on('click', fn ); // 特定のセレクタにクリックイベントを付与
$('selector2').click( fn ); // .click()は.on('click')のエイリアス

// remove Event
$('selector1').off('click'); // 特定のセレクタに(on()で)付与したクリックイベントを削除
$('selector2').off('click'); // 内部的に.on()を使用しているため削除可
// add Event
$('selector').on('click.namespace', fn ); // 特定のクリックイベントに名前空間を利用する

$(window).on('scroll', fn );
$(window).on('scroll.namespace', fn2 ); // 名前空間を利用して複数のスクロールイベントを付与する
$(window).on('scroll.namespace resize.namespace', fn3 ); // 複数のイベントを名前空間付きで付与(名前空間は別名も可)

// remove Event
$('selector').off('.namespace'); // 名前空間を指定してイベントを削除('selector'に付与された.namespaceを持つイベントすべてを削除)
$(window).off('scroll.namespace'); // eventType+名前空間で複数付与されている中から一つのイベントを削除
$(window).off('scroll resize'); // 複数のeventTypeを一度で削除

jQueryでのイベント付与・削除のポイント

  • .on()はメソッドチェーンで繋ぐことで違ったeventTypeのものを一度で記述できます。
  • .off()は半角スペースで区切って(同selectorに付与されている)複数のeventTypeをまとめて削除できます。
  • 名前空間を使用すると、同じeventTypeのものを同じ要素に複数付与できます。
  • 名前空間のみを指定したイベント削除は、同名の名前空間を使用したイベントすべてを対象にできます。
  • eventType+名前空間を指定してピンポイントでのイベント削除ができます。

さて、ここで問題。

$(window).on('scroll', fn );
$(window).on('scroll.myScroll', fn );

/* 1 */ $(window).off('scroll');
/* 2 */ $(window).off('.myScroll');
/* 3 */ $(window).off('scroll.myScroll');

した時のそれぞれの結果はどうなるでしょう?

→ A.(クリックで答えを表示)
1 => 両方削除される
2 => $(window).on(‘scroll.myScroll’, fn )のみ削除される($(window).on(‘scroll’, fn )は残る)
3 => 2と同じ

この性質を利用すれば、ページ毎に異なるscrollやresizeイベントを使用し、また不要になったイベントのみを削除することも容易です。

また、前回の「pjax初期設定」項目でも紹介したように

// 第二引数に指定した(子孫)要素へイベントをデリゲート
$('parent').on('click', 'child', fn );

といった使い方もでき、この場合、parentをpjaxエリア外の要素にしていれば、各ページ毎に毎度イベント付与する必要もなくなる為、積極的に利用していくのをおすすめします。
(※イベントリスナの節約を重視するのみの考えで、pjaxエリア内の要素をparentにした使い方でも全く問題ありません)

.on()の注意点

元々.on()はイベントを重複して付与させます。(上書きしない)
その為、同じ要素に同じイベントで.on()を複数回用いるとその回数分イベントが付与されます。重複してしまったイベントは同じ回数.off()しなければ削除できません。

Native

// イベント付与
element.addEventListener( eventType, listener[, option] );

// イベント削除
element.removeEventListener( eventType, listener[, option] );

注意点としてはremoveEventListener対となるaddEventListenerと同じ引数を指定しなければ効果がありません

listener名が違っていたり、option部分が違っていてもイベントリスナを削除できません。
同様に、addEventListenerのlistenerに無名関数を渡した場合もちょっとややこしいことをしなければ削除ができません。
その為、Nativeでの実装の際、jQueryと同じ感覚で

// 無名関数を渡した記述
element.addEventListener( eventType, () => { // function(){
	// some code
}, false);

といった記述をするのは避けましょう。
こと、削除が必要なイベントだった場合、書き直さなければならなくなります。2度手間をするくらいなら、最初から記述方式を改めるほうが早いです。

listenerには関数名を渡しましょう。

// listenerには関数名を渡すべき(処理をちゃんと関数に分けて関数名を渡す)
function fn(){
	// some code
}
/* or
const fn = () => {
	// some code
}
*/
element.addEventListener( eventType, fn, false );

element.removeEventListener( eventType, fn, false ); // 削除できる

// これは削除できない(addEventListener時とremoveEventListener時でoption指定が異なる)
element.removeEventListener( eventType, fn, true );

※addEventListener時のoptionがfalseの際は、removeEventListenerでoptionを省略しても削除できます。

また、通常であればaddEventListenerで付与したイベントは重複されず、上書きされます。しかし、クラスやオブジェクトからの呼び出しの際は別listenerとして扱われるケースがあり、上書きされずに重複していきます
その為、pjax後のイベント付与の際、上書きを期待せず不要になったイベントは必ずremoveEventListenerした上で付与しなおしたほうが良いです

言うまでもないことかもしれませんが、Nativeで付与したイベントをjQueryで削除したり、その逆を行うこともできません。
addEventListenerに対してremoveEventListener.onに対して.offといったように対になる組み合わせでしかイベント削除はできません。

ちなみに僕はNativeで毎度addEventListenerやremoveEventListenerを書くのは結構面倒なのでこんな便利関数を作ってます。

function eachEventAdd(element,addEvent,funcName,option){
	let bubbling = (option) ? option : false;
	for (let i = element.length - 1; i >= 0; i--) {
		element[i].addEventListener(addEvent, funcName, bubbling);
	}
}

function eachEventRemove(element,addEvent,funcName,option){
	let bubbling = (option) ? option : false;
	for (let i = element.length - 1; i >= 0; i--) {
		element[i].removeEventListener(addEvent, funcName, bubbling);
	}
}

// 使用例
const elements = document.querySelectorAll('.selector');
eachEventAdd( elements, 'click', fn );
// 削除時
eachEventRemove( elements, 'click', fn );

pjaxエリア内のイベント削除のタイミング

削除のタイミングとしては、pjax遷移前です。

PJAXならpjax:fetchから
jquery-pjaxやBarba.jsならlinkClickedイベントなどクリックイベントからコールバックさせると良いでしょう。

また、各ページ毎のイベントをどのようにして削除していくか、となった時pageCheck()などから分岐させて…でも良いのかもしれませんがこれもかなり面倒な上、コードのインターフェースとしても汚くなります。

そこで僕は、Event Observerのようなものを作成して自動化しています。

const OBSERVER = {
	handlers: [],

	observeEvents: function(targets){
		this.handlers.push(targets);
	},
	clearEvents: function(){
		const _self = this;
		function loop(i){
			return new Promise(function(resolve, reject){
				let events = _self.handlers[i];
				// handlers[i]内のイベントを削除
				for (var j = events.length - 1; j >= 0; j--) {
					let remEv = events[j];
					let option = remEv[3] ? remEv[3] : false;
					remEv[0].removeEventListener(remEv[1], remEv[2], option);
				}
				// handlers[i]を削除
				_self.handlers.splice(i,1);
				resolve( i++ );
			}).then(function( count ){
				if( count < _self.handlers.length - 1 ){ // handlersのlength分loop()を回す
					loop( count )
				}
			})
		}
		loop(0);
	}
};

OBSERVERオブジェクトの使い方例

// 使い方
const PageEvents = {
	topFunc: () => {
		window.addEventListener('scroll',  moving);
		const clicElm = document.getElementById('clicElm');
		clicElm.addEventListener('click', clicking, true);

		// 省略


		// ページ内で付与したイベントを2次元配列としてOBSERVERオブジェクトへ渡す
		const listeners = [
			// [ element, eventType, listener, option ]で渡す
			[window, 'scroll', moving],
			[clicElm, 'click', clicking, true]
			// 他にもあればここに全部記述
		];
		OBSERVER.observeEvents( listeners );
	},

	// 省略

};

このOBSERVERオブジェクトの使い方としては、ページ毎のイベント時に、そのページ内でしか使わないイベントすべてを2次元配列の形式でOBSERVERに渡し、pjax遷移前にclearEvents()を発火させるようにして使います。

jquery-pjaxの場合、jQueryの内部処理で自動でイベント削除までされるものがほとんどなのでOBSERVERオブジェクトはあまり必要なく、使うとすればwindowやdocumentなどpjaxエリア外に紐付けたイベント用くらいのものになります。

要素の追加と削除

.append().apeendChild()などを使って動的にDOMを追加したり.remove().removeChild()で要素を削除したりすることがあると思います。
ここでもjQueryとNativeのコードを混在させないように注意しましょう。

DOMを追加すると(=DOMが増えるとその分)メモリを使用します。
削除すればGC(ガベージコレクション)の対象となり、GCされるタイミングでメモリは解放されます。

しかし、jQueryを使用して追加したDOMをNativeのメソッドで削除するとこの限りではなくなります。

なぜなら、jQueryでは$()処理やDOM操作を行う際、内部的にjQueryオブジェクトを生成します。これはメモリ上にキャッシュされ、.remove().empty()といったjQueryメソッドを使用してそのDOMを削除した際にクリアされます。つまり、これらのメソッドを利用しなければメモリ上から消えません。=メモリが解放されずここもメモリリークの原因になります。

また、jQueryでは.remove()などで削除したDOMに紐付けたイベントはjQueryが内部処理で自動的に削除してくれますが、Nativeの.removeChild()などの場合は削除したDOMに紐付けたイベントは自動では削除されません。自前で.removeEventListener()しなければならないということです。(イベント削除をし忘れるとメモリリーク)ここも勘違いしやすいポイントなので注意してください。
しかも、DOMを削除する前にイベントを削除しなければエラーになる為注意が必要です。

こういったこともあり、無用なミスをなくす為、jQueryとNativeのコードは混在させないようにと言った次第です。(※pageCheck()関数は例外。このあたりに慣れれば関数単位の切り分けくらいならアリだと思います。)

前項に書いてある通り、もし.removeなどで削除するDOMにaddEventListener等Nativeでイベントを付与してあった場合、そのイベントは削除されません。
こういったところでもjQueryとNativeのコードを混在させると勘違いやミスを起こしやすくなります。

Nativeでの要素削除

一つの要素を削除するのは簡単ですが、.empty()のような「子要素をすべて削除」といった便利な関数は用意されていない為、ラッパー関数のような便利関数を作っておくとおすすめです。

function empty(parent){
	// parentの子要素をすべて削除(孫まで遡っていないので孫がある場合は孫→子の順でempty()するほうがベター)
	while(parent.firstChild){
		/* ここで一緒に前述のeachEventRemove()するのもアリ */
		parent.removeChild(parent.firstChild);
	}
}

// 使い方
const parent = document.getElementById('foo');
empty(parent);

innerHTML = “”じゃダメ?

innerHTML = ''では表面上は削除されているものの、DOMオブジェクト上に要素の参照が残っているケースがあります。通常のサイトではそれでも特に問題はないでしょう。しかしpjaxサイトでは一度メモリ上に追加されたものは自分で削除しなければ(削除の処理を自前で書かなければ)ずっと残っているのです。
処理速度そのものも重要ですが、確実に削除がされる方法で行うことの方がずっと重要です。
※最近のブラウザでは.removeChild()をwhileループで回した方が処理速度が速くなっていたりもします。

「pjax遷移後にイベントが上手く動かない」のトラブルシューティング

遷移後にクリックイベントなどが上手く動かない、という現象に出くわした場合の対処について少し書かせていただきます。
次のどちらのケースかをまずチェックしましょう。

  1. イベントが実行されない
  2. イベントは実行されているが動作がおかしい

前回紹介したPageEventsオブジェクトを使って説明していきます。

function scrollEvent(e){
	let move = window.pageYOffset;
	console.log(move);
	//some code
}
function click1(){
	console.log('click1');
	//some code
}
function click2(){
	console.log('click2');
	//some code
}

const PageEvents = {
	commonFunc: () => {
		// すべてのページ毎に毎回実行したい処理
		console.log('Change Content Complete.');
	},
	page1: () => {
		// ページ1の時にのみ実行したい処理
		console.log('page1');
		const a = document.getElementById('a');
		const b = document.getElementById('b');

		a.addEventListener('click', click1 );
		b.addEventListener('click', click2 );
	},
	page2: () => {
		// ページ2の時にのみ実行したい処理
		console.log('page2');
		window.addEventListener('scroll', scrollEvent );
	},
	elseFunc: () => {
		// それ以外の時にのみ実行したい処理
		console.log('else');
	}
}
function pageCheck(){
	// 省略
}
function init(){
	// 省略
}

window.addEventListener('DOMContentLoaded', init, false);

まずはこのような感じで各メソッドや関数にconsole.logを仕込んでチェックするとどこで処理がされていないのかが把握できるでしょう。

エラーが発生しているのがPage1だった場合、正解はコンソールに「page1」と「Change Content Complete.」が表示される。

  • コンソールに正解の表示がされない => pageCheck関数のページ判定の記述にミスがある
  • a、bをクリックしてもコンソールに対応した表示がされない => 要素の取得時のミスを疑う

といった具合に、処理のどのタイミングでエラーが発生しているかを追うことができます。

1(イベントが実行されない)の場合

イベントが付与できていない=遷移後にイベント付与を行う処理が呼び出されていない為です。
前回紹介した雛形を使っていればほぼほぼこのケースは起こらないですが、ページ取得判定が間違っていて、表示中のページと分岐されたページイベントが一致していなければこういったことが起こります。

また、ページ分岐は成功しているが、要素の取得にミスがあった場合もイベントが実行されない原因の一つになります。

2(イベントは実行されているが動作がおかしい)の場合

例えば、このように特定のイベントのconsole.logが複数表示される、といった症状は出ていませんでしょうか?

ここでは「click2」の表示に2という数字が付いていますが、これはイベントが重複してしまい、一度の呼び出しで2回同じイベントが実行されてしまっている状態です。

この場合はイベント削除などのリフレッシュ漏れが原因でしょう。
特に多いのはwindowに対するscrollやresizeイベントの重複やheaderなどpjaxエリア外の要素に対してPageEventsでイベントを重複付与してしまうケース。これらの要素に対してイベント付与や切り替えを行う際は重複していないかをよくよくチェックしましょう。

その他、pjax遷移前に付与した属性値やクラスの削除も、こうした関数を作成しておいて適宜呼び出すようにしておくと良いでしょう。

function refresh(){
	// style属性値を削除
	$('#foo').attr('style', '');

	// pjaxエリア外の#barの特定クラスを削除
	$('#bar').removeClass('.baz');
}
function refresh(){
	// style属性値を削除
	document.getElementById('foo').removeAttribute('style');

	// pjaxエリア外の#barの特定クラスを削除
	document.getElementById('bar').classList.remove('.baz');
}

使用タイミングとしてはOBSERVER.clearEvents()と同じタイミングやPageEvents.commonFunc()でも良いでしょう。

プラグインの挙動がおかしい

プラグインによってはpjaxに対応していないものも少なくありません。そういった場合、

  • リロードやアップデートメソッドなど、プラグイン内でキャッシュする要素を置き換えるメソッドは提供されているか?
  • デストロイなど破棄するメソッドが提供されているか?

このいずれかに該当するメソッドが提供されていない場合、PhotoSwipeとInfinite Scrollを共存させるの「回答編:2.そもそもリロードメソッドもイベント再付与も必要無い」でも書いていた親ノードでイベントを定義する方法を用いていないプラグインであれば、pjaxサイトでの使用は好ましくありません。

また、ページ毎のイベント付与の際、毎回無作為にjQueryプラグインの初期化処理(通常の呼び出し方)をしてしまわないように注意しましょう
ここはちょっと分かりにくいところかもしれませんが、jQueryプラグインで内部的(プラグイン内のコードで)に.on()処理がされているものの、それを知らずに何度も.onを繰り返していることになります。

当然、イベントは重複する為、ライトボックスが二重に開いたり、スクロール値が正常に取得されずにおかしな挙動をしたり…といったバグが発生します。pjax遷移後は基本、アップデートやリロードメソッドで処理しましょう。(デストロイ->初期化はアップデート・リロードに比べて処理負荷が遥かに大きいので苦肉の策程度に考えてください。)

jQueryプラグインのイベント重複はpjaxに慣れていないとやってしまいがちです。プラグインをpjaxサイトで使用する際は一度内部の処理に目を通しましょう。上記に該当するメソッドが提供されていない場合、他のものを使うか、メソッドを書き加えなければなりません。
(※慣れれば、プラグイン内で付与されているイベントを見つけ出して、それを削除することもできるにはできます。)

勘違いしやすいSNSボタンの処理

公式ボタンであればpjax後(urlの変化後)アップデート処理が必要になったりするのですが、最近だとほとんどの場合自前で作成していますね?
そういうSNSボタンの仕組みは基本、aタグにonclick属性で該当サイトへそのページのURLを送信するというものです。

はい、もうお分かりですね?
そう、現在のページ状況がどう変わっていようと関係ありません。

例として、facebookのOGPで言えば、pjax遷移後でmeta propertyの値が変わっていないとしても、何ら気にする必要はありません。 SNS側のプログラムは「送られてきたURLに対して」情報を取得する為、SNS側には直接URLを入力してアクセスした時の情報が取得されます。

何かしら特別な理由がなければpjax後にhead内部の情報を書き換える必要はありません。

最後に

ここまでを踏まえて雛形をアップデートしてみましょうか。(メソッド名などは適宜変更してください)

const OBSERVER = {
	handlers: [],

	observeEvents: function(targets){
		this.handlers.push(targets);
	},
	clearEvents: function(){
		const _self = this;
		function loop(i){
			return new Promise(function(resolve, reject){
				let events = _self.handlers[i];
				// handlers[i]内のイベントを削除
				for (var j = events.length - 1; j >= 0; j--) {
					let remEv = events[j];
					$(remEv[0]).off(remEv[1]);
				}
				// handlers[i]を削除
				_self.handlers.splice(i,1);
				resolve( i++ );
			}).then(function( count ){
				if( count < _self.handlers.length - 1 ){ // handlersのlength分loop()を回す
					loop( count )
				}
			})
		}
		loop(0);
	}
};

/* Events Detail */

	// 関数群一覧をここへ

/*  */

const PageEvents = {
	commonFunc: () => {
		// すべてのページ毎に毎回実行したい処理

	},
	page1: () => {
		// ページ1の時にのみ実行したい処理

		// jQueryでは[ 'element', event  ]のみでOK
		const listeners = [

		];
		OBSERVER.observeEvents( listeners );
	},
	page2: () => {
		// ページ2の時にのみ実行したい処理

		// jQueryでは[ 'element', event  ]のみでOK
		const listeners = [

		];
		OBSERVER.observeEvents( listeners );
	},
	elseFunc: () => {
		// それ以外の時にのみ実行したい処理

		// jQueryでは[ 'element', event  ]のみでOK
		const listeners = [

		];
		OBSERVER.observeEvents( listeners );
	}

}
function init(){
	// windowやdocumentなどの上位ノードに対して付与したいイベント定義
	// pjaxエリア外の要素に対して付与したいイベント定義など

	$(document).on('click', 'selector', function(e){
		e.preventDefault();
		let href = $(this).attr('href');
		// any processing

		$(pjaxContainer).fadeOut( 400, () => { // ←遷移前のアニメーション
			$.pjax({
				url: href,
				container: pjaxContainer
				//any options
			});
		});
		// pjax遷移前にclearEvents()でイベント削除
		OBSERVER.clearEvents()
	});

	$(document).on('pjax:end',function(){
		pageCheck();
	});
}

function pageCheck(){
	// 省略
}

$(function(){
	pageCheck();
	init();
});
const OBSERVER = {
	handlers: [],

	observeEvents: function(targets){
		this.handlers.push(targets);
	},
	clearEvents: function(){
		const _self = this;
		function loop(i){
			return new Promise(function(resolve, reject){
				let events = _self.handlers[i];
				// handlers[i]内のイベントを削除
				for (var j = events.length - 1; j >= 0; j--) {
					let remEv = events[j];
					let option = remEv[3] ? remEv[3] : false;
					remEv[0].removeEventListener(remEv[1], remEv[2], option);
				}
				// handlers[i]を削除
				_self.handlers.splice(i,1);
				resolve( i++ );
			}).then(function( count ){
				if( count < _self.handlers.length - 1 ){ // handlersのlength分loop()を回す
					loop( count )
				}
			})
		}
		loop(0);
	}
};

/* Events Detail */

	// listenerに渡す関数群一覧をここへ

/*  */

const PageEvents = {
	commonFunc: () => {
		// すべてのページ毎に毎回実行したい処理

	},
	page1: () => {
		// ページ1の時にのみ実行したい処理

		const listeners = [
		
		];
		OBSERVER.observeEvents( listeners );
	},
	page2: () => {
		// ページ2の時にのみ実行したい処理

		const listeners = [
		
		];
		OBSERVER.observeEvents( listeners );
	},
	elseFunc: () => {
		// それ以外の時にのみ実行したい処理

		const listeners = [
		
		];
		OBSERVER.observeEvents( listeners );
	}
}
function pageCheck(){
	// 省略
}
function init(){
	pageCheck();
	// windowやdocumentなどの上位ノードに対して付与したいイベント定義
	// pjaxエリア外の要素に対して付与したいイベント定義など
}

window.addEventListener('DOMContentLoaded', init, false);
function inAnimation(){
	// 遷移前にページ内のイベント削除
	OBSERVER.clearEvents()

	// 遷移前のアニメーション

}

window.addEventListener('pjax:fetch', inAnimation );
let linkClicked = false;

Barba.Dispatcher.on('linkClicked', function(HTMLElement){
	linkClicked = true;

	// 遷移前にページ内のイベント削除
	OBSERVER.clearEvents()
}

※jquey-pjaxとBarba.jsでOBSERVER.clearEvents()を使用する場合、必ずしもクリックイベントに紐付けなければならない訳ではなく、遷移前のイベントでも良いです。

pluginpjax

Comments

  • くま より:

    深井さま
    ご返信ありがとうございます。
    おっしゃるとおりですね。
    これだけのPJAXマスターさんはいらっしゃらないでしょうし、
    いつかtetatail復活してください。
    それでは失礼致します。

    • 深井 学 より:

      くまさま
      こちらこそご丁寧にありがとうございます。
      そう言っていただけて恐縮です。。とはいえそう名乗るのはまだまだおこがましいレベルではあります。
      ただ、多少なりと僕の知見が役に立つのであれば、また時折覗くように致します。
      更新頻度は非常に低いですが、また気の向いた時に立ち寄っていただけると嬉しく思います。

  • くま より:

    こんにちは。サイトかっこいいですね~。

    Barbaなんかだとこれでも良さそうな感じもしますね。

    Barba.Dispatcher.on('linkClicked', function() {
    $(document).off('*');
    });

    • 深井 学 より:

      くまさま
      コメントありがとうございます。
      「サイト内全てで同じイベントしか使わない」というケースであれば無しではないと思います。
      しかし、そういったケースはあまり無いのではないでしょうか?結局は条件分岐などが必要になったり、今後追加や編集が入った際に追加したイベントの個数.off()を記述する必要が出てきます。
      それが重なるとBarba.Dispatcher内が乱雑になっていってしまう為、『関数に分ける+都度条件で分ける』といった処理をしておく方がスマートだと考えます。
      規模が大きくなればなるほど疎結合にしておく方がメンテナンス性に大きな差が出ますし。

くま にコメントする コメントをキャンセル

メールアドレスが公開されることはありません。

認証コード * Time limit is exhausted. Please reload CAPTCHA.

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください