Flipping Slides with JavaScript

I’ve been writing my talk notes in XML and delivering them in HTML for years. These days I rarely if ever use PowerPoint. Especially since my talks tend to be quite code heavy, HTML works much better. It’s much easier to put a decent amount of (still legible) source code on an HTML page than a PowerPoint slide, plus I can scroll if I need to.

One of the most common questions I get when I give one of these talks is how I make the slide advance from one to the next by just hitting one key. It’s actually not that hard, but it does surprise people, so I thought I’d show you.

First, look at this technique in action. Read this slide from a recent talk, and then hit the space bar. Notice how you can keep hitting the space bar to move from one slide to the next.

View Source reveals the trick. It’s just one little JavaScript in the head:

<script language="JAVASCRIPT"><!-- 
    var isnav
        
    if (parseInt(navigator.appVersion) >= 4) {
      if (navigator.appName == "Netscape") {
        isNav = true
        document.captureEvents(Event.KEYPRESS)
      }
      else {
        isNav = false
      }
    }
    document.onkeypress = flipslide

    function flipslide(evt) {
      var key = isNav ? evt.which : window.event.keyCode
      if (key == 32 || key == 29 || key == 30 || key == 11) {
      
        location.href="04.html"
      
      }
      else if (key == 37 || key == 31 || key == 12) {
      
        location.href="02.html"
      
      }
      else if (key == 1) {
        location.href="index.html";
      }
    } //  --></script>

Yes, the JavaScript is sort of old-fashioned. I first wrote this in 1999 or so and haven’t really looked at it since. Back then, I tried to also capture the arrow keys to allow me to move backward in the deck with one key too. At the time this was possible with the browser I was using (maybe Netscape 4.0?) but it doesn’t seem to work anymore. I should really figure out how to fix that one of these days.

The downside to this code fragment is that each slide does need to know which slide comes next so it can jump to it. If I were manually editing the HTML, that would be painful. Every time I moved a slide I’d have to update all the JavaScript and all the links, so I don’t do that. Instead the raw source is one XML document, and the XSLT stylesheet splits that file into individual slides (using a Saxon extension function). As it does so it manually renumbers all the slides and all the links.

 <xsl:document method="html" encoding="ISO-8859-1" 
               href="{format-number(position(),'00')}.html">
   <html>
     <head>
       <meta name="description">
         <xsl:attribute name="content">
           <xsl:apply-templates select="description" mode="meta"/>
         </xsl:attribute>
       </meta>
       <title><xsl:value-of select="title"/></title>
       <script language="JAVASCRIPT"> 
          <xsl:text disable-output-escaping="yes"><!-- </xsl:text>
          var isnav
          
          if (parseInt(navigator.appVersion) >= 4) {
            if (navigator.appName == "Netscape") {
              isNav = true
              document.captureEvents(Event.KEYPRESS)
            }
            else {
              isNav = false
            }
          }
          document.onkeypress = flipslide

          function flipslide(evt) {
            var key = isNav ? evt.which : window.event.keyCode
            if (key == 32 || key == 29 || key == 30 || key == 11) {
              <xsl:if test="position()!=last()">
                location.href="<xsl:number value="position()+1" format="01"/>.html"
              </xsl:if>
            }
            else if (key == 28 || key == 31 || key == 12) {
              <xsl:if test="position()!=1">
                location.href="<xsl:number value="position()-1" format="01"/>.html"
              </xsl:if>
              <xsl:if test="position()=1">
                location.href="index.html"
              </xsl:if>
            }
            else if (key == 1) {
              location.href="index.html";
            }
          } // <xsl:text disable-output-escaping="yes"> --></xsl:text>             
       </script>         
     </head>
     <body>           
         <xsl:apply-templates/>                  
       <hr/>
       <!-- Need to treat first and last slide specially --> 
       <xsl:choose>
         <xsl:when test="position()=1">
           <div align="center">
             <a href="{format-number(position()+1,'00')}.html">Next</a> 
             | <a href="index.html">Top</a> 
             | <a href="http://www.cafeconleche.org/">Cafe con Leche</a>
             | <a href="http://www.cafeaulait.org/">Cafe au Lait</a>
           </div>
         </xsl:when>
         <xsl:when test="position()=last()">
           <div align="center">
             <a href="01.html">Start</a> 
             | <a href="{format-number(position()-1,'00')}.html">Previous</a> 
             | <a href="http://www.cafeconleche.org/">Cafe con Leche</a>
             | <a href="http://www.cafeaulait.org/">Cafe au Lait</a>
           </div>
         </xsl:when>
         <xsl:otherwise>
           <div align="center">
             <a href="{format-number(position()-1,'00')}.html">Previous</a> 
             | <a href="{format-number(position()+1,'00')}.html">Next</a> 
             | <a href="index.html">Top</a> 
             | <a href="http://www.cafeconleche.org/">Cafe con Leche</a>
             | <a href="http://www.cafeaulait.org/">Cafe au Lait</a>
           </div>
         </xsl:otherwise>
       </xsl:choose>
...
     </body>     
   </html>
 </xsl:document>

The downside to this approach is that if I add, move, or remove a slide from the deck the numbers the URLs of all the other slides change; and cool URLs don’t change. I should probably revise my stylesheet so it dynamically generates the slide names from the content of each slide rather than an autogenerated number.* The XSLT could still generate the correct next and previous links.


As a side benefit, this would also optimize for seach engines since search engines are ridiculously sensitive to the words in the URLs.

7 Responses to “Flipping Slides with JavaScript”

  1. Adam Constabaris Says:

    Eric Meyer’s s5 achieves some similar navigational results, although it uses a single HTML file (it does, however, support fragment identifiers so it’s possible to link directly to specific slides)

  2. John Roth Says:

    S5 seems to navigate properly, at least under Firefox. It’s at:

    http://www.meyerweb.com/eric/tools/s5/

    John Roth

  3. Keith Gaughan Says:

    My HTML presentation template works quite similarly to S5 in this regard, but why not embed the filename of the next slide in a LINK tag? That way you can pull all of that JS into a separate file. Much less nasty.

  4. Elliotte Rusty Harold Says:

    S5 is very pretty, and quite impressive technically. I’m definitely going to have to reverse engineer this to borrow the best ideas for my own work. However, it’s got one major flaw. Because the individual slides are all in a single file, there’s only one URL. This is bad for linking, bookmarking, ad placement, and search engine optimization. It’s much better for the individual slides to each have their own URLs.

    Per Bothner has fixed at least the linking and bookmarking problems by using fragment identifiers. Search engines still seem to strip the fragment ID when indexing though.

  5. Xavi Miró Says:

    There is also another format called “Slidy” (http://www.w3.org/Talks/Tools/Slidy/). I don’t know if it’s a W3C “official” format or simply an experiment, but it looks quite nice. It’s very similar to S5.

    – Xavi

  6. Richard Says:

    Hi there. I just thought I’d let you know about html accesskeys, which have been part of the spec since about version 5 browsers, and are supported by IE/Mozilla/Opera/Amaya and Safari. very easy to use, and starting to gte very commmon. See the link above

  7. Monkeyget Says:

    Or…you could use the projection media mode which is standard css. No need to use javascript or juggle with ids.
    An example here : http://meyerweb.com/eric/tools/slideshow/test.html
    Just look at the source, it doesn’t get simpler than that.