読了プログレス
stimeo--reading-progress
記事をどこまで読んだかを CSS へ渡します。進捗バーは CSS だけで書けます。
記事の中をどこまで読み進めたかを測り、その割合を CSS から使える形で渡します。記事そのものとページ全体の両方に書き出すので、画面のどこに固定したバーでも CSS だけで伸ばせます。よくある可視判定の仕組みでは、背の高い記事が画面を通り抜けている間ずっと同じ値になってしまうため、この計算はここが受け持ちます。描画のタイミングに合わせて間引くので、スクロールしても重くなりません。中でスクロールする枠に入れた記事でも動きます。読み終わったこともイベントで受け取れます。飾りのバーには読み上げ対象から外す指定を付けてください。
このページをスクロールしてください。記事の上のバーは CSS の値だけで伸びます。利用側の JavaScript はありません。
段落 1: スクロールを続けてください — 上のバーは記事の幾何から計算された読了率を、CSS カスタムプロパティ経由で反映しています。
段落 2: スクロールを続けてください — 上のバーは記事の幾何から計算された読了率を、CSS カスタムプロパティ経由で反映しています。
段落 3: スクロールを続けてください — 上のバーは記事の幾何から計算された読了率を、CSS カスタムプロパティ経由で反映しています。
段落 4: スクロールを続けてください — 上のバーは記事の幾何から計算された読了率を、CSS カスタムプロパティ経由で反映しています。
段落 5: スクロールを続けてください — 上のバーは記事の幾何から計算された読了率を、CSS カスタムプロパティ経由で反映しています。
段落 6: スクロールを続けてください — 上のバーは記事の幾何から計算された読了率を、CSS カスタムプロパティ経由で反映しています。
段落 7: スクロールを続けてください — 上のバーは記事の幾何から計算された読了率を、CSS カスタムプロパティ経由で反映しています。
段落 8: スクロールを続けてください — 上のバーは記事の幾何から計算された読了率を、CSS カスタムプロパティ経由で反映しています。
段落 9: スクロールを続けてください — 上のバーは記事の幾何から計算された読了率を、CSS カスタムプロパティ経由で反映しています。
段落 10: スクロールを続けてください — 上のバーは記事の幾何から計算された読了率を、CSS カスタムプロパティ経由で反映しています。
段落 11: スクロールを続けてください — 上のバーは記事の幾何から計算された読了率を、CSS カスタムプロパティ経由で反映しています。
段落 12: スクロールを続けてください — 上のバーは記事の幾何から計算された読了率を、CSS カスタムプロパティ経由で反映しています。
段落 13: スクロールを続けてください — 上のバーは記事の幾何から計算された読了率を、CSS カスタムプロパティ経由で反映しています。
段落 14: スクロールを続けてください — 上のバーは記事の幾何から計算された読了率を、CSS カスタムプロパティ経由で反映しています。
段落 15: スクロールを続けてください — 上のバーは記事の幾何から計算された読了率を、CSS カスタムプロパティ経由で反映しています。
段落 16: スクロールを続けてください — 上のバーは記事の幾何から計算された読了率を、CSS カスタムプロパティ経由で反映しています。
段落 17: スクロールを続けてください — 上のバーは記事の幾何から計算された読了率を、CSS カスタムプロパティ経由で反映しています。
段落 18: スクロールを続けてください — 上のバーは記事の幾何から計算された読了率を、CSS カスタムプロパティ経由で反映しています。
段落 19: スクロールを続けてください — 上のバーは記事の幾何から計算された読了率を、CSS カスタムプロパティ経由で反映しています。
段落 20: スクロールを続けてください — 上のバーは記事の幾何から計算された読了率を、CSS カスタムプロパティ経由で反映しています。
段落 21: スクロールを続けてください — 上のバーは記事の幾何から計算された読了率を、CSS カスタムプロパティ経由で反映しています。
段落 22: スクロールを続けてください — 上のバーは記事の幾何から計算された読了率を、CSS カスタムプロパティ経由で反映しています。
段落 23: スクロールを続けてください — 上のバーは記事の幾何から計算された読了率を、CSS カスタムプロパティ経由で反映しています。
段落 24: スクロールを続けてください — 上のバーは記事の幾何から計算された読了率を、CSS カスタムプロパティ経由で反映しています。
段落 25: スクロールを続けてください — 上のバーは記事の幾何から計算された読了率を、CSS カスタムプロパティ経由で反映しています。
段落 26: スクロールを続けてください — 上のバーは記事の幾何から計算された読了率を、CSS カスタムプロパティ経由で反映しています。
段落 27: スクロールを続けてください — 上のバーは記事の幾何から計算された読了率を、CSS カスタムプロパティ経由で反映しています。
段落 28: スクロールを続けてください — 上のバーは記事の幾何から計算された読了率を、CSS カスタムプロパティ経由で反映しています。
キーボード操作
このコンポーネント自体はキーボード操作を持ちません。
<%# reading-progress: the article publishes its progress as a CSS custom property
on itself AND on :root; the bar here consumes it with pure CSS (no consumer
JS). Scroll the page to move the bar. %>
<div class="reading-progress-demo">
<p class="reading-progress-demo__hint"><%= t("components.reading_progress.demo.hint") %></p>
<div class="reading-progress-demo__bar" aria-hidden="true"></div>
<article class="reading-progress-demo__article" data-controller="stimeo--reading-progress">
<% 28.times do |i| %>
<p><%= t("components.reading_progress.demo.paragraph", index: i + 1) %></p>
<% end %>
</article>
</div>
/*
* Presentation-only styles for the reading-progress demo. The library maintains
* --stimeo--reading-progress (0..1); the bar's width is pure CSS from that
* variable. The bar is sticky so it stays visible while the article scrolls.
*/
.reading-progress-demo__hint {
margin: 0 0 0.75rem;
color: var(--muted);
}
.reading-progress-demo__bar {
position: sticky;
/* Clear the site header: pinned at 0 the bar sits behind it, so the reader sees
nothing move even though the progress value is changing. */
top: var(--header-h, 3.5rem);
z-index: 31;
height: 0.375rem;
border-radius: 0.25rem;
background: var(--border);
overflow: hidden;
}
.reading-progress-demo__bar::before {
content: "";
display: block;
height: 100%;
width: calc(var(--stimeo--reading-progress, 0) * 100%);
background: var(--accent);
}
/*
* The article must be decisively taller than the viewport: progress is
* -top / (height - viewportHeight), so an article shorter than the viewport is
* binary (0 -> 1) by contract and the bar never fills gradually. Real text plus
* a vh floor keeps the demo progressive on any screen size.
*
* The floor also has to be tall enough to dominate the page it sits on: with only
* a little more than one viewport of slack, the bar reads 0 through the page header
* and is already full long before the footer, which looks broken rather than
* gradual. The paragraphs carry the article past two viewports on their own.
*
* The paragraph count is what actually fills those three viewports — the floor is a
* safety net for very tall screens, not the thing holding the article open. Too few
* paragraphs and the article ends in a wide blank band that reads as a rendering
* fault rather than as an article.
*/
.reading-progress-demo__article {
margin-top: 0.75rem;
min-height: 200vh;
}
.reading-progress-demo__article p {
margin: 0 0 1.5rem;
color: var(--fg);
}
このデモに固有の消費側 JS はありません(挙動はコントローラが担います)。
これらのスタイルは共通のデザイントークン(ライト/ダーク両対応)を使います。共通スタイルも一緒にコピーし、ルート要素の data-theme を切り替えればダークになります。
このコンポーネントを動かすために HTML へ記述する data-* 属性です。ルート要素に下の data-controller を付け、その内側に各 target / value / action を配置します。
ルート要素に付与
data-controller="stimeo--reading-progress"
イベント
| 名前 | 説明 | イベント |
|---|---|---|
change
|
進捗が動くたびに { progress } と共に発火(rAF 粒度)。 |
stimeo--reading-progress:change |
complete
|
進捗が 1 に到達したときに発火。 | stimeo--reading-progress:complete |
状態フック
ライブラリが操作するのはこれらの ARIA / data 属性、カスタムプロパティだけです。見た目は利用側 CSS がこれらに反応して作ります([aria-selected] / [aria-expanded] / var(--stimeo--…) などのセレクタでフックします)。
| フック | 対象 | 意味 |
|---|---|---|
--stimeo--reading-progress |
コントローラ要素+document ルート(CSS カスタムプロパティ) | 読了進捗 0..1(ルート側は disconnect で除去)。 |