🏗️ What Is a Template — and Why Use One?
A template is a pre-built starter file (or pair of files) that handles all of the repetitive, mechanical plumbing of an app so the developer can focus entirely on the creative part. Think of it the way a contractor thinks about a building shell: the walls, roof, plumbing, and wiring are already in place; the interior designer just decides what goes inside.
In this SketchWave project the template provides:
- A responsive Bootstrap page layout with a nav bar, hero, and footer
- A p5.js canvas that auto-sizes to its column and re-scales on window resize
- A coordinate-grid overlay (toggleable) so students can place objects precisely
- A background colour picker and opacity slider
- Start / Pause / Reset / Save-Image controls
- A consistent HSB color system via
SWColor - A theme-color system that tints the page header and nav icon automatically
All of that infrastructure is locked away in p5jsTemplate…BaseSketch.js. The student only ever edits the two files in the green boxes to the right.
Student edits this file.
- Grid bounds and frame rate
- Theme color initialization
- All drawing code in
drawMyDesign(ctx) - (v2 only) Background & grid defaults
- (v2 only) One-time startup data in
initMyDesign()
Infrastructure — do not edit.
- p5.js
setup()anddraw() - Canvas sizing and resize handling
- Grid rendering (
SWGrid) - Control panel wiring (buttons, sliders)
- Theme-color application to the page
- Calls
drawMyDesign(ctx)each frame
⚖️ Pros and Cons of Using a Template
| ✅ Pros — what the template gives you | ❌ Cons — what you give up |
|---|---|
| Focus on creativity. All the canvas sizing, control wiring, and color setup is done. You draw; the template handles everything else. | It’s a black box at first. New students
don’t know why setup() calls wireControls() or what
ctx contains. That’s OK — but it can feel like magic. |
| Consistent UI everywhere. Every sketch built on the template has the same nav bar, same grid toggle, same save button. Reviewers know exactly where to look. | Overhead for tiny sketches. If all you want is
three bouncing circles, loading Bootstrap + SWColor + SWGrid + SWPoint
is overkill. A blank <canvas> tag runs faster and simpler. |
Responsive out of the box. The template’s
myResizeCanvas() fires on every window resize, so your sketch
looks right on phones, tablets, and desktop monitors without any extra code. |
Version confusion. If a v1 user file is paired
with a v2 base sketch (or vice versa), missing constants like
DEFAULT_BG_HEX cause silent failures. File versions must match. |
Shared vocabulary. Students who all use the same
template can read each other’s drawMyDesign(ctx) immediately —
they already know the context object, the grid, and the controls. |
You inherit the template’s choices. HSB color mode, a fixed canvas-column layout, and a particular set of controls are baked in. Changing them requires editing the base file, which breaks the “don’t edit the base” rule. |
🔧 Four Improvements from v1 to v2
Template v1 was already a big win over writing everything from scratch. But testing it with real student projects revealed four friction points. Here is what changed in v2 and why each change makes the template better to use.
🎨 Improvement 1 — Background Defaults Belong to the User, Not the Engine
In v1, the starting background colour and opacity were declared as constants inside the base sketch — the file students aren’t supposed to touch:
| ❌ v1 — buried in the base sketch | ✅ v2 — declared in the user sketch |
|---|---|
Inside p5jsTemplate1Sketch.js:const DEFAULT_BG_HEX = '#d1d1d4';const DEFAULT_BG_OPACITY = 100;
|
Inside myAppSketch2.js:const DEFAULT_BG_HEX = '#d1d1d4';const DEFAULT_BG_OPACITY = 100;
|
A student working on a dark space sketch wants a black background. In v1, they had to
open the base file, hunt for DEFAULT_BG_HEX, change it, and hope they
didn’t accidentally break something else along the way. In v2, they just change
the constant in their own file.
The principle: configuration that the user is expected to change should live in the file the user is expected to edit. When a template buries user-facing settings inside engine code, it forces students to violate the golden rule just to do something basic. That teaches bad habits.
📐 Improvement 2 — Grid Visibility Is Also a User Choice
The same problem applied to whether the grid is visible at startup. In v1 it was a constant in the base sketch:
| ❌ v1 — buried in the base sketch | ✅ v2 — declared in the user sketch |
|---|---|
Inside p5jsTemplate1Sketch.js:const DEFAULT_SHOW_GRID = true;
|
Inside myAppSketch2.js:const DEFAULT_SHOW_GRID = true;
|
A student whose sketch is a polished final product probably wants the grid
off by default. In v1, hiding the grid required editing the base. In v2
it’s a single flag in myAppSketch2.js.
Together, Improvements 1 and 2 follow a software-design guideline sometimes called the Open/Closed Principle applied to templates: the base sketch is closed to modification but open to customization through declared constants in the user file. The engine reads those constants at startup and seeds the controls from them — no engine edit required.
⏳ Improvement 3 — A Lifecycle Hook for Startup Data
v1 gave students only one entry point: drawMyDesign(ctx), which is called
every single frame. That is the right place for drawing. It is the
wrong place for work that should only happen once at startup.
Consider a sketch that pre-generates 200 random star positions. In v1, a student might write:
function drawMyDesign(ctx) {
let stars = [];
for (let i = 0; i < 200; i++) {
stars.push({ x: random(width), y: random(width) });
}
// ... draw stars ...
}
The stars flicker wildly because every frame re-randomises them. The fix is to
generate them once, but that code needs width and
height, which don’t exist until p5.js creates the canvas inside
setup(). Module-level code (outside any function) runs before
setup(), so width and height are zero there.
v2 solves this by adding a lifecycle hook: an optional function the base sketch calls at exactly the right moment.
What happens in setup() |
Timing | Width/Height valid? |
|---|---|---|
createCanvas(…) |
Canvas is created | ✅ Yes — from here on |
initMyDesign() v2 only |
Immediately after canvas | ✅ Yes — safe to use |
| Module-level code | Script load time — before setup() |
❌ No — zero |
let stars = [];
function initMyDesign() {
for (let i = 0; i < 200; i++) {
stars.push({ x: random(width), y: random(height) });
}
}
function drawMyDesign(ctx) {
// stars already exists, just draw them
for (let s of stars) {
point(s.x, s.y);
}
}
The concept: lifecycle hooks. Most frameworks (React, Vue, Android,
iOS, game engines) give you named functions that fire at specific moments in an
object’s life: created, mounted, updated,
destroyed. initMyDesign() is the same idea at a
student-friendly scale. Run setup work once, run drawing code every frame.
Never mix the two.
📦 Improvement 4 — Script Load Order: Provider Before Consumer
The v2 HTML file loads the user sketch before the base sketch. This is not an accident — it is required for v2 to work at all.
| ❌ Wrong order — base loads first | ✅ Correct order — user loads first |
|---|---|
<script src="p5jsBaseSketch.js"></script><script src="myAppSketch2.js"></script>When base loads, DEFAULT_BG_HEX doesn’t exist yet.
SWColor.fromHex(DEFAULT_BG_HEX, …)
crashes or silently uses undefined.
|
<script src="myAppSketch2.js"></script><script src="p5jsBaseSketch.js"></script>When base loads, DEFAULT_BG_HEX is already defined.
wireControls() seeds the colour picker correctly.
|
The browser processes <script> tags in document order.
When p5jsTemplate2BaseSketch.js runs, the JavaScript engine has
already parsed myAppSketch2.js, so every constant that belongs to
the user file — DEFAULT_BG_HEX, DEFAULT_BG_OPACITY,
DEFAULT_SHOW_GRID — is a live, named variable.
v1 had the same physical load order in the HTML (user first, then base), but because the defaults lived in the base file, the order did not matter as much. v2 actually depends on it, and the HTML comment explains why:
“User sketch loaded FIRST so its constants and hooks are available when setup() runs.”
The rule: load providers before consumers. The same rule
applies to every dependency in every programming language —
import statements, header files, require() calls,
Maven dependencies — the thing being used must exist before the thing
that uses it. In plain HTML scripts, order in the file is the
dependency mechanism.
| Topic | v1 behaviour | v2 behaviour |
|---|---|---|
DEFAULT_BG_HEX & DEFAULT_BG_OPACITY |
Declared in the base sketch (don’t edit file) | Declared in the user sketch (edit freely) |
DEFAULT_SHOW_GRID |
Declared in the base sketch (don’t edit file) | Declared in the user sketch (edit freely) |
One-time startup code with valid width/height |
No hook — tricky to do correctly | initMyDesign() called after createCanvas() |
| Script load order | User first, then base — but didn’t technically matter for defaults | User must load first; HTML comment explains why |
| To change background colour | Edit base file (⚠️ risky) | Edit user file (✅ safe) |
| To hide grid at startup | Edit base file (⚠️ risky) | Edit user file (✅ safe) |
| Pre-generate canvas-size-dependent data | Hard (width = 0 at module level) | Easy — use initMyDesign() |
📜 Source Code
Browse all four files. The user-sketch pair is the most instructive for novices; the base-sketch pair shows how the engine was updated to support the new v2 design.
fetch(url).then(r => r.text()).
Loading sketches/myAppSketch2.js…
Loading sketches/myAppSketch.js…
Loading sketches/p5jsTemplate2BaseSketch.js…
Loading sketches/p5jsTemplate1Sketch.js…
💪 Try It Yourself
Open p5jsTemplate2.html
in your browser and try these challenges to prove the improvements to yourself.
Use the Copy buttons above to grab either user sketch as a
starting point.
-
Change the background colour (Improvement 1) —
Open
sketches/myAppSketch2.js. ChangeDEFAULT_BG_HEXfrom'#d1d1d4'to'#0e0e0e'(near-black) and reloadp5jsTemplate2.html. Notice the colour picker in the control panel starts at your new value automatically. Then open the v1 page (p5jsTemplate1.html) and try the same — you’ll have to hunt inside the base sketch instead. -
Hide the grid at startup (Improvement 2) —
In
myAppSketch2.js, changeDEFAULT_SHOW_GRIDfromtruetofalse. Reload. The grid is hidden when the page opens, but the toggle checkbox still works. Only one character changed in the user file; the base sketch required no edit at all. -
Use
initMyDesign()for stars (Improvement 3) — Replace the body ofinitMyDesign()with:
for (let i = 0; i < 200; i++) { stars.push({ x: random(width), y: random(height) }); }
and addlet stars = [];at the top of the file. Then indrawMyDesign(ctx), loop overstarsand draw them withpoint(s.x, s.y). Notice the stars are stable — they don’t flicker each frame. -
Break the load order (Improvement 4) —
In
p5jsTemplate2.html, swap the two script tags so the base sketch loads before the user sketch. Reload and open the browser console (F12). Look for an error aboutDEFAULT_BG_HEXbeing undefined. Swap them back and confirm the error disappears. -
Compare both templates live —
Open
p5jsTemplate1.htmlandp5jsTemplate2.htmlside by side. They look identical — the improvements are all about developer experience, not user-visible features. That is a hallmark of good refactoring.
⚠️ The Copy buttons require a secure context
(https:// or localhost). Viewing from a local file:// URL?
Right-click the tab and choose “View Page Source” instead.