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.