Javascript, From thumbnail to fullsize image on the same page

Javascript keywords, functions and properties used :

function, var, new, return, while if, else, length, ++, parseInt(), addEventListener(), attachEvent(), onmouseover, onclick, setTimeout(), clearTimeout(), parentNode, this, Image.src, String.replace(), style, offsetWidth, offsetHeight, height, width, top, left, padding, paddingBottom, visibility, opacity, filter, createElement(), getElementsByTagName(), getElementById(), appendChild(), removeChild(), className, innerHTML, innerWidth, innerHeight, clientHeight, clientWidth, documentElement

Using the Source...

A slightly different tack

While in past articles the code was display followed by a decomposition of the code, the approach used here will be to display the code with interspersed code comments. This method is used as some of the functions are rather long and involved making continually having to scroll up and down tedious. Explanations are to be found in-line with the code.

Please note that the same comments are present in the example ZIP file found at the end of this page.

In addition, the compressed file will include everything needed to display a demonstration page.

Window onload()...

Getting ready to begin to commence to start doing something useful

To reduce as much as possible, the inclusion of Javascript code into the HTML code, addEventListerner()/attachEvent() are used to attach a handler to the window.onload event.

The onload event handler, init_images(), will then identify the area to be searched for target thumbnails and then within the identified area, of which there can only be one, the onload event handler will then cycle through all images attaching process initiating onmouseover event handlers to each identified target.

if (window.addEventListener)
  window.addEventListener('load',init_images,false);
else if (window.attachEvent)
  window.attachEvent('onload',init_images);

Test whether window.addEventListener or window.attachEvent are defined and then use the one that is defined to attach the event handler.

Initialize event handlers...

Something useful

Called when the window completes loading, this function will attach the required event handler, init_expansion() to the required event, onmouseover.

function init_images () {
  /*  Get a pointer to the area, id=opaqued that contains the
      images to be expanded.  */
  opaque_region = document.getElementById('opaqued');
  var imgs = opaque_region.getElementsByTagName('img');
  for (var x=0; x < imgs.length; x++) {
    //  Check each link to see if it is a target, css class = "expando".
    if (imgs[x].className.indexOf('expando') != -1) {
      imgs[x].onmouseover = init_expansion;
    }
  }
}
Initial parameters...

Initial parameters

There are a rather large number of global paramters used in this script. First the more important ones will be described, those that may be useful for application dependant adjustments.

Variables described after this are primarily placeholders used during processing and of little interest to the user.

/*  The size, in pixels, of each step of the minor expansion 
    process between in-page image size and "full" minor 
    expansion size.  */
var step = 6;
/*  The number of milliseconds between each minor and 
    major expansion process iteration. */
var wait = 40;
/*  The number of iterations of the major expansion 
    process between "full" minor expansion size and 
    major expansion image full size.  */
var steps=10;
/*  The size that in-page images will appear to expand
    to during the minor expansion process.  */
var inter_size_width=150;
//  The full width the drop shadow will attain.
var sm_shdwWidth=8;
/*  The level of greatest transparency the background
    will achieve during the major expansion process.  */
var min_opacity=35;
/*  A text string in a given thumbnail image URL which 
    identifies it as a thumbnail url.  */
var thumbs_id='thmb';
/*  A text string in a given full size image URL which 
    identifies it as a full size image url.  */
var source_id='full';

Note 1: Adjust the step, wait and steps parameters for the level of smoothness desired in the expansion processes. A shorter wait time, smaller step and larger steps values will generally cause the expansion to appear more smooth but at the cost of higher client side CPU usage.

Note 2: thumbs_id and source_id support programatically determining the full size image URL from the thumbnail image URL.

For example, if the thumbnails and full size images carry the same name but in different directories, set the thumbs_id parameter to the directory of the thumbnails and the source_id parameter to the directory containing the full size images, i.e. if the thumbnail image is http://some-domain.com/images/thumbnails/some_img.jpg and the full size image is http://some-domain.com/images/fullsize/some_img.jpg, you would set thumbs_id equal to "thumbnails" and source_id to "fullsize".

