idyll-p5

Embed p5.js sketches in idyll!

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: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 USEcreateCanvasin 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:

[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.PI / 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.

[mathisonian note]:

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
  sketch:`(p5, { width, height, updateProps }) => {
    let size = 25;
    p5.draw = () => {
      p5.fill(clickBgColor, 16);
      p5.noStroke();
      p5.rect(0, 0, width, height);
      size = 40 + 10*p5.sin(p5.frameCount * p5.PI / 60);
      p5.stroke(clickLineColor);
      p5.strokeWeight(size);
      p5.line(p5.mouseX, p5.mouseY, p5.pmouseX, p5.pmouseY);
    };

    p5.mouseClicked = () => {
      updateProps({ clickBgColor: 255 - clickBgColor, clickLineColor: 255 - clickLineColor })
    }
}` /]

Sketch size and ratio

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 /]

Manually triggered resets

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]