Wake Lock API でスマホが消えない「料理モード」を作る
料理中にスマホでレシピを見ていたら、画面が消えた。手は油まみれ。電源ボタンを肘で押して、ロック解除して、またブラウザに戻って――この体験、誰もが一度はやっているはずです。
recipe-ai では「料理モード」という機能でこの問題を解決しました。Screen Wake Lock API で画面スリープを抑制し、キーボード操作でステップを進められる全画面UIを実装しています。この記事では、その実装をコード付きで全公開します。
この記事でわかること:
- Screen Wake Lock API の基本と対応ブラウザ
- 料理中に最適化されたフルスクリーンUIの設計判断
- キーボードナビゲーションで手を使わずにステップを進める方法
- Wake Lock が使えないブラウザでのフォールバック処理
料理中の「画面消える問題」
料理中のスマホ操作には、デスクワークとはまったく異なる制約があります。
- 手が濡れている・汚れている -- 小麦粉、油、生肉を触った手でスマホには触りたくない
- タッチ操作が困難 -- 濡れた指ではタッチパネルが反応しにくい
- 頻繁に画面を確認する -- 「次の手順なんだっけ」を何度も見返す
- 画面スリープが短い -- 多くのスマホは30秒〜1分で画面が消える
設定アプリからスリープ時間を延ばす方法もありますが、料理のためだけに設定を変えて、終わったら戻す――そんな面倒なことは誰もしません。アプリ側で解決すべき問題です。
Wake Lock API とは
Screen Wake Lock API は、Webページがデバイスの画面スリープを一時的に抑制できるブラウザAPIです。ネイティブアプリでなくても、ブラウザだけで画面の常時点灯を実現できます。
基本的な使い方は非常にシンプルです。
// Wake Lock を取得(画面スリープを抑制)
const wakeLock = await navigator.wakeLock.request("screen");
// 不要になったら解放
await wakeLock.release();
navigator.wakeLock.request("screen") を呼ぶと、ブラウザはOSに対して「このページが表示されている間は画面を消さないでほしい」とリクエストします。返ってくる WakeLockSentinel オブジェクトの release() を呼べば、抑制を解除できます。
重要なのは、ユーザーがタブを切り替えたり、ブラウザを最小化したりすると、Wake Lock は自動的に解放されるという点です。バッテリーを無駄に消費し続ける心配はありません。
recipe-ai の「料理モード」実装
recipe-ai では、レシピカードの「調理モード」ボタンを押すと全画面の料理モードに切り替わります。
料理モードの起動
RecipeCard コンポーネントで、ボタンクリックにより CookingMode を表示します。
// RecipeCard.tsx(抜粋)
const [cookingOpen, setCookingOpen] = useState(false);
// 手順セクションのヘッダーに配置
<button
type="button"
onClick={() => setCookingOpen(true)}
className="text-xs border border-stone-900 text-stone-900 px-3 py-1
hover:bg-stone-900 hover:text-white transition-colors"
>
▶ 調理モード
</button>
// 条件付きレンダリング
{cookingOpen && (
<CookingMode recipe={recipe} onClose={() => setCookingOpen(false)} />
)}
Wake Lock の取得と解放
CookingMode コンポーネントのコア部分です。マウント時に Wake Lock を取得し、アンマウント時に解放します。
// CookingMode.tsx — Wake Lock 処理
useEffect(() => {
let sentinel: WakeLockSentinel | null = null;
const nav = navigator as Navigator & {
wakeLock?: { request: (type: "screen") => Promise<WakeLockSentinel> };
};
if (nav.wakeLock) {
nav.wakeLock
.request("screen")
.then((s) => {
sentinel = s;
})
.catch(() => {});
}
return () => {
sentinel?.release().catch(() => {});
};
}, []);
ここでのポイントは3つあります。
- 型アサーション -- TypeScript の型定義では
navigator.wakeLockが存在しない環境もあるため、オプショナルプロパティとして型を拡張しています - 存在チェック --
if (nav.wakeLock)で API の有無を確認してから呼び出し。非対応ブラウザではこのブロックがスキップされるだけで、料理モード自体は正常に動作します - クリーンアップ --
useEffectの return でsentinel?.release()を呼び、コンポーネント破棄時に確実に解放。.catch(() => {})で、すでに解放済みの場合のエラーを握りつぶしています
フルスクリーン表示
料理モードはダークテーマの全画面オーバーレイとして表示します。キッチンの照明下でも視認性が高く、現在のステップだけに集中できる設計です。
<div className="fixed inset-0 z-50 bg-stone-900 text-white flex flex-col">
<header className="flex items-center justify-between px-4 py-3
border-b border-stone-700 shrink-0">
<div className="min-w-0 flex-1">
<p className="text-xs text-stone-400 truncate">{recipe.title}</p>
<p className="text-sm font-bold tabular-nums">
Step {step + 1} / {total}
</p>
</div>
<button onClick={onClose} className="text-stone-300 hover:text-white px-3 py-2 text-sm"
aria-label="終了">
✕ 終了
</button>
</header>
<main className="flex-1 flex items-center justify-center px-6 py-8 overflow-y-auto">
<p className="text-2xl md:text-4xl leading-relaxed text-center max-w-2xl">
{current}
</p>
</main>
</div>
fixed inset-0 z-50 で画面全体を覆い、bg-stone-900 のダーク背景に白文字。本文は text-2xl md:text-4xl で大きく表示し、キッチンから少し離れた位置でも読めるようにしています。
また、料理モード表示中はスクロールをロックしています。
useEffect(() => {
const prev = document.body.style.overflow;
document.body.style.overflow = "hidden";
return () => {
document.body.style.overflow = prev;
};
}, []);
キーボードナビゲーション
手が汚れている状況では、画面タッチよりもキーボード操作のほうが現実的です。外付けキーボードや、Bluetoothリモコンのような物理デバイスで操作できるよう、キーボードイベントを実装しています。
useEffect(() => {
function onKey(e: KeyboardEvent) {
if (e.key === "Escape") onClose();
if (e.key === "ArrowRight" || e.key === " ")
setStep((s) => Math.min(s + 1, total - 1));
if (e.key === "ArrowLeft") setStep((s) => Math.max(s - 1, 0));
}
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [total, onClose]);
操作体系はシンプルに3つだけです。
| キー | 動作 |
|---|---|
| → / Space | 次のステップへ |
| ← | 前のステップへ |
| ESC | 料理モード終了 |
スペースキーを「次へ」に割り当てているのは、キーボードを見なくても大きなキーを叩けるからです。料理中にキーの位置を探す余裕はありません。
ステップ進行UI
画面上部にはプログレスバーを表示し、全体の進捗が一目でわかるようにしています。
const progress = ((step + 1) / total) * 100;
<div className="h-1 bg-stone-800 shrink-0">
<div
className="h-full bg-white transition-all duration-300"
style={{ width: `${progress}%` }}
/>
</div>
画面下部のナビゲーションボタンは、タッチ操作も考慮した大きめのサイズです。最後のステップでは「次へ」が「完了」に変わり、押すと料理モードが終了します。
<footer className="flex gap-3 p-4 border-t border-stone-700 shrink-0 safe-bottom">
<button
onClick={() => setStep((s) => Math.max(s - 1, 0))}
disabled={step === 0}
className="flex-1 py-4 text-lg font-medium border border-stone-600
hover:bg-stone-800 disabled:opacity-30 disabled:cursor-not-allowed"
>
← 前
</button>
<button
onClick={() => {
if (step >= total - 1) onClose();
else setStep((s) => s + 1);
}}
className="flex-[2] py-4 text-lg font-bold bg-white text-stone-900
hover:bg-stone-200"
>
{step >= total - 1 ? "完了 ✓" : "次へ →"}
</button>
</footer>
「次へ」ボタンを flex-[2] で「前」ボタンの2倍の幅にしているのは、料理中は前に戻るより先に進む操作が圧倒的に多いからです。safe-bottom クラスでiPhoneのホームインジケーター領域との干渉も回避しています。
対応ブラウザと注意点
Screen Wake Lock API の対応状況(2026年4月時点)は以下のとおりです。
| ブラウザ | 対応状況 |
|---|---|
| Chrome (Android/Desktop) | 84以降で対応 |
| Safari (iOS/macOS) | 16.4以降で対応 |
| Firefox | 未対応 |
| Samsung Internet | 対応 |
実用上はほぼ問題ありません。 iOS Safari と Android Chrome が対応しているため、スマホでレシピを見るユーザーの大多数をカバーできます。
Firefox は2026年4月時点でも未対応ですが、recipe-ai の実装では Wake Lock が使えなくても料理モード自体は正常に動作します。画面スリープの抑制だけが効かないという差分に留まります。
// 非対応ブラウザでは if ブロックがスキップされるだけ
if (nav.wakeLock) {
nav.wakeLock.request("screen").then(/* ... */).catch(() => {});
}
// → 料理モードのUI・キーボード操作・ステップ進行はすべて動く
これが「Graceful Degradation(優雅な劣化)」です。機能が使えない環境でもアプリが壊れない。Web開発の基本ですが、新しいAPIを使うときほど意識すべき設計方針です。
Wake Lock API の実装は10行程度のコードで完結します。対して、ユーザーが「画面が消えない」と感じる体験改善のインパクトは大きい。コスト対効果が非常に高い機能です。新しいブラウザAPIを追いかける習慣があると、こういう小さな改善を積み重ねられます。
まとめ
Wake Lock API は数行のコードで「画面が消えない」体験を実現できる。料理モードでは、ダークテーマの全画面表示とキーボードナビゲーションを組み合わせることで、手が汚れていても快適にレシピを進められるUIを作った。非対応ブラウザでもモード自体は動作するフォールバック設計がポイント。
この記事で作っている YouTube 料理レシピ抽出アプリ本体。動画 URL を貼るだけで AI がレシピに変換。月 5 本まで無料。
関連記事
Gemini 2.5 Flash で YouTube動画からレシピを自動抽出する方法
recipe-aiの技術スタックとAI実装の全容
Claude Code で 0→MVP を1日で作る全記録
recipe-ai Build in Public の全工程を公開
Claude Crew Lab Free — 毎月の実験記録をメールで
Claude Code × 個人開発のリアルな事故・発見・SaaS アイデアを毎月第1月曜にお届け。登録で「収益化チェックリスト 15 項目」を無料プレゼント。
Lab Free 登録(月1回・無料)
この記事が役に立ったらシェア