Slideshow

Slideshow

Showing off your work

Let's say that you're going to an open-mic/open-projector event in your area to show off some of the smaller projects and sketches you've been working on. The trouble is, when you practice presenting, you have to keep switching between your slide deck and the sketches. If only there was some way to add the p5 sketches you've made to the slideshow.

Oh, wait! What if you did the opposite, and added your sketches and slides into the same p5 program?

That might take a lot of content migration — copying and pasting the text from your slides into the JavaScript program. So for now, let's just use some placeholders.

What we need

  • Slides that can encapsulate the behavior of each of your sketches
  • One type of slide that is static (text/image)
  • Another type of slide that is animated (p5 sketches)
  • A model that holds all the slides, tracks which one is showing, and handles navigation

The sketches

Here are the three sketches we'll be building the slideshow around:

A base class for slides

Every slide — whether it shows text or runs an animation — needs to do two things: set up its initial state, and draw itself each frame. That's a perfect fit for a class with setup() and draw() methods.

Let's define a Slide base class that does nothing by default:

class Slide {
  setup() {}
  draw()  {}
}

Now any specific slide can extend Slide and override whichever methods it needs. The extends keyword means "this is a kind of Slide" — it inherits the empty defaults, and you only write what's different.

Static slides

A title or text slide just needs a constructor to store its text and a draw to put it on screen. The constructor calls super() first — that's required any time you extend a class and write your own constructor — then saves the arguments as properties:

class TitleSlide extends Slide {
  constructor(header, subHeader) {
    super();
    this.header    = header;
    this.subHeader = subHeader;
  }
 
  draw() {
    background(242);
    fill(255, 0, 88);
    if (this.header)    { textSize(48); text(this.header,    width * 0.05, height * 0.22); }
    if (this.subHeader) { textSize(36); text(this.subHeader, width * 0.05, height * 0.33); }
  }
}

Creating one is now just a constructor call with the text you want:

let title = new TitleSlide("HELLO, WORLD!", "subtitle")
let end   = new TitleSlide("Thanks! (◡‿◡✿)", "")

Animated slides

The standalone sketches above each used a bare global variable (let time = 0, let h = 0). That's fine when a sketch runs on its own, but inside a slideshow those globals would all share the same scope and overwrite each other.

Each animated sketch becomes its own class extending Slide. Variables that used to be globals now live on this, which keeps each slide's state completely separate:

class Sketch1 extends Slide {
  setup() {
    this.time = 0;
  }
 
  draw() {
    background(242);
    for (let i = 0; i < 360; i += 3) {
      let x = cos(radians(i)) * 50 + width / 2;
      let y = sin(radians(i)) * 100 + height / 2;
      let w = abs(sin(radians(this.time + i)) * 200);
      let col = map(i, 0, 360, 120, 255);
      noStroke();
      fill(col, 0, 88);
      ellipse(x, y, w, w);
    }
    this.time++;
  }
}

TitleSlide, Sketch1, Sketch2, Sketch3 — they all share the same shape (setup + draw), so they're interchangeable from the outside. This is the real power of a base class.

Modeling the state

Now we need something to hold all the slides and track which one is currently showing. A Model class handles this cleanly:

class Model {
  constructor(slides) {
    this.slides = slides;
    this.index  = 0;
  }
 
  setup() {
    this.current().setup();
  }
 
  draw() {
    this.current().draw();
  }
 
  current() {
    return this.slides[this.index];
  }
 
  next() {
    if (this.index < this.slides.length - 1) {
      this.index++;
      this.current().setup();
    }
  }
 
  back() {
    if (this.index > 0) {
      this.index--;
      this.current().setup();
    }
  }
 
  keyPressed() {
    if (keyCode === LEFT_ARROW)  this.back();
    if (keyCode === RIGHT_ARROW) this.next();
    if (keyCode === 32)          this.next(); // spacebar
  }
}

A few things worth noticing:

  • current() is a small helper that returns this.slides[this.index]. Giving it a name keeps draw(), next(), and back() readable.
  • setup() is called on the first slide when the program starts, and then again on whichever slide you land on each time you navigate. This means each sketch resets cleanly when you return to it — rather than having been running in the background the whole time.

Putting it all together

Click the canvas, then use the left/right arrow keys or spacebar to navigate: