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"> </div>
{/each}
<style>
.circle {
@apply
fixed
h-16
bordered
border-black
shadow-lg
w-16 rounded-full
bg-yellow-100;
z-index: 500;
}
</style>