The blog of Ada Rose Edwards

How I rolled my own presentation solution in a couple of hours with es6

For my talk at the Extensible Web Summit I had limiited time because I had put off working on it until the last minute. I also wanted to run and pause javascript in the slides and because of these constraints, I figured it would be quicker to roll my own solution than to use a pre-existing presentation framework such as

End result:; it has some issues, but for this situation the slides only needed to work in Chrome.


I started off with a custom yeoman template I’d made based upon the gulp webapp generator. It adds es6 transpiling with traceur and browserify (through traceurify). This boilerplate setup allows me to very quickly prototype webapps with es6 and scss.

I did use JQuery 😞 because I was very short on time and it allowed me to cut corners in writing the code.

The slides are hosted on which is a tilde club-like static host.

Currently only chrome and ff are supported and I’ll add iPad support next time I need to give a talk.

How it works

The slide hiding is handled purely through css transforms, which allows me to animate them in/out at 60fps easily.
This method unfortunately has issues on osx since compositing changes don’t seem to update on fullscreen on cloned secondary displays. E.g. on the projector at the conf!! 😞

The slideshow code is handled over 3 very small (<100loc) modules.


main.js is the browserify entry point. It handles DOM manipulation and sets up listeners for keypress/clicks. I initially used hammerjs to allow for touch swiping on mobile devices, but this prevented me from scrolling the notes so I disabled it for this build.


slides.js is quite lovely (if I do say so myself)! Each slide has 3 functions– setup: function (), teardown: function () and action: function *() . If you have a keen eye you’ll notice the * which denotes the action function as a generator.

setup is run when the slide is first initiated (see goToSlide() in main.js). The action is the generator, which is then setup and run once to reach the first yield statement. When the user interacts with the page .next() is run and if it is done then it triggers progression to the next slide. (See triggerEvent() in main.js).

This allows me to easily step through a large block of code so we can see the status of the DOM at each yield, allowing us to break down the animation code into steps.

The downside of this is that to use the generators the traceur transpiler needs to add a runtime component, which is about 100kB, but for an entirely front loaded media heavy page such as a slideshow presentation this is not a big issue.


webrtc.js is the controller for recieving/sending commands to the client. To save time here we use the great PeerJS.

If a client connects with a location.hash of #controller then they will become the master slideshow. They will be shown the slideshow notes regardless of the screen size.

If that hash is not present or the master role is taken then they will fallback to being the client and will connect to the master slideshow via webRTC.

When the master slideshow performs any action this action gets passed to all of the client slideshows. The client slideshows then fire the appropriate event, which then gets acted upon in main.js. This allows me to control every slideshow whilst I am giving my presentation from my smartphone whilst also getting having my notes to hand. It also allows me to demo my talk remotely without screen casting.

https + cloudflare + peerjs provided some unexpected issues. I am hosting the presentation on, which is https only. This means that I need to run peer.js in secure mode so will need to host my own instance of the PeerJS server. Fortunately I already run PeerJS on 1am club, but it had been untested since I put cloudflare infront of the server to speed up connections and provide caching. Unfortunately PeerJS relies on websockets to connect to the signalling server and cloudflare, as of writing, cannot handle websockets for non-business customers. I tweaked my cloudflare setup to run // through cloudflare and // to go straight through to the aws server. I then set peerjs to connect straight to


On the morning of the presentation the presentation computer lost connection because it was put to sleep. Also, since it was a lightning talk and I only had limited time, instead of faffing with refreshing the target computer over flaky wifi and hoping it would connect, I just gave the talk from memory, controlling the presentation from the presentation computer. This was not as flashy as controlling the presentation over webRTC, but if I wasn’t panicking I probably could have quickly restored the connection by refreshing the presentation. I should add automatic reconnection for next time.

With fullscreen + mac + secondary monitor, it seems the compositing effects would not show up on the sencond monitor when fullscreen, which is odd. I do not have a mac so cannot debug this; any insight would be welcome. This lead to the slides not updating on the projector and to fix this we just took the browser out of fullscreen.


For the general styling: the app is built using Bootstrap and Bootstrap-Material-Design. This is extremely wasteful since 90% of the provided styles and scripts will not be used at all.

The material design elements were very useful for quickly assembling realistic looking demo webapps for the slides. I made 4 demos in 4 hours on the flight from the UK to San Francisco.

The presentation has two main modes, if the device is the master controller or a mobile phone then then the notes are displayed and the slideshow is hidden. This allows me to have notes on my phone if I need them and the slideshow on the presentation medium which is quite neat.

The slide transition animation: for a little bit of flair at no extra cost I use a custom transition function I designed using the Ceaser editor. It has a small anticipation and elasticity.

The slide transformations are quite nice too and pretty neat:

.slide {

	// By default all slides are off to the left
	transition: transform 1.5s cubic-bezier(0.030, 0.200, 0.360, 1.210);
	transform: translateX(-100%) translateZ(-50px) rotateY(-10deg);

	// The active slide has no transform
	&.active {
		transform: translateX(0);

	// All of the slides after the active one are off to the right
	&.active~.slide {
		transform: translateX(100%) translateZ(-50px) rotateY(10deg);

The small translateZ and rotateY give a really nice ‘album cover switcher’ effect. Even if many slides get skipped at once it still looks really nice.

To do

  • Address issues listed above
  • Clean up and seperate sass so that the talk specific styles go in their own file.
  • Namespace the master controller’s PeerJS id to the talk name
  • Remove dependence on bootstrap and jquery


For a small, quick and dirty ‘Ada-Special’ solution to doing a presentation it worked really well–the tech is neat and it doesn’t need much cleaning up to be ready for a 1.0 release.

Definitely a project to keep working on.