App

Bundles

Stats
BundleMinifiedGzipBrotli
index.js8.05 kB3.3 kB2.98 kB

Source

import {
	createSignal,
	onCleanup,
	createMemo,
	createRenderEffect
} from "solid-js";
import { render } from "solid-js/web";

function App() {
	const [lastRenderTime, setLastRenderTime] = createSignal(performance.now());
	const [elapsed, setElapsed] = createSignal(0);
	const [duration, setDuration] = createSignal(5000);
	const formattedElapsed = createMemo(() => (elapsed() / 1000).toFixed(1));

	/** @type {number | null} */
	let frame = null;
	function requestFrame() {
		// Always access signals first so Solid can track the dependencies,
		// regardless of if the frame is null or not.
		if (elapsed() < duration() && frame == null) {
			frame = requestAnimationFrame(now => {
				frame = null;
				const timeToAdd = Math.min(
					now - lastRenderTime(),
					duration() - elapsed()
				);
				setElapsed(prevElapsed => prevElapsed + timeToAdd);
				setLastRenderTime(now);
			});
		}
	}

	createRenderEffect(() => requestFrame());

	onCleanup(() => {
		if (frame != null) {
			cancelAnimationFrame(frame);
		}
	});

	return (
		<>
			<label>
				Elapsed time: <progress value={elapsed() / duration()} />
			</label>
			<div class="elapsed">{formattedElapsed()}s</div>
			<label>
				Duration:{" "}
				<input
					type="range"
					min="1"
					max="20000"
					value={duration()}
					onInput={e => setDuration(e.currentTarget.valueAsNumber)}
				/>
			</label>
			<div>
				<button
					class="btn btn-primary"
					onClick={() => {
						setElapsed(0);
						setLastRenderTime(performance.now());
					}}
				>
					Reset
				</button>
			</div>
		</>
	);
}

render(App, document.getElementById("app"));