読了プログレス
stimeo--reading-progress
記事をどこまで読み進めたかを CSS カスタムプロパティで公開する。
stimeo--reading-progress は、要素の中をどこまで読み進めたか(0..1)を計測し、 --stimeo--reading-progress カスタムプロパティとして公開します — 要素自身と document ルートの両方に書くため、ページのどこにある固定バーでも CSS だけで消費できます。IntersectionObserver 単体ではこれを表現できません(背の高い記事がビューポートを通過している間、交差率は一定のまま)— そこで本パーツが rAF スロットリング付きのスクロール数学を所有します。監視は capture フェーズなので、オーバーフローコンテナ内の記事でも動きます。進捗が動くと change、1 に到達すると complete が発火します。バーの描画は消費側 CSS の責務で、装飾バーには aria-hidden を付けてください(意味のある進捗は stimeo--progress の領分)。
このページをスクロールしてください — 記事上部の追従バーは CSS 変数だけで伸びます(消費側 JS なし)。
段落 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 カスタムプロパティ経由で反映しています。
<%# 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">
<% 14.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;
top: 0;
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.
*/
.reading-progress-demo__article {
margin-top: 0.75rem;
min-height: 180vh;
}
.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 で除去)。 |