0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaScript を遅延させたページで、最初のタップはどのイベントが消えるのか【Playwright で実測】

0
Posted at

JavaScript の読み込みを最初の操作まで遅らせると、その最初の操作は誰が受け取るのでしょうか。

先に要点です。

最初の操作(ページを開いてすぐ) 遅延スクリプトのハンドラ ページの動き
チェックボックスのラベルをタップ change 届かず チェックは入る
ボタンをタップ click 届かず 何も起きない
入力欄をタップして1文字入力 input 届かず 文字は入る
送信ボタンをタップ submit 届かず JS を通らずにフォームが送信され、ページ遷移
先に別の操作をしてから待った時間 スクリプト到着まで 0ms 300ms
0ms 届いた 届かず
100ms 届いた 届かず
300ms 届いた 届いた
1000ms 届いた 届いた

4種類とも、最初の1回は落ちました。
2回目からは全部届きます。
境目は、ハンドラが登録されるまでの時間でした。

先に断っておくと、遅延JSが最初の操作を取りこぼすことは知られています。WP Rocket にも、初回のクリックが遅延スクリプトの読み込み後まで待たされる、という報告が issue #7296 に上がっているようです。ここで足すのは、どのイベントが落ちるかを種類ごとに測った結果と、落ちる時間の幅です。

9/25 サイドバーが1回目だけ真っ白

発端は自分のサイトでした。

Cocoon テーマのスマホ用スライドインサイドバーが、1回目のタップだけ空で開きます。
2回目からは正常。

使っているキャッシュプラグインは、自作の Prime Cache です。
JavaScript の遅延(Delay JS)を有効にしていました。

2回目からは直るので、見落としやすい種類の不具合でした

Cocoon のドロワーは、CSS のチェックボックスで開閉します。
中身のサイドバーは、javascript.js がチェックボックスの change を受けて移動させます。

その javascript.js が遅延されていました。

9/25 ローダーが見ているイベント

Prime Cache のローダーは、次のイベントで遅延スクリプトの読み込みを始めます。

var triggerEvents = [
	'keydown', 'mousedown', 'mousemove', 'touchstart',
	'touchend', 'wheel', 'scroll'
];

click は入っていません。
コメントには、リンクが必ず即座に動くように、クリックは止めない、とあります。

タップすると、順番はこうなります。

  1. touchstart で読み込みが始まる
  2. click が発火し、チェックボックスが切り替わり、change が発火する
  3. そのあとで javascript.js が実行され、ハンドラが登録される

change の時点で、受け取る人がいません。
ローダーは click も change も再生しないので、それきりです。

読めば分かる話でした。読んだのは、壊れてからでした

9/26 種類ごとに測る

change だけなのか。
ほかのイベントはどうか。

WordPress 抜きで最小のページを作りました。
ローダーは Prime Cache 1.10.62 の pc-delay-loader.js をそのまま使っています(GitHub)。

<script>window.pcDelayTimeout = 0;</script>
<script src="pc-delay-loader.js"></script>
...
<label for="cb" id="lb">メニュー</label><input type="checkbox" id="cb">
<button id="btn">ボタン</button>
<input id="tx">
<form id="fm"><button id="sb">送信</button></form>
<script type="pc-delay/javascript" data-pc-src="handlers.js"></script>

遅延される handlers.js では、jQuery の $(document).on() と同じ委譲の形と、要素への直接登録の両方を置きました。

window.__boundAt = performance.now();
function log(n) { (window.__log = window.__log || []).push(n); }

document.addEventListener('change', e => { if (e.target.id === 'cb') log('change-deleg'); });
document.addEventListener('click',  e => { if (e.target.id === 'btn') log('click-deleg'); });
document.addEventListener('input',  e => { if (e.target.id === 'tx') log('input-deleg'); });
document.addEventListener('submit', e => { e.preventDefault(); log('submit-deleg'); });
document.getElementById('cb').addEventListener('change', () => log('change-direct'));
document.getElementById('btn').addEventListener('click', () => log('click-direct'));

submit のハンドラは preventDefault() しています。
Ajax で送るフォームの形です。

これを Playwright(Chromium 141)で開いて、最初の操作をしてから __log を読みます。
スマホは Pixel 7 のエミュレーション。
handlers.js は、ローカルのサーバーで 0ms と 300ms の遅れを付けて返しました。

const ctx = await browser.newContext({ ...devices['Pixel 7'] });
const page = await ctx.newPage();
await page.goto(url);
await page.waitForTimeout(500);
await page.tap('#lb');              // 最初の操作
await page.waitForTimeout(1500);
console.log(await page.evaluate(() => window.__log));

9/26 全部落ちた

冒頭の1つめの表が結果です。

change だけではありませんでした。
click も input も submit も、最初の1回は落ちます。
委譲でも直接登録でも同じ。
マウスのクリックでも、Tab で移ってスペースキーでも同じでした。

submit がいちばん怖い

送信ボタンのタップでは、ハンドラがいないので preventDefault() されません。
フォームは素のまま送信され、ページが遷移しました。
Ajax で送るつもりのフォームが、JS の検証も通らずに GET で飛んでいきます。

ハンドラが登録された時刻も測りました。
最初の操作から、スクリプト到着 0ms のときで約100ms、300ms のときで約350〜400ms 後です。
この間に起きたイベントは、全部落ちます。

9/26 先に触っていれば間に合う

では、ページを開いてから少しスクロールした人はどうか。

スクロール(wheel)でローダーを起こしておき、待ってからタップしました。
デスクトップでは、マウスを動かしてからクリックしています。
冒頭の2つめの表がそれです。スマホのスクロールでも、デスクトップのマウス移動でも、結果は同じでした。

スクリプトの到着に 300ms かかる設定だと、100ms 後のタップはまだ落ちます。
300ms 待てば届きます。

デスクトップで取りこぼしにくいのは、クリックの前にたいていマウスが動くからです。
ローダーは最初の mousemove を1回見送りますが、その次で起きます。
スマホにはそれがありません。
ページを開いて最初にメニューを押す人は、そのタップが最初の合図になります。

Prime Cache の Delay JS は、既定ではスマホにだけ掛かります。
取りこぼしやすい側にだけ掛かっている、ということでもあります。

直し方

Cocoon の件は、1.10.62 で直しました。
遅延しない小さなスクリプトを1本だけ置いて、サイドバーを移す処理を肩代わりさせています。
Cocoon のハンドラが動き出したら、そちらに任せます。

一般には、手は3つだと思います。

手 効くところ 代わりに失うもの
そのスクリプトを遅延の対象から外す 確実 遅延の効果が減る
最初の操作を捕まえておき、読み込み後に再生する 全体 リンクの即応性。再生の順序の管理
初回の操作に要る処理だけ、遅延しない小さなスクリプトで持つ 対象の箇所 箇所ごとに手当てが要る

Prime Cache は、リンクを止めない方針なので、2つめは選んでいません。
1.10.62 は3つめです。

次の自分に渡すメモ

  • 遅延JSで落ちるのは change だけではない。最初の click input submit も落ちる
  • submit が落ちると、preventDefault() されないままフォームが送信される
  • 落ちる幅は、最初の操作からハンドラ登録まで。スクリプトの到着時間にほぼ比例する
  • デスクトップは直前の mousemove で間に合いやすい。スマホは最初のタップが合図そのもの
  • 遅延させるスクリプトを選ぶときは、ページを開いて最初に押されうるものを先に探す

ふだんはraplsworks.comで、WordPressプラグイン開発やClaude Codeまわりのことを書いています。
https://raplsworks.com/

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?