More than one sketch in same HTML page

My index.html ( relevant code):


  <!--Utilitzem la llibreria p5-->
    <script type="module" src="/js/libraries/p5.min.js"></script>
    <!--Carreguem moduls-->

    <!--Carreguem sketch-->
    <script type="module" src="/js/sketch.js"></script>
    <script type="module" src="/js/secondsketch.js"></script>
<p>Hello world! This is HTML5 Boilerplate.</p>
<div id="sketch-holder"></div>
<p>Pagina hecha con HTML5</p>
<div id="second-sketch-holder"></div>
<img src="/img/imghtml5.jpg" alt="Pacman" width="200" height="200">

secondsketch.js


function setup() {
  // Creates a canvas 600 pixels wide
  // and 400 pixels high.
  createCanvas(400, 200).parent("#second-sketch-holder");
}

function draw() {
    textSize(100);
    text("Valor de a: " + globalThis.a, 10, 10);
}

globalThis.setup = setup;
globalThis.draw = draw;

sketch.js

function setup() {
  // Creates a canvas 600 pixels wide
  // and 400 pixels high.
  createCanvas(600, 400).parent("#sketch-holder");
}

function draw() {
  // sky blue background
  background(135, 206, 235);
  globalThis.a = 5;
  // sun in top right
  fill("yellow"); // yellow

  stroke("orange"); // orange outline

  strokeWeight(20); // large outline

  circle(550, 50, 100);
}

globalThis.setup = setup;
globalThis.draw = draw;

The problem suppose is the name of setup and draw functions that are the same, and as you can see im trying to share a ā€œglobalā€ variable a to transport data from one canvas o sketch to the other…
The idea is one sketch/sketch for the game and the other canvas to act as scoreboard with time and points for example

Thanks

ā€œp5.min.jsā€ isn’t a ECMA Script Module (ESM) file, so type="module" is unneeded, even though in this particular case, it doesn’t have any bad consequences.

Also, starting the path w/ ā€œ/ā€ here it unneeded.

For better performance for regular JS scripts, use defer.
Or even async if there’s no library relying on ā€œp5.jsā€:
<script async src="js/libraries/p5.min.js"></script>

Optionally, you can grab ā€œp5.jsā€ via a CDN:
<script async src="//cdn.JsDelivr.net/npm/p5"></script>

Neither of your 2 sketches are module files.
Again, you don’t need type="module" and you can start their path w/o a leading ā€œ/ā€:

<script async src="js/sketch.js"></script>
<script async src="js/secondsketch.js"></script>

And given they’re not being loaded as modules (non-ESM) anymore, you can delete these lines:

globalThis.setup = setup;
globalThis.draw = draw;
1 Like

For each <iframe>, including the root frame, we can have at most only 1 p5.js sketch using the ā€œglobal modeā€.

Either you create a separate HTML file for 1 of your 2 sketches, so it can be loaded in a separate <iframe> or…

You convert 1 of them to use p5.js ā€œinstance modeā€:

Let’s say you keep your sketch ā€œsketch.jsā€ (the biggest 1) in ā€œglobal modeā€:

"use strict";

var bg, a = 5; // 'a' is a global variable b/c it's' declared w/ `var` keyword

function setup() { // setup is a global variable b/c it's declared w/ `function` 
  createCanvas(600, 400).parent("sketch-holder");
  fill("yellow").stroke("orange").strokeWeight(20);
  bg = color(135, 206, 235); // sky blue
}

function draw() { // draw is a global variable b/c it's declared w/ `function`
  background(bg).circle(550, 50, 100);
}

So now you need to convert your sketch ā€œsecondsketch.jsā€ (the smallest 1) to ā€œinstance modeā€:

"use strict";

new p5(p => {

  p.setup = function () {
    this.createCanvas(400, 200).parent("second-sketch-holder");
    this.textSize(100);
  };

  p.draw = function () {
    this.text("Valor de a: " + a, 10, 10); // var 'a' was defined in "sketch.js"
  };

});

And their HTML file:

<!-- <script async src="js/libraries/p5.min.js"></script> -->
<script async src="//cdn.JsDelivr.net/npm/p5"></script>

<script async src="js/sketch.js"></script>
<script defer src="js/secondsketch.js"></script>

<p>Hello world! This is HTML5 Boilerplate.</p>
<div id="sketch-holder"></div>

<p>Pagina hecha con HTML5</p>
<div id="second-sketch-holder"></div>

<img src="img/imghtml5.jpg" alt="Pacman" width="200" height="200">
1 Like

