Making the floating bubbles on svekyll.com
Svekyll is an exciting new blog tool (Jekyll + Svelte), but it can get boring to read about SEO features and sorting by date or name. Adding an interesting visual effect spices things up, and you can see this when you visit Svekyll.com. The floating bubbles don't distract heavily, but are lighthearted and fun. Here is how to build them yourself in about 70 lines of Svelte code.

Floating Bubbles

It takes two files, one which is the Svelte file (Circles.svelte), and another which is a store (circles.js). I added the store later so that a user could turn off the floating effect. If you click on any bubble, the store keeps track of that so that when you navigate to another page, it maintains the state of the movement. A further simple enhancement could be to store that value into localStorage so it could be restored when you refresh the page, but right now, the state is reset each time you refresh the page.

$lib/circles.js is really simple as you can see:

import { writable } from 'svelte/store';
const stop = writable(false);
export { stop };

That stop method is pulled into the Svelte template and used when someone clicks on it.

The $lib/Circles.svelte file is below. It adds a bunch of circles, and then starts a loop to move them around behind the main content. I think it is a nice non-obtrusive effect.

Notice that Svelte uses a bunch of actual dom calls like document.body.scrollWidth inside the JS. Other frameworks require you to use an abstraction wrapper around the actual call to make sure they can maintain the right internal state. Svelte permits you to work with pure JavaScript most of the time, and removing all the overhead of that extra layer of abstraction is pure bliss.

<script>
    import { stop } from '$lib/circles';
import { onMount } from 'svelte';
	let count = 10;
	let refs = [];
	let reversed = [];

	function getNewValue(val, i) {
		return (
			parseInt(val || '0px', 10) + (reversed[i] ? '-1' : '1') * parseInt(Math.random() * 5, 10)
		);
	}

	function move() {
		for (let i = 0; i < refs.length; i++) {
			const r = refs[i];
			if (r) {
				const top = getNewValue(r.style.top, i);
				const left = getNewValue(r.style.left, i);
				if (top < 0 || left < 0) {
					reversed[i] = false;
				}
				if (document.body.scrollHeight < top + 100 || document.body.scrollWidth < left + 100) {
					reversed[i] = true;
				}
				r.style.top = `${top}px`;
				r.style.left = `${left}px`;
			}
		}
	}

    $: {
        if ($stop) {
            clearInterval(mover);
        } 
    }

    let mover;
	onMount(() => {
		console.log('Added circles');
		for (let i = 0; i < refs.length; i++) {
			refs[i].style = randomStart();
		}

		mover = setInterval(move,50);
	});

	function randomStart() {
		const left = `${parseInt(Math.random() * document.body.scrollWidth, 10) - 75}px`;
		const top = `${parseInt(Math.random() * document.body.scrollWidth, 10) - 75}px`;
		return `left: ${left}; top: ${top}`;
	}
</script>

{#each new Array(count) as i, j}
	<div on:click={() => $stop = !$stop} bind:this={refs[j]} class="circle">&nbsp;</div>
{/each}

<style>
.circle {
    @apply
        fixed
        h-16
        bordered
        border-black
        shadow-lg 
        w-16 rounded-full
        bg-yellow-100;
    z-index: 500;
}
</style>