astro-loading-indicator icon indicating copy to clipboard operation
astro-loading-indicator copied to clipboard

What about computing the animation duration dinamically based on the time to load the previous page?

Open zanhk opened this issue 9 months ago • 1 comments

<div
	transition:persist
	id="astro-loading-indicator"
	class="astro-loading-indicator"
>
</div>

<style>
	.astro-loading-indicator {
		--progress: 0;
		--animation-duration: 300ms;
		--opacity-animation-duration: 150ms;
		pointer-events: none;
		background-color: hsl(192, 41%, 84%);
		position: fixed;
		z-index: 1031;
		top: 0;
		left: 0;
		width: 100%;
		height: 3px;
		transition:
			transform var(--animation-duration) ease-out,
			opacity var(--opacity-animation-duration)
				var(--opacity-animation-duration) ease-in;
		transform: translate3d(0, 0, 0) scaleX(var(--progress, 0));
		transform-origin: 0;
	}
</style>

<script>
	(() => {
		// This is the initial interval
		let animationDuration = 50;
		let progress = 0.25;
		let opacity = 0;
		let trickleInterval: number | undefined = undefined;

		let preparationTimestamp: number = 0;

		const element = document.getElementById("astro-loading-indicator");

		/** @param {typeof progress} _progress */
		const setProgress = (_progress: number) => {
			progress = _progress;
			element?.style.setProperty("--progress", String(progress));
		};

		/** @param {typeof opacity} _opacity */
		const setOpacity = (_opacity: number) => {
			opacity = _opacity;
			element?.style.setProperty("opacity", String(opacity));
		};

		const setAnimationDuration = (duration: number) => {
			animationDuration = duration;

			let styleAnimationDuration = Math.min(300, animationDuration);

			element?.style.setProperty(
				"--animation-duration",
				`${styleAnimationDuration}ms`,
			);
			element?.style.setProperty(
				"--opacity-animation-duration",
				`${styleAnimationDuration / 2}ms`,
			);

			return styleAnimationDuration;
		};

		setOpacity(opacity);

		document.addEventListener("astro:before-preparation", () => {
			preparationTimestamp = performance.now();
			setOpacity(1);
			trickleInterval = window.setInterval(() => {
				setProgress(progress + Math.random() * 0.05);
			}, animationDuration * 0.05);
		});

		document.addEventListener("astro:before-swap", (ev) => {
			let computedDuration = performance.now() - preparationTimestamp;
			var styleAnimationDuration = setAnimationDuration(computedDuration);

			window.clearInterval(trickleInterval);
			trickleInterval = undefined;

			setProgress(1);

			window.setTimeout(() => {
				setOpacity(0);
			}, styleAnimationDuration / 2);

			window.setTimeout(() => {
				setProgress(0.25);
			}, styleAnimationDuration * 1.5);
		});
	})();
</script>

zanhk avatar Apr 25 '24 12:04 zanhk

I can't remember exactly where it is but @martrapp had been doing experiments about getting the upcoming page loading time for better loaders, but it's not possible because of streaming or something. I'm not in favor of adjusting the loader based on the previous page because there can be many differences between 2 pages (eg. not as heavy, connection downgrade etc) but thanks for the snippet! still useful for anyone wanting it

florian-lefebvre avatar Apr 25 '24 13:04 florian-lefebvre