inputs e.g. slider not work well with p5

Hi everyone,

@mbostock

I am trying to make P5 work with Framework inputs slider, however, whenever the slider changes value, the p5 sketch is drawn multiple times as demoed in the images below.


Could anyone help please? Below is my code inside Framework

<div id="obsNotebook"></div>
const sketch = (p) => {
  p.setup = () => {
    p.createCanvas(200, 100).parent("obsNotebook");
  };

  p.draw = () => {
    p.background(250);
    p.textSize(32);
    p.text("totalNum: " + totalSpheres, 10, 30);
  };
}

new p5(p => sketch(p));


const totalSpheres = view(
  Inputs.range(
    [1, 100],
    {step: 1, label: "totalSpheres", value: 5, description: "total number of spheres"}
  )
);
1 Like

You’ll need to call instance.remove().

See p5 / Tom MacWright | Observable for an implementation with Observable notebooks, which I ported to Framework here: p5.js | Pangea Proxima.

1 Like

Here’s my take:

import P5 from "npm:p5";

function p5(sketch) {
  const node = document.body.appendChild(document.createElement("div"));
  const p = new P5(sketch, node);
  const draw = p.draw;
  p.draw = () => node.isConnected ? draw.apply(p, arguments) : p.remove();
  return node;
}

You can use like so:

const color = view(Inputs.color({label: "Color", value: "#dc3f74"}));
p5((p) => {
  p.setup = () => {
    p.createCanvas(width, 300);
    p.textAlign(p.CENTER);
    p.textFont("sans-serif");
    p.textStyle(p.BOLD);
  };
  const c = p.color(color);
  p.draw = () => {
    p.translate((p.millis() / 10) % p.width, p.height / 2);
    p.clear();
    p.fill(c).textSize(100);
    p.text("p5.js", 0, 0);
  };
})

The idea is that the draw method checks whether the sketch’s node is still connected, and if it’s not, it calls p.remove to terminate the animation loop. This way when you trigger the sketch to be re-created, as when the input value changes, you’re getting a new sketch and throwing away the old one.

Also note that you can write this in a way that doesn’t re-recreate the sketch when the input changes, and instead refers to the latest input value within the draw loop. That looks like this:

const colorInput = display(Inputs.color({label: "Color", value: "#dc3f74"}));
p5((p) => {
  p.setup = () => {
    p.createCanvas(width, 300);
    p.textAlign(p.CENTER);
    p.textFont("sans-serif");
    p.textStyle(p.BOLD);
  };
  p.draw = () => {
    const c = p.color(colorInput.value);
    p.translate((p.millis() / 10) % p.width, p.height / 2);
    p.clear();
    p.fill(c).textSize(100);
    p.text("p5.js", 0, 0);
  };
})

Use whichever technique you prefer!

I’ve also opened a small PR so that p5 is a built-in, so this “just works” without needing to import anything or writing a helper.

1 Like

Thank you both so so much! @mbostock @Fil

with the helper function, it saved so much of the mess I have to write to get the code running:

  1. no longer need to create an instance of P5 each time I create a sketch;
  2. no longer need to append the sketch to a div to avoid the sketch is thrown to the bottom of the page;
  3. no long need to worry about duplicate sketches when using Inputs

More importantly, because of your helper function, the strange feeling of working in instance mode is almost gone, as now it feels almost exactly like working in the familiar global mode of P5.js! Thank you so much!

2 Likes

Hi @mbostock @Fil

I have new use case in which the wonderful helper function won’t remove the duplicate of canvas for me. when you click on any of the sliders, the sketch directly above it will keep duplicating. Here is the link to the demo. Nature of Code Original Demos | Code in Steps

I tried to add some code to the helper function but didn’t work. Could you help to make the helper func work on this case too? you can click “source code” details to see the code


update:

As the problem caused by this new use case is the setup function get triggered or the redraw function get triggered under the scene, and your helper function seems not deal with this function at the moment.

Of course, I can avoid this problem by adding p.draw function. However, I would love to see the helper func can automatically handle all use cases.


another update:

Even without the new use case, I still experience some sort of flickering (I can notice the sketch is duplicated and then quickly being removed) when click or drag the slider (50% times I can see the flickering effect, 50% of times are just perfect). Can such flickering effect be eliminated? (maybe flickering is the wrong word, apologize for my English)

The problem is you’re inserting the created canvas into another part of the page when you say e.g. .parent("randomDemo1"). That prevents it from being cleaned up automatically, which is this line here in the code I posted:

  p.draw = () => node.isConnected ? draw.apply(p, arguments) : p.remove();

I would avoid trying to “insert content elsewhere” and using global identifiers in the DOM, and stick to the pattern I suggested where the code is local to where it is displayed. Or, you could use the invalidation promise to clean up the canvases you are inserting elsewhere, but that’s probably more work.

1 Like

Thank you so much @mbostock , you are absolutely right about the problem I am running into. After implementing the code as you suggested, the problem solved!

1 Like