I'm Michael Suodenjoki - a software engineer living in Kgs. Lyngby, north of Copenhagen, Denmark. This is my personal site containing my blog, photos, articles and main interests.

Article

Creating a photo album slideshowCreate your own photo gallery in your browser using XML, XSL and DHTML

Updated 2011.01.23 17:22 +0100

Ikke tilgængelig på dansk.

By Michael Suodenjoki, michael@suodenjoki.dk.

Version 1.2 April 2004.

Abstract

This article describes how you by using XML together with XSL and DHTML can create your own browser based slideshow for your favorite digital photo album.

Click here for my Lyngby Photo Gallery example (Requires IE6 only, sorry).

Illustration of a photo gallery in full screen mode within the browser.

» Illustration of a photo gallery in full screen mode within the browser.

Update April 2004: I've recently been very interesting in adding more features to my photo gallery slide show and has therefore dived into examination of what features that should be added to support more compelling presentation techniques.  While many of these features in the future will be supported by different software applications the really important thing that will distinguish the competitors from each other will be the user friendliness (and of course price) of the software. It is and/or will be important that the products are easy to learn and easy to use and have a nice "slick/professional" interface that makes the software compelling to use.

Features a great photo presentation program should have:

Feature Basic Advanced
Runs in browser
Runs in browser without any third-party plug-ins like Flash, Java or any other viewer/player  
Full screen mode
Captions    
Caption for each photo
Individual position of caption pr. photo. Can be placed in photo (transparently).  
Individual caption style pr. photo  
Individual caption effects; slide-in, fading, zoom, ...  
Music and speech    
Common background music
Music/speech for each photo  
Narrative voice recording and playback for each photo  
Transition Effects    
Transition effects between photos.
Control start/end and lengths of transitions effects on individual photos  
Synchronize transition effects with background music  
Ken Burns Effects    
Panning and zooming effects (either individually or together).  
Control panning and zoom areas on individual photos  
Control panning and zoom start/middle/end sequences on individual photos  
Input/Export Formats    
Gallery can be customized and/or imported/exported to/from XML
Support for importing video sequences    
Support for burning the "show" to DVD (different formats)  

While I haven't (yet) added these features to my code I thought the links to my found sources may be useful for you too:

Miscellaneous software supporting some of the desired features:

Contents

1.0 Introduction
2.0 Dividing Contents from Style
  2.1 The XML content
  2.2 The XSLT style transformation
3.0 The Full Screen Browser Mode
  3.1 Starting the photo gallery
  3.2 Closing the photo gallery
  3.3 Stopping the browser scrollbar form being displayed
4.0 Showing Photos One at a Time
5.0 Calculating the Image Size
6.0 Making Slick Fading Transition between Photo Selections
  6.1 Applying transition filter
7.0 Adding Slide Show Feature
8.0 Conclusion

Requirements

The code in this article requires:

An understanding of CSS, XML, XSL, DHTML and JavaScript is also preferable.

Download Source

Download the gallery.zip source file for this article.

The gallery.zip file contains:

1.0 Introduction

I bought my very first digital camera in august 2000. During that years time I have taken a lot of photos both from around the area where I live and during my summer trekking holidays in France and my yearly skiing trips. Usually I end up with having a lot of photos that I would like to share with my friends or family and I often find it too annoying to create new HTML pages containing the photos every time. I know that you can buy of-the-shelf photo viewer software packages - e.g. to browse CD's etc - or you can use your favorite image editor to create HTML based photo albums with auto thumbnails (e.g. Photoshop). Even Microsoft FrontPage 2002 have this photo album feature.

However I would like to have complete control of how the album should look like, so I set up a task for myself to create a photo gallery/album slideshow that could be distributed on my homepage.

This article describes what I ended up with. The source is free to be downloaded and can be freely used.

My own requirements to the gallery was:

2.0 Dividing Contents from Style

One of the central mantras of today's programming and web-page design are that you should strive for a solution that divide your contents from that of how the contents are to be presented (the layout style). Not only will this "design pattern" make it possible that one group of people are creating content and that another group are creating the visual style, but it does also allow the visual style to be changed quickly without having to mess around in the actual content.

Since that I am using this design pattern in my daily work I find it natural to do the same with my photo gallery.

I have chosen that my content language is XML and that I will be using XSLT to generate a nice looking XHTML page presenting my gallery.