And can be both of them in instance mode ? i usually make the code and finally when all is working i transform from global to instance mode…

About module:

  • In my tests it works with and without type=ā€œmoduleā€
  • Also works with the only put defer in seconsketch…this thing i dont understand because in theory secondsketch is loaded after sketch, so would not need any extra parameter like defer
  • Global.this = draw; is not necessary in sketch.js Āæ?Āæ

So i make some extra tests…i need type=ā€œmoduleā€ to use p5.js in vercel

I use vite in local, and then deploy to vercel…

secondsketch.js

"use strict";

new p5(p => {

    p.setup = function () {
        this.createCanvas(400, 200).parent("#second-sketch-holder");
        this.textSize(22);
    };

    p.draw = function () {
        this.background(135, 206, 235);
        this.fill('yellow');
        console.log("variable a is: ", a);
        this.text(`${a}`, 10, 50); // var 'a' was defined in "sketch.js"
    };

});

sketch.js

let a=5;

function setup() {
  // Creates a canvas 600 pixels wide
  // and 400 pixels high.
  createCanvas(600, 400).parent("#sketch-holder");
  a++;
}

function draw() {
  // sky blue background
  background(135, 206, 235);
  // sun in top right
  fill("yellow"); // yellow

  stroke("orange"); // orange outline

  strokeWeight(20); // large outline

  circle(550, 50, 100);
}

globalThis.setup = setup;
globalThis.draw = draw;

index.html

    <!--Carreguem sketch-->
    <script type="module" src="/js/sketch.js"></script>
    <script type="module" src="/js/secondsketch.js"></script>

</head>
<body>
<p>Hello world! This is HTML5 Boilerplate.</p>
<div id="sketch-holder"></div>
<p>Pagina hecha con HTML5</p>
<div id="second-sketch-holder"></div>
<img src="/img/imghtml5.jpg" alt="Pacman" width="200" height="200">

</body>

It show the first one but not the second one, with the variable a
Uncaught reference error a is not defined

I get it to work local with vite, but didnt in vercel… so i had to add the type module…but dont know how to get back…i made lots of tests with defers and asyncs…and didnt get with it…

I used to do that because the global mode involved less typing but once the sketch got big this became a pain in the butt. :smile:

Now I do most of my coding in instance mode because although it is more work initially it makes it easy to have multiple canvas sketches.

So here is a basic example of a webpage that has 2 ā€˜sketches’ each with their own canvas.

html file

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Multi-canvas example</title>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.8.0/p5.min.js"></script>

    <script src="multicanvas.js" type="text/javascript"></script>

</head>

<body>
    <p>Main display canvas</p>
    <div id='main_canvas'></div>
    <br><br>
    <p>Sub display canvas</p>
    <div id='sub_canvas'></div>
</body>

</html>

js file

/*
File:   multicanvas.js
Keys:
'm'  sends a message from MAIN canvas to SUB canvas
's'  sends a message from SUB canvas to MAIN canvas
*/

let main_display = function (p) {

    let string = '';

    p.setup = function () {
        p.createCanvas(400, 300);
        p.cursor(p.CROSS);
        p.fill('darkgreen');
        p.textSize(24);
    }

    p.draw = function () {
        p.background('lightgreen');
        p.text(string, 40, 40);
    }

    p.sendMessage = function (message) {
        sub.setMessage(`Message from MAIN canvas \n"${message}"`);
    }

    p.setMessage = function (message) {
        string = message;
    }

    p.keyTyped = function () {
        if (p.key == 'm')
            main.sendMessage('Quark finds a way! ')
    }
}

let sub_display = function (p) {

    let string = '';

    p.setup = function () {
        p.createCanvas(300, 200);
        p.cursor(p.HAND);
        p.fill('darkblue');
        p.textSize(20);
    }

    p.draw = function () {
        p.background('lightblue');
        p.text(string, 20, 40);
    }

    p.sendMessage = function (message) {
        main.setMessage(`Message from SUB canvas \n"${message}"`);
    }

    p.setMessage = function (message) {
        string = message;
    }

    p.keyTyped = function () {
        if (p.key == 's')
            p.sendMessage('Quark seeks the truth')
    }
}

let main = new p5(main_display, 'main_canvas');

let sub = new p5(sub_display, 'sub_canvas');

There are other issues you need to take into account - for instance all key and mouse events on the web page will be processed by both sketches but this example might help get you started.

1 Like

