Back to list
tweened-staggered
A tweaked version of the official svelte
tweenedmethod, with two updates: a: it takes an
iDelayconfiguration option, which staggers the transition of individual items in a tweened array, and b: it uses d3.js's interpolation function, which handles interpolation of colors.
tweened-staggered.js
See on Github
import { writable } from "svelte/store";
import { assign, loop, now } from "svelte/internal";
import { linear } from "svelte/easing";
import { interpolate } from "d3-interpolate";
import { scaleLinear } from "d3-scale";
function get_interpolator(a, b, iDelay = 0, length = 0, delay = 0) {
if (a === b || a !== a) return () => a;
const type = typeof a;
if (type !== typeof b || Array.isArray(a) !== Array.isArray(b)) {
throw new Error("Cannot interpolate values of different type");
}
if (Array.isArray(a)) {
const arr = b.map((bi, i) => {
return get_interpolator(a[i], bi, iDelay, b.length, i * iDelay);
});
return (t) => arr.map((fn) => fn(t));
}
if (type === "object") {
if (!a || !b) throw new Error("Object cannot be null");
const keys = Object.keys(b);
const interpolators = {};
keys.forEach((key) => {
interpolators[key] = get_interpolator(
a[key],
b[key],
iDelay,
length,
delay
);
});
return (t) => {
const result = {};
keys.forEach((key) => {
result[key] = interpolators[key](t);
});
return result;
};
}
if (["string", "number"].includes(type)) {
const allDelays = iDelay * length;
const iDuration = 1 - allDelays;
const scale = scaleLinear()
.domain([delay, delay + iDuration])
.range([0, 1])
.clamp(true);
const interpolationFunction = interpolate(a, b);
// console.log()
return (t) => {
return interpolationFunction(scale(t));
// a + Math.max(0, t * maxT - delay) * delta;
};
}
throw new Error(`Cannot interpolate ${type} values`);
}
export function tweened(value, defaults = {}) {
const store = writable(value);
let task;
let target_value = value;
function set(new_value, opts) {
if (value == null) {
store.set((value = new_value));
return Promise.resolve();
}
target_value = new_value;
let previous_task = task;
let started = false;
let {
delay = 0,
duration = 400,
iDelay = 10,
easing = linear,
interpolate = get_interpolator,
} = assign(assign({}, defaults), opts);
if (duration === 0) {
if (previous_task) {
previous_task.abort();
previous_task = null;
}
store.set((value = target_value));
return Promise.resolve();
}
const start = now() + delay;
let fn;
task = loop((now) => {
if (now < start) return true;
if (!started) {
fn = interpolate(value, new_value, iDelay / duration);
if (typeof duration === "function")
duration = duration(value, new_value);
started = true;
}
if (previous_task) {
previous_task.abort();
previous_task = null;
}
const elapsed = now - start;
if (elapsed > duration) {
store.set((value = new_value));
return false;
}
store.set((value = fn(easing(elapsed / duration))));
return true;
});
return task.promise;
}
return {
set,
update: (fn, opts) => set(fn(target_value, value), opts),
subscribe: store.subscribe,
};
}
Usage example:
TweenedStaggeredWrapper.svelte
See on Github
<script>
import { scaleLinear } from "d3-scale"
import { interpolateHcl } from "d3-interpolate"
import SimplexNoise from "simplex-noise"
import { tweened } from "./tweened-staggered"
import move from "./move"
const simplex = new SimplexNoise(0)
const height = 8
const width = 10
const colorScale = scaleLinear()
.domain([0, 1])
.range(["#C3B6DF", "#0B2830"])
.interpolate(interpolateHcl)
let iteration = 3
const createData = () => {
iteration = iteration + 1
return new Array(150).fill(0).map((_, i) => {
const x = (i * 10) / 150
const y = Math.random() * height
const r = Math.max(0, simplex.noise2D(x, y))
return {
x: x - r,
y: y - r,
r,
color: colorScale(Math.random()),
}
})
}
let dots = tweened(createData(), {
duration: 2000,
iDelay: 6,
})
</script>
<svg viewBox="{[-1, -1, width + 2, height + 2].join(' ')}">
{#each $dots as { x, y, r, color }}
<rect
style="{move(x, y)}"
width="{r * 2}"
height="{r * 2}"
fill="{color}"></rect>
{/each}
</svg>
<div class="note">Click to update</div>
<svelte:window
on:click="{() => dots.set(createData())}"
on:touchend="{() => dots.set(createData())}" />
<style>
svg {
width: 100%;
}
rect {
mix-blend-mode: multiply;
}
.note {
position: absolute;
top: 0;
font-style: italic;
color: var(--text-light);
}
</style>
Click to update