2.1 The XML content

The photo gallery xml content will be looking like the xml code below. The gallery have a name, date and a general description. It will contain a collection of photos where each photo is described with different properties. I have selected a few properties that I think is relevant such as a description of the photo, a link to the actual photo (the file name), but it is of course possible to extend this with a lot more properties.

<?xml version="1.0"?>
<PhotoGallery>
  <Name>Sample Photo Gallery</Name>
  <Description>Misc. photos from my collection.</Description>
  <Date>2001.08.01</Date>

  <Photos>
    <Photo>
      <Description>Nice oak tree with a deer under.</Description>
      <Link>photo12.jpg</Link>
      <Dated></Dated>
      <Copyright>
        <Year>2001</Year>
        <Name>Michael Suodenjoki</Name>
      </Copyright>
      <Credit></Credit>
      <Camera>
        <Name>Canon PowerShot S20</Name>
      </Camera>
    </Photo>

    <Photo>
       ...
    </Photo>
    ...
  </Photos>

</PhotoGallery>

2.2 The XSLT style transformation

As mentioned a XSLT file will be used to generate the XHTML page that will be the one the end-user will see. The XSLT file will be given the XML content file and will transform it using the transformation rules to generate different parts of the end XHTML page.

I start with describing the main content of the XSLT file. The file which itself is a xml file have the usual xsl:stylesheet as the root element, which tells the system that the file contains a style sheet. The output that will be generated is set to html. The style sheet itself contains only two templates; one for the root of the input file (the xml content file) and one template that matches each photo.

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html"/>

<xsl:template match="/">
   ...
</xsl:template>

<xsl:template match="Photo">
  ...
</xsl:template>

</xsl:stylesheet>

In the following sections the different parts of the style sheet will be further extended.

3.0 The Full Screen Browser Mode

One important requirement that I setup was that the photos should be presented in a full screen, thus the browser window toolbars and the windows taskbar should not be visible. I liked the idea that the photo gallery slideshow could run as a screen saver in the background to e.g. a family gathering.

However I quickly noted that many people had problems with IE to do exactly that. A quick search within groups.google.com revealed that some fancy code were required.

3.1 Starting the photo gallery

The first job were to actual startup the gallery in full screen mode from a html page in my site. It wanted that a second window opened in full screen so a call to the script function window.open() were required. As can be seen by the sample code below two different ways are illustrated; one using a link and another using a button. You can choose whatever that you feel most appropriate in your context. It is this code that you put into your homepage. Note that it specifies which xml content file (here "gallery.xml") that should be opened and displayed.

<h1>Photo Gallery</h1>
<p>
<a href="#" onclick="window.open('gallery.xml', '', 'fullscreen=yes')">
Open Photo Gallery</a>
</p>
<input type=button value="Open full screen" onclick="window.open('gallery.xml', '', 'fullscreen=yes')">

The reason for why I wanted to start a new second window was that when it is displayed in full screen the user looses somewhat control over his workplace (the desktop window) and would probably like to exit the gallery without actually exiting the browse session itself.

3.2 Closing the photo gallery

When the browser is showing the gallery in a full screen mode it should be possible for the user to exit it in a nice way. I have chosen to incorporate an exit link on the gallery page that can be selected. The code below is inserted into the style sheet in an appropriate place.

<a href="" title="Exit the Photo Gallery (Alt+F4)" onclick="window.close()">Exit</a>

Another simpler way for the user is simply to press Alt+F4 which will close the secondary browser window.

3.3 Stopping the browser scrollbar form being displayed

When the gallery is displayed it may contain more than one photo and the browser will thus display the scrollbar so that the user can scroll down to the other photos. However I know that only one photo will be displayed at a time, so the scrollbar is somewhat not necessary. It can be removed by inserting a piece of code in the output body element of style sheet page.

<body scroll='no'>
  ...
</body>

4.0 Showing Photos One at a Time

The gallery should present each photo at a time in its maximum size depending on the screen resolution. A simple trick is to hide all photos except the one that we want to show. There are two CSS attributes which controls whether a XHTML element should be displayed: the display attribute or the visibility attribute. The visibility attribute is special in the way that it retain the size of the element, but this will not be important in this context.

