Embed p5js sketches (relatively) easily in idyll with this custom component! Source code for the component and this page in this github repository
[Sketch webGL:1 ratio:`3/1` sketch:`(p5) => {
p5.draw = () => {
const frame = p5.frameCount;
p5.background(250);
p5.rotateY(frame * 0.01);
for(var j = 0; j < 5; j++){
p5.push();
for(var i = 0; i < 80; i++){
p5.translate(
p5.sin(frame * 0.001 + j) * 100,
p5.sin(frame * 0.001 + j) * 100,
i * 0.1
);
p5.rotateZ(frame * 0.002);
p5.push();
p5.sphere(8, 6, 4);
p5.pop();
}
p5.pop();
}
}
}` /]
(WebGL example taken from p5js examples website)
To embed a sketch, we have to use instance mode. The code is passed as a string representing the function body of an instance mode sketch. So:
function sketch(p5){
p5.setup = () => { /* setup code */ };
p5.draw = () => { /* draw code */ };
// etc.
}
Becomes:
[Sketch sketch:`(p5, options) => {
p5.setup = () => {
/* DO NOT USE createCanvas here! */
};
p5.draw = () => { /* draw code */ };
// etc.
}` /]
There are a few more subtle differences with regular p5 code, and a few added conveniences to play nice with idyll. First, the Sketch component handles the sketch size. That is probably worth emphasizing:
IMPORTANT: DO NOT USEcreateCanvas
in p5.setup! The size depends on the container that holds the sketch, and the Sketch component handles this logic. This is explained in more detail below, at Sketch size and ratio.
Because this function body does not have access to the global browser scope, it is passed variables via the options parameter to make life little easier:
width
and height
, which work like the normal p5 width/height. Note that this means you can use them before setup()
. See “Size and ratio” belowdevicePixelRatio
, which matches window.devicePixelRatio
. Useful in case you want to respond to zoom- or high density display-related effect on the pixels.updates
, use this function to trigger an update to an Idyll variable[Sketch sketch:`(p5, { width, height }) => {
p5.setup = () => {
/* DO NOT USE createCanvas here! */
};
p5.draw = () => { /* draw code */ };
// etc.
}` /]
any variables that are defineed in the Idyll document are automatically in the scope of the sketch function. So
[var name:"x" value:10 ]
[Sketch sketch:`(p5, { width, height }) => {
p5.draw = () => {
// you can reference x here
const xSquared = x * x;
};
}` /]
The sketch unmounts and resets in response to resize events to keep a correct size. To try this out, resize the browser window, or switch between portrain/landscape if you are on mobile.
If the sketch needs to run specific code before unmounting the component, define a p5.unmount
function. If will be triggered just before the component unmounts.
Background Color: Line Color:
Code for the above demo:
[var name:"bgColor" value:5 /]
[var name:"lineColor" value:250 /]
[Sketch
bgColor:bgColor
sketch:`(p5, { width, height }) => {
let size = 25;
p5.setup = () => {
// no createCanvas required!
}
p5.draw = () => {
p5.fill(bgColor, 16);
p5.noStroke();
p5.rect(0, 0, width, height);
size = 40 + 10*p5.sin(p5.frameCount * p5.TAU / 60);
p5.stroke(lineColor);
p5.strokeWeight(size);
p5.line(p5.mouseX, p5.mouseY, p5.pmouseX, p5.pmouseY);
};
p5.unmount = () => {
console.log('The sketch was unmounted. Width was ' +
width + ', height was ' + height);
}
}` /]
Background Color: [Range min:0 max:255 value:bgColor /]
Line Color: [Range min:0 max:255 value:lineColor /]
The sketch can use the updateProps
option to trigger an update in an Idyll variable. Click on the
sketch to make it invert its colors.
Right now you have to pass in the property explicity if you
want Idyll to be able to modify it. Thats why clickBgColor
and clickLineColor
are provided to the
Sketch component. I think this is something that should be updated in Idyll.
[var name:"clickBgColor" value:0 /]
[var name:"clickLineColor" value:255 /]
[Sketch
clickBgColor:clickBgColor
clickLineColor:clickLineColor
ratio:`4/1`
sketch:`(p5, { width, height, updateProps }) => {
let size = 25;
p5.draw = () => {
p5.fill(clickBgColor, 16);
p5.noStroke();
p5.rect(0, 0, width, height);
p5.fill((128+clickLineColor)/2);
let size = 300 - 300*p5.cos(p5.frameCount * p5.TAU / 240);
p5.ellipse(width/2, height/2, size, size);
};
p5.mouseClicked = () => {
updateProps({
clickBgColor: 255 - clickBgColor,
});
}
}` /]
[Sketch
clickBgColor:clickBgColor
clickLineColor:clickLineColor
ratio:`4/1`
sketch:`(p5, { width, height, updateProps }) => {
// because this sketch is derived each time,
// frame is reset each time. This is not
// expected behaviour (from p5 users POV).
let frame = 0;
p5.draw = () => {
p5.noStroke();
p5.fill((128+clickBgColor)/2, 16);
p5.rect(0, 0, width, height);
p5.fill(clickLineColor);
let size = 300 - 300*p5.cos(frame * p5.TAU / 240);
p5.ellips
frame++;
};
p5.mouseClicked = () => {
updateProps({
clickLineColor: 255 - clickLineColor
});
}
}` /]
[var
name:"mouseSketch"
value:`(p5, { width, height, updateProps }) => {
let clickedX = 0, clickedY = 0;
let pressedX = 0, pressedY = 0;
let releasedX = 0, releasedY = 0;
let movedX = 0, movedY = 0;
let draggedX = 0, draggedY = 0;
let wheelVal = 0;
p5.draw = () => {
p5.background(0);
p5.noStroke();
p5.textSize(16);
p5.fill(0, 0, 255);
p5.text('clicked: ' + clickedX + ', ' + clickedY, 32, 20);
p5.fill(255, 255, 0);
p5.text('pressed: ' + pressedX + ', ' + pressedX, 32, 40);
p5.fill(255, 0, 255);
p5.text('released: ' + releasedX + ', ' + releasedY, 32, 60);
p5.fill(0, 255, 0);
p5.text('moved: ' + movedX + ', ' + movedY, 32, 80);
p5.fill(255, 0, 0);
p5.text('dragged: ' + draggedX + ', ' + draggedY, 32, 100);
p5.fill(0, 255, 255);
p5.text('wheel: ' + wheelVal, 32, 120);
};
p5.mouseClicked = () => {
clickedX = p5.mouseX;
clickedY = p5.mouseY;
}
p5.mousePressed = () => {
pressedX = p5.mouseX;
pressedY = p5.mouseY;
}
p5.mouseReleased = () => {
releasedX = p5.mouseX;
releasedY = p5.mouseY;
}
p5.mouseMoved = () => {
movedX = p5.mouseX;
movedY = p5.mouseY;
}
p5.mouseDragged = () => {
draggedX = p5.mouseX;
draggedY = p5.mouseY;
}
p5.mouseWheel = (event) => {
wheelVal += event.delta;
// prevent scrolling when
// mouse is on sketch
// return false;
}
}` /]
[Sketch
ratio:`4/1`
sketch:mouseSketch /]
[Sketch
ratio:`4/1`
alwaysListen:1
sketch:mouseSketch /]
[jobleonard note]:
I can't seem to get the touch events to behave right (and they only work on Chrome for me, not on Firefox). It's probably easier to just advice people to rely on the mouse triggers.[var
name:"touchSketch"
value:`(p5, { width, height, updateProps }) => {
let start = 0, ended = 0;
let movedX = 0, movedY = 0;
p5.draw = () => {
p5.background(0);
p5.noStroke();
p5.textSize(16);
p5.fill(0, 0, 255);
p5.text('touch start: ' + start, 32, 20);
p5.fill(255, 255, 0);
p5.text('touch moved: ' + movedX + ', ' + movedY, 32, 40);
p5.fill(255, 0, 255);
p5.text('ended: ' + ended, 32, 60);
p5.fill(0, 255, 0);
};
p5.touchStarted = () => {
start++;
}
p5.touchMoved = () => {
movedX = p5.touches[0].x;
movedY = p5.touches[0].y;
}
p5.touchEnded = () => {
ended++;
}
}` /]
[Sketch
ratio:`4/1`
sketch:touchSketch /]
[Sketch
ratio:`4/1`
alwaysListen:1
sketch:touchSketch /]
[var
name:"keySketch"
value:`(p5, { width, height, updateProps }) => {
let _keyPressed = '';
let _keyReleased = '';
let _keyTyped = '';
p5.draw = () => {
p5.background(0);
p5.fill(255);
p5.textSize(16);
p5.text('keyPressed: ' + _keyPressed, 20, 20);
p5.text('keyReleased: ' + _keyReleased, 20, 40);
p5.text('keyTyped: ' + _keyTyped, 20, 60);
};
p5.keyPressed = () => {
_keyPressed += p5.key;
};
p5.keyReleased = () => {
_keyReleased += p5.key;
};
p5.keyTyped = () => {
_keyTyped += p5.key;
};
} ` /]
[Sketch
ratio:`4/1`
sketch:keySketch /]
[Sketch
ratio:`4/1`
alwaysListen:1
sketch:keySketch /]
By default, the width of the sketch is equal to column width of the text, and height will be half of the width.
You can pass a value to width
and height
to override this. The value can be the number of pixels like 100
, or a CSS-valid string like "50%"
or "2em"
.
By default, height depends on width, but width does not depend on height. Passing a value to width
will make height
half of the new width. Passing a value to height
will not affect width
, which will still be as wide as the text column.
To enforce a ratio, you can pass a number to ratio
, i.e. the expression ratio:`3/1`
will produce a sketch with a width three times the height.
If no width is defined, but a height is, width depends on height (as you can see below, this part is still a bit buggy). If a ratio, width and height are defined, height is overridden to depend on width.
[var name:"sketch_ratio" value:`(p5) => {
p5.setup = () => {
p5.noLoop();
};
p5.draw = () => {
p5.background(0);
};
}` /]
[Sketch
sketch:sketch_ratio /]
[Sketch
ratio:`4/1`
sketch:sketch_ratio /]
[Sketch
height:50
sketch:sketch_ratio /]
[Sketch
ratio:`2/1`
width:"50%"
sketch:sketch_ratio /]
[Sketch
ratio:`2/1`
width:200
height:200
sketch:sketch_ratio /]
[Sketch
ratio:`2/1`
height:50
sketch:sketch_ratio /]
A watchedVal
triggers a reset of the sketch whenever the value it watches is changed:
[var name:"resetSketch" value:0 /]
[var name:"sketch_reset" value:`(p5, {width, height}) => {
let x = width/2, y = height/2, dx = 0;
p5.draw = () => {
p5.fill(0, 4);
p5.noStroke();
p5.rect(0, 0, width, height);
p5.stroke(256);
const dx_scaled = dx / (1<<4);
p5.strokeWeight(20 + dx_scaled);
p5.line(x, y, x+dx_scaled, y);
x = (x+dx_scaled)%width;
p5.line(x-dx_scaled, y, x, y);
dx++;
};
}` /]
[Sketch
height:100
watchedVal:resetSketch
sketch:sketch_reset /]
[Button onClick:`resetSketch++`]Reset Sketch![/Button]