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?

画面に出していないブラウザでアニメを確かめたら「動かない」と出た → 非表示のページは requestAnimationFrame が止まる。headless Chrome で測り直す

0
Posted at

並び替えたときにカードが滑らかに動く表示(FLIP と呼ばれるやり方)を作り、動くかどうかを確かめていました。確かめる作業は生成AI(Claude Code)に任せていて、AIはアプリの中のブラウザでページを開き、JavaScript で要素の位置とアニメーションの状態を読んでいました。

返ってきた結果は「動いていない」でした。Web Animations API のアニメーションは pending のまま進まず、測った位置は 0。コードを何度読み直しても、おかしいところは見つかりません。

原因:ページが画面に出ていなかった

そのブラウザの画面は、測っている間ずっと閉じた(非表示の)状態でした。

ブラウザは、見えていないページの描画を止めて電気と CPU を節約します。非表示のページでは requestAnimationFrame のコールバックがほとんど呼ばれず、描画に合わせて進む処理(アニメーションの進み・ResizeObserver など)も止まります。コードが正しくても、見えていないページで測ると「動かない」と出ます。

背景のタブで同じことが起きます。人が操作していれば気づきますが、自動で測ると「ページが見えているか」は結果に出てきません。

直し方1:測る前に document.hidden を見る

まず、測った結果と一緒に「そのページが見えていたか」を出すようにします。

console.log(document.hidden, document.visibilityState); // true, "hidden" なら測っても意味がない

描画が進んでいるかは、1秒間に requestAnimationFrame が何回呼ばれたかで分かります。

let n = 0;
const tick = () => { n++; requestAnimationFrame(tick); };
requestAnimationFrame(tick);
setTimeout(() => console.log({ hidden: document.hidden, frames: n }), 1000);
// 見えているページなら { hidden: false, frames: 60 } くらい。0〜1 なら描画が止まっている

直し方2:動きの確認は headless Chrome で測る

動き・リサイズの確認は、画面の表示に左右されない headless のブラウザで測るようにしました。Playwright で手元の Google Chrome を使う例です(channel: 'chrome' にすると、Playwright 同梱のブラウザを入れていなくても動きます)。

check-anim.mjs
import { chromium } from 'playwright';
import { pathToFileURL } from 'node:url';

const browser = await chromium.launch({ channel: 'chrome' }); // 手元の Google Chrome を使う
const page = await browser.newPage();
await page.goto(pathToFileURL('page.html').href);

const state = () => page.evaluate(() => {
  const el = document.getElementById('box');
  const a = el.getAnimations()[0];
  return {
    hidden: document.hidden,
    playState: a?.playState,
    currentTime: Math.round(a?.currentTime ?? 0),
    x: Math.round(el.getBoundingClientRect().x),
  };
});

console.log('開始直後', await state());
await page.waitForTimeout(500);
console.log('0.5秒後', await state());
await page.waitForTimeout(800);
console.log('1.3秒後', await state());
await browser.close();

page.html は、四角を1秒で右へ300px動かすだけのページです。

page.html
<!doctype html><meta charset="utf-8">
<div id="box" style="width:80px;height:80px;background:#36c"></div>
<script>
document.getElementById('box').animate(
  [{transform:'translateX(0)'},{transform:'translateX(300px)'}],
  {duration:1000,fill:'forwards'}
);
</script>
npm i -D playwright
node check-anim.mjs
# 開始直後 { hidden: false, playState: 'running', currentTime: 17, x: 13 }
# 0.5秒後 { hidden: false, playState: 'running', currentTime: 533, x: 168 }
# 1.3秒後 { hidden: false, playState: 'finished', currentTime: 1000, x: 308 }

hidden: false のまま、時間に合わせて x が進み、最後に finished になれば動いています(x の 8 は body の余白の分です)。

使い分け

  • 見た目(色・レイアウト)のスクリーンショット:画面に出ているブラウザで撮る
  • 動き・リサイズ・時間で変わるもの:headless で測る。結果に document.hidden を必ず並べる

まとめ

  • 非表示のページ・背景のタブでは requestAnimationFrame がほぼ止まり、アニメーションは進まない
  • 自動で測るときは、結果と一緒に document.hidden と1秒あたりのフレーム数を出す
  • 動きの確認は headless Chrome(Playwright の channel: 'chrome')で測る

生成AI(Claude Code)に書かせて一人で会社を回している記録を note に書いています。
https://note.com/kaito_cometa

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?