5. Binding¶
Calling value() everywhere gets tedious, especially with objects that draw themselves. Binding lets Keyed write the animated value into a property or setter before every draw(), so your code just uses plain variables.
Field binding¶
Keyed.bind(object, 'property') animates a property by name. The property's current value becomes the default. For top-level globals in p5's global mode, that means using var, because let and const are not properties of window.
var x = 60;
var diameter = 20;
function setup() {
Keyed.init().setDuration(3);
Keyed.bind(window, 'x')
.key(Key.at(0).setEasing(1 / 3), 60)
.key(Key.at(1.5).setEasing(1 / 3), 340)
.key(Key.at(3).setEasing(1 / 3), 60);
Keyed.bind(window, 'diameter')
.key(0, 20)
.key(1.5, 80)
.key(3, 20);
}
function draw() {
background(255);
circle(x, 180, diameter);
}
No value() calls: x and diameter are plain numbers, already up to date.
Full sketch: FieldBinding
// Plain variables. Their values here become the defaults.
// Top-level var variables are properties of window, so they can be bound by name; let and const are not.
var x = 60;
var diameter = 20;
function setup() {
createCanvas(400, 400);
Keyed.init().setDuration(3);
textFont('monospace');
textSize(14);
// bind(object, "property") writes the animated value into a property
// before every draw(). It works for properties of any object,
// e.g. Keyed.bind(ball, "x"); here the object is window.
Keyed.bind(window, 'x')
.key(Key.at(0).setEasing(1 / 3), 60)
.key(Key.at(1.5).setEasing(1 / 3), 340)
.key(Key.at(3).setEasing(1 / 3), 60);
Keyed.bind(window, 'diameter')
.key(0, 20)
.key(1.5, 80)
.key(3, 20);
}
function draw() {
background(255);
// No value() calls: x and diameter are plain numbers, already up to date.
noStroke();
fill(0);
circle(x, 180, diameter);
fill(150);
text(`x = ${nf(x, 1, 1)}`, 40, 330);
text(`diameter = ${nf(diameter, 1, 1)}`, 40, 355);
}
Setter binding¶
For any other type, bind a setter: a function that receives the animated value. Pass a Lerp (how to blend, see Types), a default value and the setter:
Keyed.bind(new ColorLerp(), color(255), (c) => (bg = c))
.key(0, color(255, 235, 215))
.key(2, color(215, 230, 255))
.key(4, color(255, 235, 215));
Or call bind() on an existing Keyed. The setter can convert the value, here from an integer to a line of text:
score = Keyed.ofInt()
.key(0, 0)
.key(4, 100)
.bind(showScore);
function showScore(v) {
label = `score ${v}`;
}
unbind()stops the updates, so the target keeps its last value. In the sketch the score freezes for a moment.apply()writes the value right away, without waiting fordraw(), e.g. to use it insetup().
Full sketch: SetterBinding
let bg;
let label = '';
let score;
let bound = true;
function setup() {
createCanvas(400, 400);
Keyed.init().setDuration(4);
textFont('monospace');
textAlign(CENTER, CENTER);
// A Lerp, a default value and a setter, which receives
// the animated value before every draw().
Keyed.bind(new ColorLerp(), color(255), (c) => (bg = c))
.key(0, color(255, 235, 215))
.key(2, color(215, 230, 255))
.key(4, color(255, 235, 215));
// Or bind an existing Keyed. The setter can convert the value,
// here from a number into a line of text.
score = Keyed.ofInt()
.key(0, 0)
.key(4, 100)
.bind(showScore);
// apply() writes the value right away, without waiting for draw(),
// e.g. to use label in setup().
score.apply();
console.log(label);
}
function showScore(v) {
label = `score ${v}`;
}
function draw() {
// No value() calls: bg and label are already up to date.
background(bg);
// text() is outlined by the current stroke in p5.
noStroke();
fill(0);
textSize(32);
text(label, width / 2, 180);
fill(150);
textSize(14);
text(bound ? 'click to unbind' : 'unbound, click to bind', width / 2, 350);
}
function mousePressed() {
// unbind() stops the updates, so label keeps its last value.
// bind() starts them again.
bound = !bound;
if (bound) {
score.bind(showScore);
} else {
score.unbind();
}
}
Binding objects¶
Binding shines with objects. Each Ball gets its y property bound by name, and its color through a setter method. Delaying the keys of each ball a little creates a wave:
for (let i = 0; i < 7; i++) {
const b = new Ball(60 + i * 47, 300);
balls.push(b);
const delay = i * 0.1;
Keyed.bind(b, 'y')
.key(Key.at(delay).setEasing(1 / 3), 300)
.key(Key.at(delay + 0.5).setEasing(1 / 3), 120)
.key(Key.at(delay + 1).setEasing(1 / 3), 300);
Keyed.bind(new ColorLerp(), color(0), (c) => b.setColor(c))
.key(delay, color(0))
.key(delay + 0.5, color(230, 60, 60))
.key(delay + 1, color(0));
}
The balls are always up to date and simply draw themselves.
Full sketch: ObjectBinding
class Ball {
constructor(x, y) {
this.x = x;
this.y = y;
this.col = color(0);
}
setColor(c) {
this.col = c;
}
draw() {
noStroke();
fill(this.col);
circle(this.x, this.y, 30);
}
}
const balls = [];
function setup() {
createCanvas(400, 400);
Keyed.init().setDuration(2);
for (let i = 0; i < 7; i++) {
const b = new Ball(60 + i * 47, 300);
balls.push(b);
// Each ball starts a little later than the one before.
const delay = i * 0.1;
// Binds the y property of this ball, by name.
Keyed.bind(b, 'y')
.key(Key.at(delay).setEasing(1 / 3), 300)
.key(Key.at(delay + 0.5).setEasing(1 / 3), 120)
.key(Key.at(delay + 1).setEasing(1 / 3), 300);
// Colors are objects, so a setter of the ball receives them.
// (Binding the color by name works too: Keyed.bind(b, "col").)
Keyed.bind(new ColorLerp(), color(0), (c) => b.setColor(c))
.key(delay, color(0))
.key(delay + 0.5, color(230, 60, 60))
.key(delay + 1, color(0));
}
}
function draw() {
background(255);
// The balls are already up to date; they just draw themselves.
for (const b of balls) {
b.draw();
}
}
Next: Paths.