Alternately, if the thumbnails are in the same directory as the full size images and their repsective URLs differ only in some identifier embedded into the image name for example http://some-domain.com/images/some_img-thumb.jpg for the thumbnail and http://some-domain.com/images/some_img-full.jpg for the full size image, you would set thumbs_id equal to "thumb" and source_id equal to "full"

Placeholders...

Variables primarily used to maintain state information.

//  A pointer to the in-page thumbnail
var thm_image=null;
//  Size, position and center of in-page thumbnail
var thmWidth, thmHeight, thmLeft, thmTop;
var thmCntrX, thmCntrY;
//  Height and width of full size image
var full_height, full_width;
/*  Amount added or subtracted from current values
    during each major expansion iteration.  */
var stepX, stepY, stepWidth, stepHeight;
//  The current values at any give point.
var crntX, crntY, crntWidth, crntHeight;
//  Timer used to control expansion and collapse
var expando_timer;
/*  The amount the drop shadow padding is 
    adjusted on each minor expansion and
    its current value.  */
var sm_shdwStep, sm_shdwCurrent;
/*  The amount the background opacity is 
    adjusted on each major expansion and
    its current value.  */
var opacity, opacity_step;
/*  Set during minor and major expansion
    and unset after complete collapse.  */
var bShowing=false;
/*  Container div used to contain the minor expansion image
    as well as the padding and drop shadow elements and
    a pointer to where the minor expansion image is
    inserted into the minor expansion container.  */
var thm_container= null;
var thm_insertion=null;
var small_expand=null;
//  Similar elements and pointers as for the minor expansion
var full_container=null;
var full_insertion=null;
var full_expand=null;
//  Area whose opacity is effected during the major expansion
var opaque_region=null;
/*  Used to properly locate elements with respect to
    the in-page thumbnail.  */
var scrollParent=null;
//  Object used for pre-loading the major expansion image
var pre_loader=null;
Expansion initialization...

Starting out

The following code is executed when a mouseover event fires from an in-page thumbnail image.

function init_expansion () {
  //  If we are already in the expansion process, no need to start it again.
  if (bShowing) return;
  /*  Reality check, kill the timer so that multiple timer 
      events don't fire while we are processing this one. */
  if (expando_timer) clearTimeout(expando_timer);
  //  If a minor expansion element already exists, delete it from the page.
  if (small_expand != null) {
    thm_container.parentNode.removeChild(thm_container);
  }
  /*  Set the region that will be made semi-transparent 
      during the major expansion process */
  scrollParent = opaque_region.parentNode;
  /*  The owner of this function call is the in-page thumbnail
      image. We will collect various data to be used to locate the
      minor and major expansion elements.  */
  thm_image = this;
  /*  Kill the in-page thumbnail image onmouseover event
      so that it doesn't fire while the minor and major elements
      exist.  */
  thm_image.onmouseover = null;
  /*  Collect height, width, top and left position information
      from the in-page thumbnail image.  */
  thmWidth = thm_image.offsetWidth;
  thmHeight = thm_image.offsetHeight;
  thmLeft = getOffsetLeft(thm_image);
  thmTop = getOffsetTop(thm_image) - scrollParent.scrollTop;
  //  Identify the center of the in-page thumbnail image.
  strtCntrY = thmTop+thmHeight/2;
  strtCntrX = thmLeft+thmWidth/2;
  /*  Create and populate the container that will contain the minor 
      expansion image.  */
  small_expand = document.createElement('img');
  small_expand.style.visibility = 'hidden';
  small_expand.src = thm_image.src;
  small_expand.className = 'grow_img';
  small_expand.title = 'Click to Expand';
  thm_container = document.createElement('div');
  //  Locate the "body" element and add the container to it.
  document.getElementsByTagName('body')[0].appendChild(thm_container);
  thm_container.className = 'center2';
  thm_container.id = 'thm_container';
  //  Setup the container with the drop shadow divs.
  thm_container.innerHTML = '<div class="fs_t">' +
                              '<div class="fs_r">' +
                                '<div class="fs_b">' +
                                  '<div class="fs_l">' +
                                    '<div class="fs_tr">' +
                                      '<div class="fs_br">' +
                                        '<div class="fs_bl">' +
                                          '<div class="fs_tl" id="thm_insertion">' +
                                          '</div>' +
                                        '</div>' +
                                      '</div>' +
                                    '</div>' +
                                  '</div>' +
                                '</div>' +
                              '</div>' +
                            '</div>';
  /*  Locate the container image insertion point and
      insert the image.  */
  insertion_point = document.getElementById('thm_insertion');
  insertion_point.appendChild(small_expand);
  /*  Initialize parameters used to keep track of expansion size
      and set the initial size and position of the expansion image.  */
  crntWidth = thmWidth;
  crntHeight = thmHeight;
  small_expand.style.width = crntWidth+'px';
  small_expand.style.height = crntHeight+'px';
  thm_container.style.top = thmTop-2+'px';
  thm_container.style.left = thmLeft-2+'px';
  //  Set the event handler for mouseout.
  small_expand.onmouseout = init_thumb_collapse;
  //  Make it visible.
  small_expand.style.visibility = 'visible';
  /*  Initialize the current drop shadow step size and 
      its current value to 0.  */
  sm_shdwStep = (sm_shdwWidth/(140 - thmWidth))*step/2;
  sm_shdwCurrent = 0;
  //  Begin the iterative expansion process.
  expand_thumb();
}
First stage expansion...

