multiple canvases on one page

I can't get more than one sketch to run on a webpage. Each works fine when it's on its own. I have in the sketches

var c1 = createCanvas(200, 200);
c1.parent('firstcanvas');

and

var c2 = createCanvas(300, 300);
c2.parent('secondcanvas');

and, in the html

<div id = "firstcanvas"></div>
<script src="pathto/firstscript.js" type="text/javascript"></script>
<div id = "secondcanvas"></div>
<script src="pathto/secondscript.js" type="text/javascript"></script>

Inspect shows

<div id="firstcanvas"></div>
<script src="pathto/firstscript.js" type="text/javascript"></script>
<canvas id="defaultCanvas0" width="200" height="200" style="width: 200px; height: 200px;"></canvas>
<div id = "secondcanvas"></div>
<script src="pathto/secondscript.js" type="text/javascript"></script>

and the webpage just shows the first sketch. I've tried various combos of div and canvas, with no luck.

<div id="firstcanvas">
<script src="pathto/firstscript.js" type="text/javascript"></script>
<canvas id="firstcanvas" width="200" height="200" style="width: 200px; height: 200px;"></canvas>
</div>

and

<canvas id="firstcanvas">
<script src="pathto/firstscript.js" type="text/javascript"></script>
</canvas>

with no luck. If I put ordinary javascript animations on the page, as long as I target the right canvas id, they work fine together. Any ideas?

