import { useCallback, useEffect, useRef } from "react";
export function useRafThrottle(callback) {
const callbackRef = useRef(callback);
const frameRef = useRef(null);
const latestArgs = useRef([]);
useEffect(() => {
callbackRef.current = callback;
}, [callback]);
const throttled = useCallback((...args) => {
latestArgs.current = args;
if (frameRef.current !== null) return;
frameRef.current = requestAnimationFrame(() => {
frameRef.current = null;
callbackRef.current(...latestArgs.current);
});
}, []);
useEffect(() => {
return () => {
if (frameRef.current !== null) {
cancelAnimationFrame(frameRef.current);
}
};
}, []);
return throttled;
}
import { useCallback, useEffect, useState } from "react";
import { useRafThrottle } from "./useRafThrottle";
function computeProgress() {
const doc = document.documentElement;
const scrollTop = doc.scrollTop || window.pageYOffset;
const scrollable = doc.scrollHeight - window.innerHeight;
if (scrollable <= 0) return 0;
return scrollTop / scrollable;
}
export function useScrollProgress() {
const [progress, setProgress] = useState(0);
const update = useCallback(() => {
setProgress(computeProgress());
}, []);
const onScroll = useRafThrottle(update);
useEffect(() => {
update();
window.addEventListener("scroll", onScroll, { passive: true });
window.addEventListener("resize", onScroll, { passive: true });
return () => {
window.removeEventListener("scroll", onScroll);
window.removeEventListener("resize", onScroll);
};
}, [onScroll, update]);
return progress;
}
import React from "react";
import { useScrollProgress } from "./useScrollProgress";
export default function ReadingProgressBar({ color = "#2f80ed", height = 3 }) {
const progress = useScrollProgress();
const clamped = Math.min(1, Math.max(0, progress));
return (
<div
aria-hidden="true"
style={{
position: "fixed",
top: 0,
left: 0,
right: 0,
height,
pointerEvents: "none",
zIndex: 1000,
}}
>
<div
style={{
height: "100%",
background: color,
transform: `scaleX(${clamped})`,
transformOrigin: "0 0",
willChange: "transform",
}}
/>
</div>
);
}
A reading progress bar needs to react to every scroll event, but the browser fires scroll far faster than the screen refreshes, so naive handlers thrash layout and drop frames. This snippet splits the problem across three collaborating files: a low-level rAF throttle hook, a useScrollProgress hook that computes how far the document has been read, and a thin presentational component that renders the bar.
In useRafThrottle hook, the throttling is done with requestAnimationFrame rather than a setTimeout interval. The returned function schedules at most one callback per frame: repeated calls before the frame fires just overwrite latestArgs.current, so work coalesces to the display's refresh rate. Reading the DOM inside a rAF callback also aligns measurement with the browser's own paint cycle, which avoids forced synchronous layout. The hook keeps the caller's latest callback in a ref so the throttled function has a stable identity while still invoking fresh logic, and it cancels any pending frame on unmount to prevent calls into an unmounted component.
In useScrollProgress hook, computeProgress derives a 0..1 value from scrollTop, scrollHeight, and the viewport height. The denominator is the scrollable distance, guarded against division by zero for short pages by returning 0 when there is nothing to scroll. The measurement is wrapped in useRafThrottle and bound to both scroll and resize, because a resize changes scrollHeight and must recompute progress. Listeners use { passive: true } so the browser knows the handler never calls preventDefault, letting it scroll on the compositor thread. An initial call to update() seeds progress for pages loaded already scrolled (for example when navigating to an anchor).
In ReadingProgressBar, the numeric progress becomes a scaleX transform, which is cheaper than animating width since transforms skip layout. aria-hidden marks the bar decorative, and clamping keeps the value in range.
The main trade-off is that rAF throttling ties update frequency to frame rate rather than a fixed interval, which is usually exactly what visual UI wants. A pitfall to watch is measuring document.documentElement versus a scrolling container — this implementation assumes the document scrolls, and a container-scroll variant would pass the element into the hook instead.
Related snips
require "csv"
class PeopleCsvStream
include Enumerable
HEADERS = %w[id full_name email signed_up_at plan].freeze
Resilient CSV Export as a Streamed Response
Rails.application.configure do
config.after_initialize do
Bullet.enable = true
Bullet.alert = false
Bullet.bullet_logger = true
Bullet.console = true
N+1 query detection with Bullet gem
json.array! @posts do |post|
json.cache! ['v1', post], expires_in: 1.hour do
json.id post.id
json.title post.title
json.excerpt post.excerpt
json.published_at post.published_at
Fragment caching for expensive JSON serialization
import axios, { AxiosError } from 'axios'
import { v4 as uuidv4 } from 'uuid'
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000/api/v1',
timeout: 15000,
Axios API client with interceptors
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface FilterState {
search: string
category: string | null
Zustand for lightweight state management
package deps
import (
"net"
"net/http"
"time"
HTTP client tuned for production: timeouts, transport, and connection reuse
Share this code
Here's the card — post it anywhere.