The minor expansion process

The following function is called repeatedly until the thumbnail sized image has expanded to its intermediate size prior to expanding to full size if clicked on.

function expand_thumb () {
  //  Another timer reality check
  if (expando_timer) clearTimeout(expando_timer);
  /*  Increment the height and width variables and update
      expanding image's top and left position as well as
      adjusting the expanding image to the center of the
      in-page thumbnail.  */
  crntHeight += step;
  crntWidth += step;
  small_expand.style.height = crntHeight+'px';
  small_expand.style.width = crntWidth+'px';
  thm_container.style.top = parseInt(strtCntrY-crntHeight/2)+'px';
  thm_container.style.left = parseInt(strtCntrX-crntWidth/2)+'px';
  /*  Increment the shadow width counter variable and update the left
      and bottom padding values giving the appearance that the shadow
      actually grows.  */
  sm_shdwCurrent += sm_shdwStep;
  insertion_point.style.padding = parseInt(sm_shdwCurrent)+'px';
  insertion_point.style.paddingBottom = parseInt(sm_shdwCurrent/2)+'px';
  /*  Check to see if the expanding image has achieved the desired size, 
      as set by "inter_size_width".  If not, set the timer and do it again.  */
  if (crntWidth < inter_size_width) {
    expando_timer = setTimeout(expand_thumb,wait);
  } else {
    /*  If the expanding image has reached the desired size, set the
        inter_size_height to the current height as the resulting height
        may be plus or minus a few pixel and we need to know exactly
        where we started so we can know where to end up on collapse.  */
    inter_size_height = crntHeight;
    /*  Set the expanded image's onclick so that if the user clicks
        on the expanded image, it will then begin the process to
        fully expand. This is set at this time so that it can not be fired
        earlier when the image may be in the process of completing
        the initial minor expansion.  */
    small_expand.onclick = init_expand_full;
  }
}
First stage collapse...

Collapsing the minor expansion

As has been mentioned, the process of collapsing the minor expansion image is essentially the reverse of the minor expansion process itself.

