ナビゲーション
トップへ戻るコントロール
スクロール量に応じた表示切替と、戻る操作の成功フィードバックを確認するレシピです。
動きを試す
プレビュー
必要なファイルを選ぶ
コード
index.html
利用前に変更する箇所
- 実サービス本文と戻る閾値を調整する
<!doctype html>
<html lang="ja" data-theme="light">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>トップへ戻る</title>
<style>
:root { --bg:#f7f8fa; --surface:#ffffff; --text:#18212f; --muted:#5d6b7a; --border:#d7dee8; --focus:#0077cc; --accent:#245fd6; }
:root[data-theme="dark"] { --bg:#0f141a; --surface:#171d26; --text:#f2f5f8; --muted:#aab4c0; --border:#354152; --focus:#65c7ff; --accent:#7ea7ff; }
* { box-sizing: border-box; }
body { margin: 0; padding: 0; background: var(--bg); color: var(--text); font-family: Arial, sans-serif; }
.frame { block-size: min(620px, 100vh); overflow-y: auto; padding-block-end: 140px; }
header, section {
max-width: 760px;
margin: 0 auto;
}
header { padding: 16px 18px 6px; }
.body {
max-width: 760px;
margin: 0 auto;
border: 1px solid var(--border);
border-radius: 12px;
background: var(--surface);
padding: 12px;
}
.placeholder {
margin: 10px 0;
min-height: 520px;
border: 1px dashed var(--border);
border-radius: 10px;
padding: 12px;
}
.go-top {
position: fixed;
right: 18px;
bottom: 18px;
min-height: 44px;
border-radius: 999px;
border: 1px solid var(--border);
background: var(--surface);
padding: 10px 14px;
}
.go-top[data-visible="false"] { display: none; }
button:focus-visible { outline: 3px solid var(--focus); }
.status { min-height: 1.2em; margin-top: 8px; }
[data-state-panel][hidden] { display: none; }
@media (max-width: 360px) { body { padding: 10px; } .frame { padding-bottom: 160px; } }
@media (prefers-reduced-motion: reduce) { * { transition-duration: 0.01ms !important; animation: none !important; } }
</style>
</head>
<body>
<div class="frame" id="frame">
<header>
<h1>トップへ戻るコントロール</h1>
</header>
<main class="body">
<div data-state-panel="hidden">
<p>最上部付近では「トップへ戻る」は非表示です。</p>
</div>
<div data-state-panel="visible" hidden>
<p>十分に下までスクロールしたとみなしています。</p>
</div>
<div data-state-panel="activated" hidden>
<p>戻る操作を受け付け、上部へ移動できました。</p>
</div>
<div class="placeholder">長文エリア(ダミー)</div>
<div class="status" role="status" data-status></div>
</main>
</div>
<button class="go-top" type="button" data-go-top>上へ戻る</button>
<script>
(() => {
const params = new URLSearchParams(window.location.search);
const instance = params.get('instance') ?? 'missing-instance';
const statePanels = Array.from(document.querySelectorAll('[data-state-panel]'));
const status = document.querySelector('[data-status]');
const button = document.querySelector('[data-go-top]');
const frame = document.querySelector('#frame');
const validStates = new Set(['hidden', 'visible', 'activated']);
const setTheme = (theme) => {
if (theme === 'dark' || theme === 'light') document.documentElement.dataset.theme = theme;
};
const setState = (nextState) => {
if (!validStates.has(nextState)) return;
document.documentElement.dataset.state = nextState;
for (const panel of statePanels) panel.hidden = panel.dataset.statePanel !== nextState;
if (button) button.dataset.visible = String(nextState !== 'hidden');
if (status) status.textContent = `状態: ${nextState}`;
};
const updateFromScroll = () => setState(frame.scrollTop > 200 ? 'visible' : 'hidden');
let activatedByControl = false;
frame.addEventListener('scroll', () => {
if (activatedByControl && frame.scrollTop === 0) {
activatedByControl = false;
setState('activated');
return;
}
updateFromScroll();
}, { passive: true });
button.addEventListener('click', () => {
activatedByControl = true;
frame.scrollTo({
top: 0,
behavior: window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 'auto' : 'smooth',
});
});
window.addEventListener('message', (event) => {
if (event.source !== window.parent || typeof event.data !== 'object' || event.data === null) return;
if (event.data.source !== 'wpl-host' || event.data.instance !== instance) return;
if (event.data.type === 'wpl-preview-state') setState(event.data.value);
if (event.data.type === 'wpl-preview-theme') setTheme(event.data.value);
});
setTheme(params.get('theme') === 'dark' ? 'dark' : 'light');
setState(validStates.has(params.get('state')) ? (params.get('state') ?? 'hidden') : 'hidden');
window.parent.postMessage({ source: 'wpl-preview', type: 'wpl-preview-ready', instance }, '*');
})();
</script>
</body>
</html>
プレビューで試せる動き
- 非表示: 上部近辺では非表示で邪魔にならない。
- 表示: 下位へスクロールした時のみ表示される。
- 戻った: クリック後に上部へ戻り、状態表示を更新する。
使い分けを確認する
このレシピが合う場面
適する場面
- 長文ページでトップ移動を短い操作で行う
- 読み進め状況を壊さずに戻る導線を提供する
- マウスだけでなくキーボードでも戻る操作を確認する
適さない場面
- ページ内に十分な高さがない短文レイアウト
- スクロール位置監視を重くしたくない重いSPAの都合