Answers

  • Ha! I nearly wrote at the end of my question that I've looked an instance mode and don't understand it.

    The example is

    var sketch = function (p) {
      var gray = 0;
    
      p.setup = function () {
        p.createCanvas(600, 400);
      };
    
      p.draw = function () {
        p.background(gray);
        p.rect(p.width/2, p.height/2, 200, 200);
      };
    
      p.mousePressed = function () {
        gray = (gray + 16) % 256;
      };
    };
    
    new p5(sketch, node);
    

    Am I right in inferring:

    • 'p' and 'sketch' can be anything as long as I use the same string throughout?
    • all functions and reserved variables need to be prefixed by whatever string I choose instead of 'p'?
    • the whole sketch (including what I would call classes in ordinary Processing) needs to be inside this new function?
    • so functions inside 'classes' would look like this?

       p.ClassName.prototype.myfunction = function() 
       {
       }
      

    I don't get what 'node' is, and don't see if I have to reference the html parent element for my canvas.
    Could you expand, please?

  • edited June 2016

    1) p and sketch can be anything as long as I use the same string throughout?

    • They're not strings but variables. And in the case of p, it's more appropriately called a parameter.
    • And the answer is yes. And we can use any name for variables.
    • p is merely a Processing's convention. :)

    2) All functions & reserved variables need to be prefixed by whatever string I choose instead of p?

    That's a tricky question and demands a huge answer! #:-S

    • The parameter p holds the reference to an instance object of class p5.
    • We can look up its API going to this link btW: http://p5js.org/reference/
    • Theoretically only properties of class p5 should be prefixed w/ parameter p.
    • But in practice we can prefix everything w/o 1 w/ p. But that'd be bad separation of concerns.
    • So var gray = 0; if we wish could be written instead: p.gray = 0;.
    • And gray = (gray + 16) % 256; would be p.gray = (p.gray + 16) % 256;
    • However, given that class p5 doesn't have any member called gray, appending 1 to it would be considered bad practice. =;
    • And even worse, if there was indeed a gray member, we'd be overwriting it w/ our own! :-SS
    • Here's a very good example: http://CodePen.io/anon/pen/zqbZYV?editors=0010
    • Notice that global constant URL & variable img aren't prefixed w/ parameter p.
    • And even function loadImgErrFix() isn't either! :-bd
  • edited June 2016

    3) The whole sketch (including what I would call classes in ordinary Processing) needs to be inside this new function?

    • In Processing Java, all ".pde" tab files are merged as 1 ".java" before compilation.
    • So in reality, there's only 1 sketch class, even though they're spread by many ".pde" files.
    • And since there's only 1 top class, our own classes are nested to it.
    • That's why we can access PApplet members even inside our nested classes w/o any prefix.
    • But if we want top classes instead of nested 1s, we place them in ".java" tabs instead of ".pde" 1s.
    • That behavior is mirrored in p5.js too. We pass 1 function callback as argument to its constructor.
    • And everything inside that callback access the parameter p in order to reach p5's API.
    • But if you wish for something to be outside that big callback, it won't be able to access anything!
    • Unless it requests the p5 reference as 1 of its parameters. *-:)
    • Just like a class defined in a ".java" tab file would require a PApplet reference.

    4) So functions inside 'classes' would look like this? p.ClassName.prototype.myfunction = function() {}

    5) I don't get what 'node' is, ...

    • I've never used it myself. My suggestion is to simply drop it! :P
    • Seems like its feature is to automatically parent() the createCanvas() to some specific Node defined in the ".html" file.
    1. https://developer.Mozilla.org/en-US/docs/Web/API/Node
    2. http://p5js.org/reference/#/p5.Element/parent
    3. http://p5js.org/reference/#/p5/createCanvas
  • Thx, gotoloop [insert overwhelmed emoticon here ;) ].

    All I would like to do is to run more than one P5 sketch on a webpage, and I have achieved that by putting the scripts on separate pages and using iframes to show them. This may be an inelegant solution but as I don't have to go through (theoretically) loads of codes inserting "p." before every function, it'll do for me.

    For completeness, though, by class I meant a class - in my normal Processing code this is usually a Ball class - not a pde tab. So question 4 still stands:

    • if I use instance mode, do the functions inside classes still need to be prefixed by "p."?
  • edited June 2016

    ... I have achieved that by putting the scripts on separate pages and using iframes to show them.

    Actually that's the best solution for performance reasons. B/c each <iframe> is a separate thread: \m/
    http://StackOverflow.com/questions/11329487/iframe-cost-of-implementing-an-iframe-in-a-web-application

    If I use instance mode, do the functions inside classes still need to be prefixed by p.?

    Those are answered in 2) & 4). Gonna try to elaborate more... >-)

    • Only those properties which belong to class p5 demand to be prefixed w/ its reference.
    • In this case, that reference is the callback's parameter p.
    • Those we create ourselves don't. Although we can still do it. But it's bad programming.
    • ClassName doesn't belong to class p5's API. So we shouldn't prefix it w/ p..
    • Again, you're better off using the keyword class in order to implement your classes in JS:
      https://developer.Mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
  • @allonestring: I've been inspired to write a tutorial post on instance mode which will hopefully explain what it does...

  • edited June 2016

    ... in my normal Processing code this is usually a Ball class - not a ".pde" tab.

    • If it's not a ".pde" tab, then it's a ".java" tab file.
    • Classes defined in ".java" files which happen to also need to access Processing's API, especially the sketch's "canvas", demand the reference of the PApplet "sketch" object.
    • That's why we pass this to almost all 3rd-party libraries: new Minim(this);.
    • If your class or function is defined outside the big callback sketch, they're gonna need the p5 reference the same way a Java library needs the PApplet reference.
    • So in your app, if you need an instance of class ClassName, and that class is defined outside your big sketch callback, its instantiation should be something like this: new ClassName(p);.
  • Thank you both, but although allonestring != anIdiot; I'm afraid that
    allonestring != anExpert; and, more unfortunately, allonestring.age > theAgeAtWhichThingsAreEasytoLearn; so I'm still struggling to understand.

    Below are two very simple sketches that I have in P5. Would you mind making just the changes necessary to make them work in instance mode? I know, of course, that the code itself could be improved, but it's my understanding of instance mode that needs improving here, please.

    sketch 1:

        var num = 13;
        var balls = [];
        var bgcolour;
    
    
        function setup() 
        {
          createCanvas(300, 300);
    
          bgcolour = color(random(217, 255), random(217, 255), random(217, 255));
          background(bgcolour);
    
          for(var i = 0; i < num; i++)
          {
            balls[i] = new Ball();
           }
          noStroke();
        }
    
        function draw() 
        {
          background(bgcolour);
          for(var i = 0; i < num; i++)
          {
            balls[i].display();
            balls[i].update();
          }
        }
    
        function Ball()
        {
          this.makeBall();
        }
    
        Ball.prototype.makeBall = function()
        {
            var v = 2;
            this.pos = createVector(random(width), random(height));
            this.vel = createVector(random(-v, v), random(-v, v));
            this.colour = color(random(255), random(255), random(255));
            this.radius = random(5, 30);
        }
    
        Ball.prototype.update = function()
        {
          this.pos.add(this.vel);
    
          if(this.pos.x > width || this.pos.x < 0) this.vel.x *= -1;
          if(this.pos.y > height || this.pos.y < 0) this.vel.y *= -1;
        }
    
        Ball.prototype.display = function()
        {
          push();
          translate(this.pos.x, this.pos.y);
          fill(this.colour);  
          ellipse(0, 0, 2 * this.radius, 2 * this.radius);
          pop();
        }
    

    and sketch 2:

    var num = 3;
    var bodies = [];
    var bgcolour;
    
    function setup() {
      createCanvas(400, 400);
      bgcolour = color(20, 20, 40);
    
      for (var i = 0; i < num; i++) {
        bodies[i] = new Body();
        bodies[i].radius = 30 - 10 * i;
        bodies[i].rate = i * 0.01;
      }
        bodies[0].orbit = 0;
        bodies[1].orbit = 80;
        bodies[2].orbit = 35;
    }
    
    function draw() {
      background(bgcolour);
    
      push();
      translate(width / 2, height / 2);
      for (var i = 0; i < num; i++) {
        rotate(bodies[i].rot);
        translate(bodies[i].orbit, 0);
        bodies[i].display()
        bodies[i].update();
      }
      pop();
    }
    
    function Body() {
      this.rot = random(TWO_PI);
      this.colour = color(random(255), random(255), random(255));
    }
    
    Body.prototype.update = function() {
      this.rot += this.rate;
    }
    
    Body.prototype.display = function() {
      fill(this.colour);
      ellipse(0, 0, 2 * this.radius, 2 * this.radius);
    }
    

    Thx! allonestring.feeling = stupid;

  • @allonestring - did you check my instance mode example with two sketches? Today I also added examples of using instance mode with external classes.

    I'd suggest looking at the latter example first and trying to convert one of your sketches to instance mode. If that doesn't work post your attempt here. If you do get that working get the second sketch working separately in instance mode and then try getting them both running alongside each other...

  • edited June 2018

    @allonestring, I wonder if you had already read:
    https://GitHub.com/processing/p5.js/wiki/Processing-transition

    Lotsa conversion tips from Java Mode to p5.js above. O:-)

    Anyways, I've converted your 1st ES5 JS sketch to ES6 using class structure. :ar!
    Watch it online here: http://p5ide.HerokuApp.com/editor#?sketch=577310949e3f9603000ab5bc

    <script async src=http://p5js.org/assets/js/p5.min.js></script>
    
    <script>
    
    /**
     * Bouncing Colorful Balls [Global Mode] (v1.2)
     * AllOneString & GoToLoop (2016-Jun-28)
     *
     * https://Forum.Processing.org/two/discussion/17306/
     * multiple-canvases-on-one-page#Item_12
     *
     * http://p5ide.HerokuApp.com/editor#?sketch=577310949e3f9603000ab5bc
     * http://CodePen.io/GoSubRoutine/pen/KaerGb/right/?editors=101
    */
    
    "use strict";
    
    const NUM = 15, balls = Array(NUM);
    let bg;
    
    function setup() {
      createCanvas(400, 400).mousePressed(restart);
      noStroke();
      bg = color(random(0xd0, 0x100), random(0xd0, 0x100), random(0xd0, 0x100));
      for (let i = 0; i < NUM; balls[i++] = new Ball);
    }
    
    function draw() {
      background(bg);
      for (const b of balls)  b.display().update();
    }
    
    function restart() {
      bg = color(random(0xd0, 0o400), random(0xd0, 0o400), random(0xd0, 0o400));
      for (const b of balls)  b.initBall();
    }
    
    class Ball {
      static get VEL() { delete this.VEL; return this.VEL = 2; }
      static get MIN_RAD() { delete this.MIN_RAD; return this.MIN_RAD = 5; }
      static get MAX_RAD() { delete this.MAX_RAD; return this.MAX_RAD = 30; }
    
      constructor() {
        this.pos = createVector(), this.vel = createVector();
        this.initBall();
      }
    
      initBall() {
        const r = this.rad = random(Ball.MIN_RAD, Ball.MAX_RAD), v = Ball.VEL;
        this.pos.set(random(r, width - r), random(r, height - r));
        this.vel.set(random(-v, v), random(-v, v));
        this.c = color('#' + hex(~~random(0x1000), 3));
        return this;
      }
    
      update() {
        const { pos, rad } = this;
        pos.add(this.vel);
        pos.x > width  - rad | pos.x < rad && (this.vel.x *= -1);
        pos.y > height - rad | pos.y < rad && (this.vel.y *= -1);
        return this;
      }
    
      display() {
        fill(this.c).ellipse(this.pos.x, this.pos.y, this.rad<<1);
        return this;
      }
    }
    
    </script>
    
  • edited February 2017 Answer ✓

    Now the same sketch above converted from global to instance mode. See it online below: :-bd
    http://CodePen.io/anon/pen/RRpJGm?editors=0010

    Recall that class p5, those that need prefixing w/ p., can be looked up at p5.js' reference: L-)
    http://p5js.org/reference/

    <script async src=http://p5js.org/assets/js/p5.min.js></script>
    
    <script>
    
    /**
     * Bouncing Colorful Balls [Instance Mode] (v1.1.1)
     * AllOneString & GoToLoop (2016-Jun-28)
     *
     * https://forum.Processing.org/two/discussion/17306/multiple-canvases-on-one-page
     *
     * http://CodePen.io/anon/pen/RRpJGm?editors=0010
     * http://p5ide.HerokuApp.com/editor#?sketch=577310949e3f9603000ab5bc
    */
    
    "use strict";
    
    new p5(p => {
      const NUM = 15, balls = Array(NUM);
      let bg;
    
      p.setup = () => {
        p.createCanvas(400, 400);
        p.noStroke();
        for (let i = 0; i < NUM; balls[i++] = new Ball);
        bg = p.color(p.random(0xd0, 0x100), p.random(0xd0, 0x100), p.random(0xd0, 0x100));
      };
    
      p.draw = () => {
        p.background(bg);
        for (let b of balls)  b.display(), b.update();
      };
    
      p.mousePressed = () => {
        for (let b of balls)  b.initBall();
        bg = p.color(p.random(0xd0, 0o400), p.random(0xd0, 0o400), p.random(0xd0, 0o400));
      };
    
      class Ball {
        static get VEL() { return 2; }
        static get MIN_RAD() { return 5; }
        static get MAX_RAD() { return 30; }
    
        constructor () {
          this.pos = p.createVector(), this.vel = p.createVector();
          this.initBall();
        }
    
        initBall() {
          const r = this.rad = p.random(Ball.MIN_RAD, Ball.MAX_RAD), v = Ball.VEL;
          this.pos.set(p.random(r, p.width - r), p.random(r, p.height - r));
          this.vel.set(p.random(-v, v), p.random(-v, v));
          this.c = p.color('#' + p.hex(~~p.random(0x1000), 3));
        }
    
        update() {
          const {pos, rad} = this, {width: w, height: h} = p;
          pos.add(this.vel);
          pos.x > w - rad | pos.x < rad && (this.vel.x *= -1);
          pos.y > h - rad | pos.y < rad && (this.vel.y *= -1);
        }
    
        display() {
          p.fill(this.c).ellipse(this.pos.x, this.pos.y, this.rad<<1);
        }
      }
    });
    
    </script>
    
  • Thank you blindfish and gotoloop for your patience. I hope, for your sakes, that you never start to lose your marbles.

    for(i = today; i < endOfLife; i += day) allonestring.marbles--;

  • edited February 2017

    No worries, as long as you're making progress! :))
    Nonetheless, I did your 2nd "Orbits" sketch too.
    Watch it online here: http://p5ide.HerokuApp.com/editor#?sketch=577369489e3f9603000ab5c6

    <script async src=http://p5js.org/assets/js/p5.min.js></script>
    
    <script>
    
    /**
     * Orbits [Global Mode] (v1.0)
     * AllOneString & GoToLoop (2016-Jun-29)
     *
     * https://forum.Processing.org/two/discussion/17306/multiple-canvases-on-one-page
     *
     * http://p5ide.HerokuApp.com/editor#?sketch=577369489e3f9603000ab5c6
     * http://CodePen.io/anon/pen/RRpJGm?editors=0010
    */
    
    "use strict";
    
    const ORBS = Uint8Array.of(0, 80, 35), bodies = Array(ORBS.length);
    let bg;
    
    function setup() {
      createCanvas(300, 300);
      stroke(0).strokeWeight(2.5);
      bg = color(random(0xd0, 0x100), random(0xd0, 0x100), random(0xd0, 0x100));
      for (let i = 0; i < ORBS.length; ++i)
        bodies[i] = new Body(ORBS[i], i*.01, Body.BASE_RAD - i*10);
    }
    
    function draw() {
      background(bg).translate(width>>1, height>>1);
      for (let b of bodies)  b.display(), b.update();
    }
    
    function mousePressed() {
      bg = color(random(0xd0, 0o400), random(0xd0, 0o400), random(0xd0, 0o400));
      for (let b of bodies)  b.initBody();
    }
    
    class Body {
      static get BASE_RAD() { return 30; }
    
      constructor (orbit, rate, radius) {
        this.orb = orbit, this.spd = rate, this.rad = radius;
        this.initBody();
      }
    
      initBody() {
        this.rot = random(TAU);
        this.c = color('#' + hex(~~random(0x1000), 3));
      }
    
      update() { this.rot += this.spd; }
    
      display() {
        rotate(this.rot).translate(this.orb, 0);
        fill(this.c).ellipse(0, 0, this.rad<<1);
      }
    }
    
    </script>
    
  • edited February 2017
    • For the Instance Mode version, I've decided to do it slightly diff.
    • This time I'm gonna place class Body outside the big sketch callback. ;;)
    • It's equivalent in having the class in a ".java" tab file under Java Mode, btW.
    • As you already know by now, it's not gonna have direct access to the sketch's p5 reference in the same vein under Java Mode a top class doesn't have for the PApplet.
    • To get around that, class Body is gonna request the p5 object as an extra parameter for its constructor and keep it around as a property too.
    • An important condition is that class Body gotta exist before instantiating the main p5's sketch.
    • Also gonna split the whole sketch as 3 files: "instance.html", "Body.js" & "orbits.js".
    • And as always, it's available online too: http://CodePen.io/anon/pen/ZOeqOy?editors=0010

    "instance.html":

    <script defer src=http://p5js.org/assets/js/p5.min.js></script>
    <script defer src=Body.js></script>
    <script defer src=orbits.js></script>
    

    "Body.js":

    class Body {
      static get BASE_RAD() { return 30; }
    
      constructor (sketch, orbit, rate, radius) {
        this.p = sketch;
        this.orb = orbit, this.spd = rate, this.rad = radius;
        this.initBody();
      }
    
      initBody() {
        const {random, hex, TAU} = this.p;
        this.rot = random(TAU);
        this.c = this.p.color('#' + hex(~~random(0x1000), 3));
      }
    
      update() { this.rot += this.spd; }
    
      display() {
        this.p.rotate(this.rot).translate(this.orb, 0);
        this.p.fill(this.c).ellipse(0, 0, this.rad<<1);
      }
    }
    

    "orbits.js":

    /**
     * Orbits [Instance Mode] (v1.0)
     * AllOneString & GoToLoop (2016-Jun-29)
     *
     * https://forum.Processing.org/two/discussion/17306/multiple-canvases-on-one-page
     *
     * http://CodePen.io/anon/pen/RRpJGm?editors=0010
     * http://p5ide.HerokuApp.com/editor#?sketch=577369489e3f9603000ab5c6
    */
    
    new p5(p => {
      "use strict";
    
      const ORBS = Uint8Array.of(0, 80, 35), bodies = Array(ORBS.length);
      let bg;
    
      p.setup = () => {
        p.createCanvas(250, 250);
        p.stroke(0).strokeWeight(2.5);
        bg = p.color(p.random(0xd0, 0x100), p.random(0xd0, 0x100), p.random(0xd0, 0x100));
        for (let i = 0; i < ORBS.length; ++i)
          bodies[i] = new Body(p, ORBS[i], i*.01, Body.BASE_RAD - i*10);
      };
    
      p.draw = () => {
        p.background(bg).translate(p.width>>1, p.height>>1);
        for (let b of bodies)  b.display(), b.update();
      };
    
      p.mousePressed = () => {
        bg = p.color(p.random(0xd0, 0o400), p.random(0xd0, 0o400), p.random(0xd0, 0o400));
        for (let b of bodies)  b.initBody();
      };
    });
    

  • edited August 2016 Answer ✓
    • And finally here's those 2 sketches merged as 1! \m/
    • Check it out online here: http://CodePen.io/anon/pen/zBZMre?editors=0010
    • Gonna post below: "instance.html", "Ball.js" & "bouncing.js" files.
    • Don't forget to include "Body.js" & "orbits.js" files from previous post too. L-)

    "instance.html":

    <script defer src=http://p5js.org/js/p5.min.js></script>
    
    <script defer src=Ball.js></script>
    <script defer src=Body.js></script>
    
    <script defer src=bouncing.js></script>
    <script defer src=orbits.js></script>
    
    <!--
    /**
     * MultiSketches [Instance Mode] (v1.0)
     * AllOneString & GoToLoop (2016-Jun-29)
     *
     * https://forum.Processing.org/two/discussion/17306/multiple-canvases-on-one-page
     * http://CodePen.io/anon/pen/zBZMre?editors=0010
    */
    -->
    

    "Ball.js":

    class Ball {
      static get VEL() { return 2; }
      static get MIN_RAD() { return 5; }
      static get MAX_RAD() { return 30; }
    
      constructor (p) {
        this.p = p;
        this.pos = p.createVector(), this.vel = p.createVector();
        this.initBall();
      }
    
      initBall() {
        const p = this.p,
              r = this.rad = p.random(Ball.MIN_RAD, Ball.MAX_RAD),
              v = Ball.VEL;
        this.pos.set(p.random(r, p.width - r), p.random(r, p.height - r));
        this.vel.set(p.random(-v, v), p.random(-v, v));
        this.c = p.color('#' + p.hex(~~p.random(0x1000), 3));
      }
    
      update() {
        const {pos, rad} = this, {width: w, height: h} = this.p;
        pos.add(this.vel);
        pos.x > w - rad | pos.x < rad && (this.vel.x *= -1);
        pos.y > h - rad | pos.y < rad && (this.vel.y *= -1);
      }
    
      display() {
        this.p.fill(this.c).ellipse(this.pos.x, this.pos.y, this.rad<<1);
      }
    }
    

    "bouncing.js":

    new p5(p => {
      "use strict";
    
      const NUM = 15, balls = Array(NUM);
      let bg;
    
      p.setup = () => {
        p.createCanvas(530, 290);
        p.noStroke();
        for (let i = 0; i < NUM; balls[i++] = new Ball(p));
        bg = p.color(p.random(0xd0, 0x100), p.random(0xd0, 0x100), p.random(0xd0, 0x100));
      };
    
      p.draw = () => {
        p.background(bg);
        for (let b of balls)  b.display(), b.update();
      };
    
      p.mousePressed = () => {
        for (let b of balls)  b.initBall();
        bg = p.color(p.random(0xd0, 0o400), p.random(0xd0, 0o400), p.random(0xd0, 0o400));
      };
    });
    

Sign In or Register to comment.