function collapse_thumb () {
  //  And yet another timer reality check.
  if (expando_timer) clearTimeout(expando_timer);
  /*  Basically do the opposite of what was done before, add where
    we subtracted and subtract where we added and put your right
    foot in, your right foot,,,, oh, sorry,  back to work.  */
  crntWidth -= step;
  crntHeight -= step;
  small_expand.style.width = parseInt(crntWidth)+'px';
  small_expand.style.height = parseInt(crntHeight)+'px';
  thm_container.style.top = parseInt(strtCntrY-crntHeight/2)+'px';
  thm_container.style.left = parseInt(strtCntrX-crntWidth/2)+'px';
  sm_shdwCurrent -= sm_shdwStep;
  insertion_point.style.padding = parseInt(sm_shdwCurrent)+'px';
  insertion_point.style.paddingBottom = parseInt(sm_shdwCurrent/2)+'px';
  if (crntWidth > thmWidth)
    expando_timer = setTimeout(collapse_thumb, wait);
  else {
    /*  There is a race condition that exists that was difficult
        to find the source of without this timer reality check.  */
    if (expando_timer) clearTimeout(expando_timer);
    /*  Reset the mouseover event for the in-page thumbnail
        so that it can be expanded again.  */
    thm_image.onmouseover = init_expansion;
    //  Remove the expansion image and its container from the page.
    thm_container.parentNode.removeChild(thm_container);
    small_expand = null;
  }
}
Prepare for second stage expansion...

Setting up the expansion to full size

Using the size and position of the expanded thumbnail, we initialize an image, image container and various parameters needed to perform the expansion to full size.

function prepare_expand_full () {
  /*  At this point, we know the image is loaded so we
    create and populate a container for it just like we
    did for the minor expansion.  */
  full_expand = document.createElement('img');
  full_expand.style.visibility = 'hidden';
  full_expand.src = pre_loader.src;
  full_expand.title = 'Click to Reduce';
  //  Begin same old - same old
  full_container = document.createElement('div');
  document.getElementsByTagName('body')[0].appendChild(full_container);
  full_container.className='center2';
  full_container.id='full_container';
  full_container.innerHTML = '<div class="fs_t">' +
                              '<div class="fs_r">' +
                                '<div class="fs_b">' +
                                  '<div class="fs_l">' +
                                    '<div class="fs_tr">' +
                                      '<div class="fs_br">' +
                                        '<div class="fs_bl">' +
                                          '<div class="fs_tl" id="full_insertion">' +
                                          '</div>' +
                                        '</div>' +
                                      '</div>' +
                                    '</div>' +
                                  '</div>' +
                                '</div>' +
                              '</div>' +
                            '</div>';
  full_insertion = document.getElementById('full_insertion');
  full_insertion.appendChild(full_expand);
  full_insertion.style.padding = sm_shdwWidth+'px';
  full_insertion.style.paddingBottom = sm_shdwWidth/2+'px';
  full_height = full_expand.offsetHeight;
  full_width = full_expand.offsetWidth;
  full_expand.style.width = crntWidth+'px';
  full_expand.style.height = crntHeight+'px';
  full_expand.className='grow_img';
  if (self.innerHeight) { // all except Explorer
    var availWidth = self.innerWidth;
    var availHeight = self.innerHeight;
  } else if (document.documentElement 
  && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
    var availWidth = document.documentElement.clientWidth;
    var availHeight = document.documentElement.clientHeight;
  } else if (document.body) { // other Explorers
    var availWidth = document.body.clientWidth;
    var availHeight = document.body.clientHeight;
  }
  //  End same old -same old
  /*  Here is where we diverge greatly from the minor
    expansion process. Besides having to check for 
    available area, which we didn't do before, we also
    have to find not the center of the in-page thumbnail
    but instead, the center of the browser viewport.  */
  if ( (full_height+sm_shdwCurrent*2) > availHeight) {
    full_width = full_width * (availHeight) / (full_height+sm_shdwCurrent*2);
    full_height = availHeight-(sm_shdwCurrent*4);
  }
  if ( (full_width+sm_shdwCurrent*2) > availWidth) {
    full_height = full_height * (availWidth) / (full_width+sm_shdwCurrent*2);
    full_width = availWidth-(sm_shdwCurrent*4);
  }
  if (self.pageYOffset) { // all except Explorer
  	var scrollX = self.pageXOffset;
  	var scrollY = self.pageYOffset;
  } else if (document.documentElement 
  && document.documentElement.scrollTop) { // Explorer 6 Strict
  	var scrollX = document.documentElement.scrollLeft;
  	var scrollY = document.documentElement.scrollTop;
  } else if (document.body) { // all other Explorers
  	var scrollX = document.body.scrollLeft;
  	var scrollY = document.body.scrollTop;
  }
  /*  Now we go back to much the same process as for the 
    minor expansion.  */
  var small_container = document.getElementById('thm_container');
  full_container.style.top = getOffsetTop(small_container)+'px';
  full_container.style.left = getOffsetLeft(small_container)+'px';
  full_expand.style.visibility = 'visible';
  thm_container.style.visibility='hidden';
  small_expand.style.visibility = 'hidden';
  startX = crntX = getOffsetLeft(full_container);
  startY = crntY = getOffsetTop(full_container);
  stepY = (crntY - ((scrollY + availHeight/2 - sm_shdwWidth) - full_height/2))/steps;
  stepX = (crntX - ((scrollX + availWidth/2 - sm_shdwWidth) - full_width/2))/steps;
  stepHeight = (full_height - small_expand.offsetHeight)/steps;
  stepWidth = (full_width - small_expand.offsetWidth)/steps;
  opacity = 100;
  opacity_step = (100-min_opacity)/steps;
  full_expand.onclick = collapse_full;
  expand_full();
}
Second stage expansion...

