Published
Edited
Jun 2, 2020
3 stars
Insert cell
Insert cell
blue_circle = {
// cx = location | cr = radius
const anim_circle = minanim.const("cx", 100)
.seq(minanim.const("cr", 0))
// (1) grow in size.
.seq(minanim.interpolated(minanim.ease_cubic, "cr", /*val=*/10, /*time=*/3))
// (2) go to right while growing.
.seq(minanim.interpolated(minanim.ease_cubic, "cx", /*val=*/300, /*time=*/1)
.par(minanim.interpolated(minanim.ease_cubic, "cr", 70, 1)))
// (3) pause.
.seq(minanim.delay(/*time=*/3))
// (4) come back to the left.
.seq(minanim.interpolated(minanim.ease_cubic, "cx", 100, 1))
// (5) pause again.
.seq(minanim.delay(/*time=*/2))
// (6) shrink to nothing.
.seq(minanim.interpolated(minanim.ease_cubic, "cr", 0, 1))
.seq(minanim.delay(/*time=*/3));
const ctx = DOM.context2d(400, 200);
let t = 0;
const out = {cx: 0, cr: 0};
const totalSeconds = 60 * 6;
const norm = anim_circle.duration / totalSeconds
while(true) {
ctx.fillStyle = "white";
ctx.fillRect(0, 0, 400, 200);
ctx.beginPath();
ctx.fillStyle = "#1a73e8";
anim_circle(t * norm, out);
ctx.arc(out.cx, 100, out.cr, 0, Math.PI * 2);
ctx.fill();
yield ctx.canvas
t = (t + 1) % totalSeconds;
}
}
Insert cell
delayed_circles = {
const ctx = DOM.context2d(400, 200);
const anim_circles_start = [];
const anim_circles_enter = [];
const anim_circles_leave = [];
const STAGGER = 80;
const NCIRCLES = 10;
for (let i = 0; i < NCIRCLES; i++) {
anim_circles_start.push(
minanim.const("cx" + i, 10 + i*20)
.seq(minanim.const("cy" + i, 300))
.seq(minanim.const("cr" + i, 7))
);
anim_circles_enter.push(minanim.interpolated(minanim.ease_cubic, "cy" + i, 100, 300));
anim_circles_leave.push(minanim.interpolated(minanim.ease_cubic, "cr" + i, 0, 800));
}

const anim = minanim.parallel_list(anim_circles_start)
.seq(minanim.stagger([
minanim.stagger(anim_circles_enter, STAGGER),
minanim.stagger(anim_circles_leave, STAGGER)
], 300));

const totalSeconds = 60 * 10;
const norm = anim.duration / totalSeconds
let t = 0;
while(true) {
ctx.fillStyle = "white";
ctx.fillRect(0, 0, 400, 200);
ctx.beginPath();
ctx.fillStyle = "#1a73e8";
const out = anim(t * norm, {});
for (let i = 0; i < NCIRCLES; i++) {
const cx = out["cx" + i], cy = out["cy" + i];
ctx.moveTo(cx, cy);
ctx.arc(cx, cy, out["cr" + i], 0, Math.PI * 2);
}
ctx.fill();
yield ctx.canvas
t = (t + 1) % totalSeconds;
}
}
Insert cell
Insert cell
minanim = {
function assert_precondition(t, out, tstart) {
console.assert(typeof(t) === "number");
if (out === undefined) { out = {}; }
console.assert(typeof(out) === "object");
if (tstart === undefined) { tstart = 0; }
else { console.assert(typeof(tstart) === "number"); }
console.assert(t >= tstart);
return [out, tstart];
}

function anim_delay(duration) {
console.assert(typeof(duration) === "number");
let f = function(t, out, tstart) {
[out, tstart] = assert_precondition(t, out, tstart); return out;
}
f.duration = duration;
f.par = ((g) => anim_parallel(f, g));
f.seq = ((g) => anim_sequence(f, g));
return f;
}

function anim_const(field, v) {
let f = function(t, out, tstart) {
[out, tstart] = assert_precondition(t, out, tstart); out[field] = v; return out;
};
f.duration = 0;
f.par = ((g) => anim_parallel(f, g));
f.seq = ((g) => anim_sequence(f, g));
return f;
}

function ease_linear(vstart, tlin, vend) { return (1.0 - tlin) * vstart + tlin * vend; }

function ease_cubic(vstart, tlin, vend) {
const cube = (1 - tlin)*(1-tlin)*(1-tlin); return cube * vstart + (1 - cube) * vend;
}
function ease_out_back(vstart, tlin, vend) {
const c1 = 1.70158; const c3 = c1 + 1; const t = 1 + c3 * Math.pow(tlin - 1, 3) + c1 * Math.pow(tlin - 1, 2);
return (1-t) * vstart + t*vend;
}


function anim_interpolated(fease, field, vend, duration) {
let f = function(t, out, tstart) {
[out, tstart] = assert_precondition(t, out, tstart);
if (t < tstart + duration && duration !== 0) {
const tlin = (t - tstart) /duration;
console.assert(tlin >= 0);
console.assert(tlin <= 1);
const vstart = out[field];
out[field] = fease(vstart, tlin, vend);
} else { out[field] = vend; }
return out;
};
f.duration = duration;
f.par = ((g) => anim_parallel(f, g));
f.seq = ((g) => anim_sequence(f, g));
return f;

}

function anim_sequence(anim1, anim2) {
const duration = anim1.duration + anim2.duration;
let f = function(t, out, tstart) {
[out, tstart] = assert_precondition(t, out, tstart);
anim1(t, out, tstart);
if (t >= tstart + anim1.duration) { anim2(t, out, tstart + anim1.duration); }
return out;
}
f.duration = duration;
f.par = ((g) => anim_parallel(f, g));
f.seq = ((g) => anim_sequence(f, g));
return f;
}

function anim_parallel(anim1, anim2) {
const duration = Math.max(anim1.duration, anim2.duration);
let f = function(t, out, tstart) {
[out, tstart] = assert_precondition(t, out, tstart);
if (t >= tstart) { anim1(t, out, tstart); anim2(t, out, tstart); }
return out;
}
f.duration = duration;
f.par = ((g) => anim_parallel(f, g));
f.seq = ((g) => anim_sequence(f, g));
return f;
}

function anim_sequence_list(xs) { var x = xs[0]; for(var i = 1; i < xs.length; ++i) { x = x.seq(xs[i]); } return x; }
function anim_parallel_list(xs) { var x = xs[0]; for(var i = 1; i < xs.length; ++i) { x = x.par(xs[i]); } return x; }

function anim_stagger(xs, delta) {
console.assert(typeof(delta) == "number");
var ys = [];
for(var i = 0; i < xs.length; ++i) {
ys.push(anim_delay(delta*i).seq(xs[i]));
}
var y = ys[0];
for(var i = 1; i < ys.length; ++i) {
y = y.par(ys[i]);
}
return y;
}

return {
assert_precondition,
delay: anim_delay,
const: anim_const,
ease_linear,
ease_cubic,
ease_out_back,
interpolated: anim_interpolated,
sequence: anim_sequence,
parallel: anim_parallel,
sequence_list: anim_sequence_list,
parallel_list: anim_parallel_list,
stagger: anim_stagger,
}
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more