To have control of the visibility I created two JavaScript functions that can hide or show elements of a given name and with a given id. The functions are general and can be used in many other contexts where this functionality is wanted.

<script language="JavaScript">
  <xsl:comment>
    <![CDATA[
    // **************************************
    //
    // 'hide'
    //
    // Hides all tags of given names that have a specified id.
    //
    // Example hide("span","test") will hide the following elements...
    //
    // <span id="test">...</span>
    // <span id="test">...</span>
    //
    // Input:
    // sTagName - Specifies the name of the tag to look for e.g. 'span','div','p' etc.
    // sTagId - Specifies the id of the tag to look for. 
    //
    function hide(sTagName,sTagId)
    {
      var cElems = document.all.tags(sTagName);
      var iNumElems = cElems.length;
      for( var i = 0; i < iNumElems; i++ ) 
      {
        if( cElems[i].id == sTagId )
        {
          cElems[i].style.visibility = "hidden";
          //cElems[i].style.display = "none";
        }
      }
    }

    // **************************************
    //
    // 'show'
    //
    // Shows all tags of given names that have a specified id.
    //
    // Example show("span","test") will show the following elements...
    //
    // <span id="test">...</span>
    // <span id="test">...</span>
    //
    // Input:
    // sTagName - Specifies the name of the tag to look for e.g. 'span','div','p' etc.
    // sTagId - Specifies the id of the tag to look for. 
    //
    function show(sTagName,sTagId)
    {
      var cElems = document.all.tags(sTagName);
      var iNumElems = cElems.length;
      for( var i = 0; i < iNumElems; i++ ) 
      {
        if( cElems[i].id == sTagId )
        {
          cElems[i].style.visibility = "visible";
          //cElems[i].style.display = "block";
        }
      }
    }
    ]]>
  </xsl:comment>
</script>

Each photo will be given a unique id - depending on its number in the gallery and prefixed with 'photo'. In this way we uniquely can identify the photo. The XSL template for each photo (see code snippet below) will output a div element that as id have the unique id.

<xsl:template match="Photo">
  <div class="photocontainer">
    <xsl:attribute name="id">photo<xsl:value-of select="position()"/></xsl:attribute>

    ...
  </div>
</xsl:template>

To select between the photos I've created a few utility functions named prevImage(), nextImage() and selectImage(). The selectImage function uses the show and hide functions and does also ensure that the succeeding photo of the last photo of the gallery will start over by selecting the first photo - thus provide some kind of wrapping. This utilizes the XSLT XPath count() function to count the number of photos in the gallery. The sample code also illustrates that it is possible to mix script code with XSLT code. Note that the script code shouldn't be embraced in CDATA and that special xml characters - such as < and > (less than and greater than characters) - which must be part of the script code must be written differently (as &lt; and &gt);

<script language="JavaScript">

  // Note: Don't use CDATA here. This script code uses XSL.

  var currImageNo = 1; // Specifies the current selected photo.

  //
  // 'prevImage'
  //
  // Select previous image. If current image is 1 the previous
  // image will be set (by selectImage) to the last image in the
  // gallery.
  //
  function prevImage()
  {
    selectImage(currImageNo,-1);
  }

  // 
  // 'nextImage'
  //
  // Selects next image.
  //
  function nextImage()
  {
    selectImage(currImageNo,+1);
  }

  //
  // 'selectImage'
  //
  // This function selects a new imaged based on the current (old) image
  // and an increment (+1,-1) telling which next new image number to show.
  //
  // The function wraps the increment around depending on the number
  // of photos in the gallery.
  //
  function selectImage( imgNumber, incr )
  {
    // Calculate the new image number to show (the old is keept in imgNumber parameter)
    currImageNo = currImageNo + incr;
    if( currImageNo == 0 )
    {
      currImageNo = <xsl:value-of select="count(/PhotoGallery/Photos/Photo)"/>;
    }
    if( currImageNo &gt; <xsl:value-of select="count(/PhotoGallery/Photos/Photo)"/> )
    {
      currImageNo = 1;
    }
    container.filters[0].Apply();

    // Show new selected image
    show("div","photo"+currImageNo);

    // Hide current image
    hide("div","photo"+imgNumber);

    container.filters[0].Play();
  }
</script>

Now I can generate the html code that utilizes these JavaScript functions. I have selected to put some links on top of the gallery page where the user can select between next and previous photo. The code is put into a table.