Absolutely! I was just reminding you that up to 1 of the sketches can be kept as ā€œglobal modeā€.

  • If we have async for loading ā€œp5.jsā€ library, the browser will run that file immediately when it’s done loading it.
  • That will make the variable p5 globally available.
  • However, ā€œsecondsketch.jsā€ has this line: new p5(p => {
  • If p5 happens to not exist yet at the moment it’s executed, that code will crash!
  • That’s why I’ve used defer in place of async to load ā€œsecondsketch.jsā€; so its execution will be postponed much later; which by then p5 already had enough time to be made globally available.

If we don’t use type=module to load a file; and make sure we declare variables using either var or function keywords, that will automatically place those variables in globalThis.

Even though the ā€œp5.jsā€ is just a regular JS script, the way it is coded, it doesn’t really matter if we force it to be loaded as an ESM module if we prefer or need it that way.

However, for those 2 ā€œsketchā€ files, you’ll have to make changes to them depending whether you’ll load them as regular JS scripts or as ESM module files.

1 Like
  • You’ve used keyword let instead of var to declare it: let a = 5;
  • Only variables declared w/ keywords var & function can also automatically place them in globalThis.
  • The others: let, const, class, import don’t have such feature!
  • Also, if a file is loaded as an ESM module, no matter which keyword we use, no variables will automatically be placed in globalThis as well!
  • For module files, we have to explicitly use globalThis.
  • Remember that execution behavior changes depending on how a file has been loaded, as a regular JS script, ESM or CommonJS.
1 Like

Thanks for your help,im very grateful and so my students…

1 - With your code let or var is not the problem. You can safely put let a=5: and works…as i told you at least in JS var is almost deprecated, dont know if p5.js still uses it as you said

2 - For now, im trying to get help in Vercel i need to put type=ā€œmoduleā€, and surely when the project gets bigger id need to use imports/exports at least in sketch.js wheres is supposed to be the pacman game. So i need how to share variable a using modules…

3 - Is cleaner or do you recommend use instance mode instead of global mode ?

This is my ā€œfinal codeā€:

index.html

<!doctype html>
<html class="no-js" lang="">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title></title>
  <link rel="stylesheet" href="css/style.css">
  <meta name="description" content="">

  <meta property="og:title" content="">
  <meta property="og:type" content="">
  <meta property="og:url" content="">
  <meta property="og:image" content="">
  <meta property="og:image:alt" content="">

  <link rel="icon" href="/favicon.ico" sizes="any">
  <link rel="icon" href="/icon.svg" type="image/svg+xml">
  <link rel="apple-touch-icon" href="icon.png">

  <link rel="manifest" href="site.webmanifest">
  <meta name="theme-color" content="#fafafa">
    <!--
    Si usamos async para cargar la biblioteca "p5.js", el navegador ejecutarĆ” ese archivo inmediatamente cuando termine de cargarlo.
 That will make the variable p5 globally available. -->
    <script defer type="module" src="js/libraries/p5.min.js"></script>
    <script defer type="module" src="js/sketch.mjs"></script>
    <script defer type="module" src="js/secondsketch.mjs"></script>

</head>

<body>

  <!-- Add your site or application content here -->
  <p>Hello world! This is HTML5 Boilerplate.</p>
  <div id="first-sketch-holder"></div>
   <p>Hello world! This is HTML5 Boilerplate.</p>
  <div id="second-sketch-holder"></div>

</body>

</html>

sketch.mjs

import {Rectangle} from "./classes/Rectangle.mjs";

globalThis.a=5;
let b=8;

const s = ( sketch ) => {

    let myrect = new Rectangle(10, 20);

    sketch.setup = () => {
        // Creates a canvas 600 pixels wide
        // and 400 pixels high.
        sketch.createCanvas(600, 400);
        globalThis.a++;
        b++;
        console.log(myrect.area);
    }

    sketch.draw = () => {
        sketch.background(135, 206, 235);
        // sun in top right
        sketch.fill("yellow"); // yellow

        sketch.stroke("orange"); // orange outline

        sketch.strokeWeight(20); // large outline

        sketch.circle(550, 50, 100);
    };
    sketch.keyPressed = () => {

        if (sketch.keyCode === sketch.RIGHT_ARROW) {
            b++;
            }
        else if (sketch.keyCode === sketch.LEFT_ARROW) {
            b--;
            }
            }//ends function keyPressed
};

let myp5 = new p5(s, document.getElementById('first-sketch-holder'));

export {b}; // Exporting 'b' to be used in "secondsketch.mjs"

secondsketch.mjs

"use strict";
import {b} from "./sketch.mjs";

const s2 = (secondsketch) => {

    secondsketch.setup = () => {
        secondsketch.createCanvas(400, 200);
        secondsketch.textSize(12);
    };

    secondsketch.draw = ()=> {
        secondsketch.background(220);
        secondsketch.text("Valor de a: " + globalThis.a, 20, 50); // var 'a' was defined in "sketch.mjs"
        secondsketch.text("Valor de b: " + b, 20, 80); // var 'a' was defined in "sketch.mjs"

    };

};

let mysecondp5 = new p5(s2, document.getElementById('second-sketch-holder'));

Rectangle.mjs

export class Rectangle {
    constructor(alto, ancho) {
        this.alto = alto;
        this.ancho = ancho;
    }
    // Getter
    get area() {
        return this.calcArea();
    }
    // MƩtodos
    calcArea() {
        return this.alto * this.ancho;
    }
}

1 - As told you seems that let or var is not the problem…but is a little detail using var or let…

2 - if put async it doesnt work:

   <script async type="module" src="js/libraries/p5.min.js"></script>
    <script defer type="module" src="js/sketch.mjs"></script>
    <script defer type="module" src="js/secondsketch.mjs"></script>

Which i dont understand…as p5.min.js doesnt rely or depend on other libraries or files or modules…

Uncaught ReferenceError: p5 is not defined

<anonymous> http://localhost:63342/MeuPacman/js/sketch.mjs:41

2 [sketch.mjs:41:12](http://localhost:63342/MeuPacman/js/sketch.mjs)

3 - This also works: all modules to async…i dont understand because seconsketch depends of variable b in first sketch…

   <script async type="module" src="js/libraries/p5.min.js"></script>
    <script async type="module" src="js/sketch.mjs"></script>
    <script async type="module" src="js/secondsketch.mjs"></script>

4- Without anything it works also…

  <script type="module" src="js/libraries/p5.min.js"></script>
    <script type="module" src="js/sketch.mjs"></script>
    <script type="module" src="js/secondsketch.mjs"></script>

5 -This also works:

    <script src="js/libraries/p5.min.js"></script>
    <script src="js/sketch.mjs"></script>
    <script type="module" src="js/secondsketch.mjs"></script>

So do you ā€œlikeā€ this code ? is more modern and flexible…without using globalThis and using import/export modules in ESM.

Also if i add which it should not hurt after the other scripts…it always fails… I dont know why…

<script type="module" src="js/classes/Rectangle.mjs"></script>

By now id try to test this in Vite, and then in Vercel.

BTW. Is recommended use strict mode? or coding with Eslint is enough ?

There are some particular behaviors for the keyword we choose to declare a variable in JS with. But for most of cases, it won’t matter. But watch out for the rare cases where it actual matters!

AFAIK, both <script> attributes async & defer are ignored when using type=module!
Files loaded via type=module are automatically deferred by default btW.

I’ve come up w/ a better idea on how to ā€œshareā€ variables a & b between ā€œsketch.mjsā€ & ā€œsecondsketch.mjsā€.

Rather than using globalThis or export/import, we can instead add them both as additional properties of the p5.js sketch instance:

ā€œsketch.mjsā€:

// Importing class Rectangle from file "Rectangle.mjs" in subfolder "classes":
import Rectangle from "../classes/Rectangle.mjs";

// Exporting sketch() callback so it can be accessed by "secondsketch.mjs":
export default function sketch(p) { // param "p" is the the newly created sketch
  // Make this p5.js sketch instance "p" see its callback methods:
  p.setup = setup;
  p.draw = draw;
  p.keyPressed = keyPressed;

  p.a = 5; // p5 instance property "a" added
  p.b = 8; // p5 instance property "b" added

  p.myrect = new Rectangle(10, 20); // p5 instance property "myrect" added
}

In the exported callback sketch() above, besides adding the regular setup(), draw() and keyPressed() callbacks to the newly instantiated p5.js sketch p, it also adds 3 more properties: a, b and myrect; so any other file that imports file ā€œsketch.mjsā€ and instantiates its sketch() will also have access to those 3 extra properties:

ā€œsecondsketch.mjsā€:

// Importing sketch() callback from file "sketch.mjs" as firstSketch():
import firstSketch from "./sketch.mjs";

// Exporting sketch() callback so it can be accessed by "index.mjs":
export default function sketch(p) { // param "p" is the the newly created sketch
  // Make this p5.js sketch instance "p" see its callback methods:
  p.setup = setup;
  p.draw = draw;

  // Instantiates firstSketch() and places it in <div> id="first-sketch-holder":
  p.firstSketch = new p5(firstSketch, "first-sketch-holder");

  // "secondsketch.mjs" will be known by "sketch.mjs" as property "panel":
  p.firstSketch.panel = p;
}

As you can spot from the code above, ā€œsecondsketch.mjsā€'s own callback sketch() is instantiating ā€œsketch.mjsā€'s sketch() and keeping that as its property ā€œfirstSketchā€.

This way, ā€œsecondsketch.mjsā€ can access ā€œaā€, ā€œbā€, ā€œmyrectā€ and any other p5.js props from it via its own ā€œfirstSketchā€ property:

ā€œdraw()ā€:

  // Unpack instance properties "a" & "b" from property "firstSketch":
  const { a, b } = this.firstSketch;

  // Instance property "a" from "sketch.mjs" p5.js sketch:
  this.text("Valor de a: " + a, 20, 50);

  // Instance property "b" from "sketch.mjs" p5.js sketch:
  this.text("Valor de b: " + b, 20, 80);

I’ve also created a new file called ā€œindex.mjsā€ which will act as an entry point:
<script type="module" src="index.mjs"></script>

ā€œindex.mjsā€:

// Importing sketch() callback from file "secondsketch.mjs" in subfolder "js":
import sketch from "./js/secondsketch.mjs";

// Instantiates sketch() and places it in <div> id="second-sketch-holder":
new p5(sketch, "second-sketch-holder");

The order is: ā€œindex.htmlā€ loads ā€œindex.mjsā€, which in turn will import ā€œjs/secondsketch.mjsā€; and the latter will import ā€œjs/sketch.mjsā€, which will then import ā€œclasses/Rectangle.mjsā€:

  1. 127.0.0.1 - - [12/Dec/2024] ā€œGET /index.mjs HTTP/1.1ā€ 200 -
  2. 127.0.0.1 - - [12/Dec/2024] ā€œGET /js/secondsketch.mjs HTTP/1.1ā€ 200 -
  3. 127.0.0.1 - - [12/Dec/2024] ā€œGET /js/sketch.mjs HTTP/1.1ā€ 200 -
  4. 127.0.0.1 - - [12/Dec/2024] ā€œGET /classes/Rectangle.mjs HTTP/1.1ā€ 200 -
  5. 127.0.0.1 - - [12/Dec/2024] ā€œGET /favs/site.webmanifest HTTP/1.1ā€ 200 -
  6. 127.0.0.1 - - [12/Dec/2024] ā€œGET /favs/favicon.svg HTTP/1.1ā€ 200 -

The complete code is on my latest repo:

And you can check it out running online here:

Feel free to ask about any part you may not understand.

1 Like

@GoToLoop Thanks for your help…

Do you know about Vercel + Vite…im going mad as code in Vite works fine but in Vercel i got an error:

Uncaught ReferenceError: p5 is not defined

??

Neither! I had to ask a chatbot about them.
It seems like Vercel is a hosting service w/ extra features.

B/c in the HTML file, ā€œp5.jsā€ library is run 1st:
<script defer src="//cdn.JsDelivr.net/npm/p5"></script>

And only then ā€œindex.mjsā€ is run:
<script type="module" src="index.mjs"></script>

Property p5 has to globally exist when ā€œindex.mjsā€ runs this line:
new p5(sketch, "second-sketch-holder");

No idea how Vercel would be interfering w/ the ā€œp5.jsā€ execution order. :confused:

Anyways, in my latest version 1.0.2, I’ve moved everything related on loading p5.js files to a separate HTML file ā€œp5js.htmlā€:

Now, ā€œindex.htmlā€ main file loads ā€œp5js.htmlā€ as an <iframe>:

Maybe moving all of the ā€œp5.jsā€ related stuff to a separate HTML file might help Vercel behave better? :man_shrugging:

1 Like

Thanks…but in local with Vite works fine…
I expect some help of the Vercel community…i keep you updated…

By the way how about p5.sound is deprecated or abandonware ? I readed something but i was paying my attention to this Vercel problem…i read about howler as "preferredĀ· sound library…

Do you know anything ? Som light in the darkness ?

I’ve never used it; only for some tests. But its reference still exists:

1 Like

On my repo version, p5 is accessed twice:

And I make sure ā€œp5.jsā€ runs before ā€œindex.mjsā€; so p5 exists before ā€œindex.mjsā€ runs new p5:

Sorry, but I have no idea how Vercel would interfere w/ that loading order.