目的
これまでブラウザの横幅に応じてイベントリスナの登録と解除時には、window.matchMedia、addEventListener、removeEventListenerを用いてきたが、AbortControllerを活用した方が管理がしやすかったため、今後のための備忘録としてサンプルを記す。
※なお、以下要件等は自身の研究下で設定した内容のため、流用の際には読み替え、置き換え、検証は必ず行うようにしてください。
要件
PC版レイアウト
・600pxより広い幅
・window.mathcMediaでfalseが取得できた場合、PC版イベントリスナを登録する
・SP版イベントリスナは無効化されたままとする
SP版レイアウト
・600px以下
・window.mathcMediaでtrueが取得できた場合、SP版イベントリスナを登録する
・PC版イベントリスナは無効化されたままとする
初期表示後、ユーザー操作によりPCからSP、またはSPからPCにリサイズされた場合の要件
・PCからSPにリサイズされた場合:
→PC版で登録されたイベントリスナは無効化する、かつ、特定の関数処理が実行された状態の場合、処理がされる前の状態に初期化する(例:特定要素にclassを付与したが、classが付いたままSP幅にリサイズ→class付与を解除)
※SPからPC側にリサイズされた場合も同様の扱いとする
サンプルコード
main.js
import { allFunctions } from './allFunctions.js';
const mql = '(max-width:600px)';
const mobileDevice = window.matchMedia(mql);
const registerFunctionsManager = () => {
const isMatches = mobileDevice.matches;
if (isMatches) {
// true
// モバイル版イベントを登録(本ファイルではPC版の解除のみ)
console.log('モバイル版');
allFunctions(isMatches)
} else {
// false
// PC版イベントを登録
console.log('PC版');
allFunctions(isMatches)
}
}
// ページ読み込み直後に実行される処理
registerFunctionsManager();
// ブラウザ幅変更にあわせて実行される処理
mobileDevice.addEventListener('change', () => {
registerFunctionsManager();
});
allFunctions.js
import { AbortControllerManagerPC } from './abortControllerManager.js';
import { AbortControllerManagerSP } from './abortControllerManager.js';
import { truthyFunctionA, falsyFunctionA } from './truthyFunctionA.js';
import { truthyFunctionB, falsyFunctionB } from './truthyFunctionB.js';
// ここに全関数のラッパー
// 関数定義はこの内部に格納する
const aaaaa = new AbortControllerManagerPC(truthyFunctionA, falsyFunctionA);
const bbbbb = new AbortControllerManagerSP(truthyFunctionB, falsyFunctionB);
export const allFunctions = (isMatches) => {
aaaaa.method(isMatches);
bbbbb.method(isMatches);
}
abortControllerManager.js
// PC側で使うイベントリスナーを登録するためのクラス
export class AbortControllerManagerPC {
constructor(truthyFunction, falsyFunction) {
this.controller = null;
this.truthyFunction = truthyFunction;
this.falsyFunction = falsyFunction;
}
method(isMatches) {
// isMatchesがfalse = PC版
if (isMatches === false) {
// 念のため、すでに登録済みのイベントがあれば解除
this.controller?.abort();
this.controller = new AbortController();
this.truthyFunction(this.controller.signal);
} else {
// isMatchesがtrue = SP版
this.controller?.abort();
this.controller = null;
this.falsyFunction();
return;
}
}
}
// SP側で使うイベントリスナーを登録するためのクラス
export class AbortControllerManagerSP {
constructor(truthyFunction, falsyFunction) {
this.controller = null;
this.truthyFunction = truthyFunction;
this.falsyFunction = falsyFunction;
}
method(isMatches) {
// isMatchesがfalse = PC版
if (isMatches === false) {
// isMatchesがtrue = SP版
this.controller?.abort();
this.controller = null;
this.falsyFunction();
return;
} else {
// 念のため、すでに登録済みのイベントがあれば解除
this.controller?.abort();
this.controller = new AbortController();
this.truthyFunction(this.controller.signal);
}
}
}
以下はおまけ
※必要な関数に対してtrue/falseを一か所の関数内で受け取る
allFunctions.js
import { AbortControllerManagerPC, AbortControllerManagerSP } from './abortControllerManager.js';
import { truthyFunctionA, falsyFunctionA } from './truthyFunctionA.js';
import { truthyFunctionB, falsyFunctionB } from './truthyFunctionB.js';
// ここに全関数のラッパー
// 関数定義はこの内部に格納する
const aaaaa = new AbortControllerManagerPC(truthyFunctionA, falsyFunctionA);
const bbbbb = new AbortControllerManagerSP(truthyFunctionB, falsyFunctionB);
export const allFunctions = (isMatches) => {
aaaaa.method(isMatches);
bbbbb.method(isMatches);
}
※例として、クラスの付けはずし処理の関数を登録するものとする
※実行が必要な幅での処理と、解除が必要な幅での処理をまとめておく
truthyFunctionA.js
import { __classListManager } from './utils/__classListManager.js';
/***
* PC版側の処理
* ボタンAにclickイベントを登録し、ターゲットAに対してjs-activeクラスをトグルする関数を定義
* false = 有効、true = 無効処理が必要
*/
export const truthyFunctionA = (signal) => {
const buttonA = document.getElementById('aButton');
buttonA?.addEventListener('click', () => {
__classListManager('targetA', 'js-active', 'toggle')
}, { signal })
}
export const falsyFunctionA = () => {
__classListManager('targetA', 'js-active', 'remove')
}
汎用関数
__classListManager.js
export const __classListManager = (targetClassName, controlClassName, classListMethodName) => {
const target = document.getElementsByClassName(`${targetClassName}`);
console.log('target', target);
if (target.length === 0) return;
const targetArray = Array.from(target);
if (classListMethodName === 'toggle') {
targetArray.forEach(element => {
element.classList.toggle(`${controlClassName}`);
});
} else if (classListMethodName === 'add') {
targetArray.forEach(element => {
element.classList.add(`${controlClassName}`);
});
} else if (classListMethodName === 'remove') {
targetArray.forEach(element => {
element.classList.remove(`${controlClassName}`);
});
}
}