<table align="center" border="0" cellspacing="0" cellpadding="0" style="position:relative; top:-12pt;">
  <tr>
    <td align="center">
      <a href="#" alt="Previous photo" onclick="prevImage()">Prev</a><xsl:text> </xsl:text> 
      <a href="#" alt="Next photo" onclick="nextImage()">Next</a>
    </td>
  </tr>
</table>

5.0 Calculating the Image Size

One requirement is that each photo are presented on the center of the screen and that it is sized to fill-up as much as possible - some margin however is required to make the photo look more nice on the black background. Each photo are displayed at the same spot thus we can put the photo images into a table that are placed at an absolute position - using CSS position attributes. This is controlled by the CSS class photocontainer associated to the div element. We do also need space for text describing the photo, copyright message and the photo number within the gallery. This text are put into separate rows of the table.

<xsl:template match="Photo">
  <div class="photocontainer">
    <xsl:attribute name="id">photo<xsl:value-of select="position()"/></xsl:attribute>

    <table class="phototable" cellspacing="0" cellpadding="0">
      <tr>
        <td></td>
        <td align="right" valign="top">
          Photo <xsl:value-of select="position()"/>/<xsl:value-of select="count(/PhotoGallery/Photos/Photo)"/>
        </td>
      </tr>
      <tr>
        <td colspan="2" align="center">
          <img border="0">
            <xsl:attribute name="onload">calcPhotoSize("img" + <xsl:value-of select="position()"/>)</xsl:attribute>
            <xsl:attribute name="id">img<xsl:value-of select="position()"/></xsl:attribute>
            <xsl:attribute name="alt"><xsl:value-of select="Description"/></xsl:attribute>
            <xsl:attribute name="src"><xsl:value-of select="Link"/></xsl:attribute>
          </img>
        </td>
      </tr>
      <tr>
        <td class="celltext" valign="top">
          <xsl:value-of select="Description"/>
        </td>
        <td class="celltext" align="right" valign="top">
          Copyright <xsl:value-of select="Copyright/Year"/>
          <xsl:value-of select="Copyright/Name"/>
        </td>
      </tr>
    </table>

  </div>
</xsl:template>

The img element which is outputted by the XSL transformation for each photo generates a number of element attributes. The 'onload' attribute is called when the img element is loaded and it calls the script function calcPhotoSize with an id of the image. The purpose of the function is too calculate the maximum (best) size that can be displayed and pertain the proportion of the photo. The most difficult aspect of the calculation is to find the size of the displayed texts into account since these dynamically can change depending on how much text there are. The code are shown below.

<script language="JavaScript">
  <xsl:comment>
  <![CDATA[
    //
    // 'calcPhotoSize'
    //
    // This function calculated the best photo (image) width so that it fits best to
    // the users screen and sets that width/height of the image.
    //
    function calcPhotoSize( imgIdprefix, no )
    {
      var nHorzPadding = 5;
      var nVertPadding = 5;
      var nLeftMargin = 0;
      var nTopMargin = 75;

      // Get the image with requested image id
      var oImage = document.images(imgIdprefix + no);
      if( oImage == null )
      {
        alert( "Image '" + imgIdprefix + no + "' is null! ");
        return;
      }

      var nImgWidth = oImage.width;
      var nImgHeight = oImage.height;
      var nProportion = nImgWidth/nImgHeight;

      // Calculate width/height of the part of the photoX div element that does not contain the image
      // (this will get the sizes of the texts displayed).
      var nDivWidth = document.all.item("photo"+no).scrollWidth-nImgWidth;
      var nDivHeight = document.all.item("photo"+no).scrollHeight-nImgHeight;

      var nMaxWidth = screen.availWidth - nLeftMargin - nDivWidth - 2*nHorzPadding;
      var nMaxHeight = screen.availHeight - nTopMargin - nDivHeight - 2*nVertPadding;

      if( nProportion > 1 )
      {
        // Width is larger than height
        if( nImgWidth - nMaxWidth > nImgHeight - nMaxHeight )
        {
          oImage.width = nMaxWidth;
          oImage.height = oImage.width/nProportion;
        }
        else
        {
          oImage.height = nMaxHeight;
          oImage.width = oImage.height*nProportion;
        }
      }
      else
      {
        // Height is larger than width
        if( nImgHeight - nMaxHeight > nImgWidth - nMaxWidth )
        {
          oImage.height = nMaxHeight;
          oImage.width = oImage.height*nProportion;
        }
        else
        {
          oImage.width = nMaxWidth;
          oImage.height = oImage.width/nProportion;
        }
      }
    }
  ]]>
  </xsl:comment>