Executing the expansion to full size

Now that everything is set up, we repeatedly call the expand_full() function until the image has reached it's full size.

function expand_full () {
  /*  Again, basically the same as for the minor expansion
    except for two things, 1. we don't need to adjust the
    width of the drop shadow because it doesn't expand
    during this phase and 2. we instead need to adjust
    the opacity of the background.  */
  crntY -= stepY;
  crntX -= stepX;
  crntHeight += stepHeight;
  crntWidth += stepWidth;
  opacity -= opacity_step;
  /*  Some browsers understand "style.opacity" and 
    some "style.filter".  */
  opaque_region.style.opacity = opacity/100;
  opaque_region.style.filter = 'alpha(opacity='+opacity+')';
  full_container.style.left = parseInt(crntX)+'px';
  full_container.style.top = parseInt(crntY)+'px';
  full_expand.style.width = parseInt(crntWidth)+'px';
  full_expand.style.height = parseInt(crntHeight)+'px';
  //  Again, if we haven't reached full size, spin the bottle again.
  if (crntHeight < full_height)
    expando_timer = setTimeout(expand_full, wait);
}
Collapsing the full size image...

Collapsing the full size image

Similar to the thumbnail collapse and the opposite of the full size expansion, we now collapse the full size image until it has reached the size it started out as and then begin the collapse of the expanded thumbnail completing the collapse down to the in-page thumbnail.

function collapse_full () {
  /*  Same as minor expansion collapse, add where
      previously one subtracted and vice versa.
      The opacity of the background must also be
      ramped back up to normal.  */
  small_expand.style.visibility = 'visible';
  thm_container.style.visibility = 'visible';
  full_expand.onclick = null;
  crntY += stepY;
  crntX += stepX;
  crntHeight -= stepHeight;
  crntWidth -= stepWidth;
  full_container.style.top = parseInt(crntY)+'px';
  full_container.style.left = parseInt(crntX)+'px';
  full_expand.style.height=parseInt(crntHeight)+'px';
  full_expand.style.width = parseInt(crntWidth)+'px';
  opacity += opacity_step;
  opaque_region.style.opacity = opacity/100;
  opaque_region.style.filter = 'alpha(opacity='+opacity+')';
  if (crntHeight + sm_shdwCurrent > inter_size_height)
    expando_timer = setTimeout(collapse_full, wait);
  else {
    small_expand.style.visibility = 'visible';
    thm_container.style.visibility = 'visible';
    //  Guess what, another timer reality check, bet you weren't expecting that!
    if (expando_timer) clearTimeout(expando_timer);
    bShowing = false;
    opaque_region.style.opacity = 1;
    opaque_region.style.filter = 'alpha(opacity=100)';
    full_container.parentNode.removeChild(full_container);
    full_expand = null;
    collapse_thumb();
  }
}
Demo Download...

Download the demo files

Download the demo files Here