</script>

6.0 Making Slick Fading Transition between Photo Selections

To make the transition between photos more professional we can utilize DHTML transition techniques. One requirement for the DHTML transition technique is that the different parts are displayed at the same position. Therefore I created a div container element that not only is identified by an unique id (equal to "container") but also is using a CSS class that setup the absolute position of the container plus additionally the DHTML transition filter to be used. In this case the fading transition.

<head>
  <style text="stylesheet">
    .transitioncontainer
    {
      position:absolute; 
      top:0; 
      left:0; 
      width:100%; 
      height:100%; 
      filter:progid:DXImageTransform.Microsoft.Fade(duration=1,overlap=1.0);
    }
  </style>
</head>

<body>

...

  <div id="container" class="transitioncontainer">
    <xsl:apply-templates select="PhotoGallery/Photos/Photo"/>
  </div>

</body>

6.1 Applying transition filter

I have already showed you the selectImage function that is called each time a new photo should be selected. Inside the function we can now insert code that apply and play the filter on the element with id "container" which is our div element on a fixed position.

  //
  // 'selectImage'
  //
  // This function selects a new imaged based on the current (old) image
  // and an increment (+1,-1) telling which next new image number to show.
  //
  // The function wraps the increment around depending on the number
  // of photos in the gallery.
  //
  function selectImage( imgNumber, incr )
  {
    ...
    container.filters[0].Apply();

    // Show new selected image
    show("div","photo"+currImageNo);

    // Hide current image
    hide("div","photo"+imgNumber);

    container.filters[0].Play();
  }

7.0 Adding Slide Show Feature

The final touch to the photo gallery is to add a fancy slideshow feature. Again DHTML can help with the introduction of a timer. A timer supports that a given function can be called in a specified interval. I introduce two functions for starting and stopping the slide show using the DHTML functions setInterval and clearInterval. The timer function is simply the nextImage function that I had shown earlier. So the slide show simply select the next photo within the gallery. Quite fancy if I may add.

<script language="JavaScript">

  var timerId; // Used as timer identifier for DHTML setInterval/clearInterval.

  //
  // 'startSlideShow'
  //
  // Starts the slideshow by using DHTML setInterval; specifying
  // the function to call (nextImage) with an interval in milliseconds.
  //
  function startSlideShow()
  {
    timerId = setInterval ("nextImage()",5000); // 5 seconds interval
  }

  //
  // 'stopSlideShow'
  //
  // Stops the slideshode using HTML clearInterval.
  //
  function stopSlideShow()
  {
    clearInterval(timerId);
  }
</script>

To make the user start and stop the slide show we insert links at the same place where the user can select the previous and next image:

<table align="center" border="0" cellspacing="0" cellpadding="0" style="position:relative; top:-12pt;">
  <tr>
    <td align="center">
      <a href="#" alt="Start SlideShow" onclick="startSlideShow()">Start SlideShow</a><xsl:text> </xsl:text> 
      <a href="#" alt="Stop SlideShow" onclick="stopSlideShow()">Stop SlideShow</a><xsl:text> </xsl:text> 
      <a href="#" alt="Previous photo" onclick="prevImage()">Prev</a><xsl:text> </xsl:text> 
      <a href="#" alt="Next photo" onclick="nextImage()">Next</a>
    </td>
  </tr>
</table>

8.0 Conclusion

I have shown you how you can create your own photo gallery using your browser with a set of fancy features such as full screen view, best image size, image transitions and a slide show. I've shown you how to implement it by using XML, XSL and DHTML features of the Internet Explorer.

There are of course some drawbacks of the photo gallery. Mostly it only works on Internet Explorer version 5.5 or newer and it requires MSXML 3.0. Furthermore the code itself could be divided into different files e.g. separate file for the CSS style sheet parts and the script code.

However there are many other possible things that you could add to extend the experience of the gallery. For example:

Nice coding.

More DHTML scripts of interest may be found at http://www.dynamicdrive.com/dynamicindex14/index.html