Not so long ago, photo collections were stored in
photo albums, sometimes with carefully written captions, or as slide shows
ready made to strike fear into the hearts of many a social guest. Lately, these
photo albums are going high-tech as people put their fondest memories on the
web.
A quick
look through some of these photo album sites indicates that there is some room
for improvement in presentation. While there are lots of photo slide show
servers on the web – and some that will even store and show your photos for
free, the slide-show format limits the size of the images and there is not so
much room for personalization. Additionally, it seems that the only other option
is to display all the images at full size with a little text accompanying each
image. This has three significant disadvantages: with the images being so large
there is little room left for layout and the images end up in a long list
resulting in a very tall page. Also, the number of bytes making up each image
can be quite large, adding to the time taken for the page to download.
In this
article is an effective solution to this problem. Images are displayed in
thumbnail size giving much more room for layout and surrounding text. Also,
many images can be seen at once giving the reader a fuller presentation. If
readers wish to see more detail on a specific image, they can click on it and
it will expand to its full size.
The images
are presented in the document in thumbnail form as in the following HTML:
<a href="spike.jpg" onclick="this.href =
'javascript:void(0);';">
<img src="spike_thumb.jpg"
title="click to expand." style="float:right;"
onclick="new
ImageExpander(this, 'spike.jpg');">
</a>
Notice
that the <img> tag displays a thumbnail version of the image. The full
size image is only downloaded and displayed when the user clicks on the image.
Thumbnail images can be created from their originals by most image processing
applications. I have also wrapped the image in a link tag to enable users
without JavaScript to view the image. If JavaScript is enabled, the onclick of
the link tag disables its own link so that it doesn’t interfere with the
JavaScript code.
The
onclick handler of the thumbnail image creates a new ImageExpander object
passing itself and the URL of the full size image as arguments.
function ImageExpander(oThumb, sImgSrc)
{
// store thumbnail image and overwrite its onclick
handler.
this.oThumb = oThumb;
this.oThumb.expander = this;
this.oThumb.onclick = function() {
this.expander.expand(); }
// record original size
this.smallWidth = oThumb.offsetWidth;
this.smallHeight =
oThumb.offsetHeight;
// initial settings
this.bExpand = true;
this.bTicks = false;
// insert into self organized list
if ( !window.aImageExpanders )
window.aImageExpanders = new Array();
window.aImageExpanders.push(this);
// create the full sized image and automatically expand
when loaded
this.oImg = new Image();
this.oImg.expander = this;
this.oImg.onload = function(){this.expander.onload();}
this.oImg.src = sImgSrc;
}
The
ImageExpander constructor takes ownership of the thumbnail object and reassigns
the onclick handler to call its own expand. The this.bTicks flag is used to
indicate whether or not the animation engine is active. Initially, this value
is false as the ImageExpander must wait for the image to download before
starting the animation.
Once
expanded, the image will take up a large portion of the visible area of the
browser so to avoid confusion; only one ImageExpander should be allowed to
expand at any one time. To enforce this, the ImageExpander inserts itself into
an array held in the window object. We’ll see how this is used later.
Lastly,
the full size image is loaded into a new Image object. The onload handler on
the image is set to call the onload method on the ImageExpander.
ImageExpander.prototype.onload = function()
{
this.oDiv = document.createElement("div");
document.body.appendChild(this.oDiv);
this.oDiv.appendChild(this.oImg);
this.oDiv.style.position = "absolute";
this.oDiv.expander = this;
this.oDiv.onclick =
function(){this.expander.toggle();};
this.oImg.title = "Click to reduce.";
this.bigWidth = this.oImg.width;
this.bigHeight = this.oImg.height;
if ( this.bExpand )
{
this.expand();
}
else
{
this.oDiv.style.visibility =
"hidden";
this.oImg.style.visibility = "hidden";
}
}
Once the
image has loaded, the ImageExpander object can display the image in the
browser. First a <div> element is created to contain the image. The
<div> element is used to position the image on the screen and to handle
the onclick event as once the image has grown to full size, the user will want
to be able to reduce it by clicking on it again. At this point, the width and
height of the full size image can be retrieved, these values will be used to
calculate how big to make the image when it is expanded.
Now, it is
possible that while the image was downloading, the user may have clicked on
another thumbnail, so the this.bExpand flag must be tested to see whether to go
on with the expansion. If not, the image and its enclosing <div> are quickly
hidden from view.
ImageExpander.prototype.toggle = function()
{
this.bExpand = !this.bExpand;
if ( this.bExpand )
{
for ( var i in window.aImageExpanders
)
if (
window.aImageExpanders[i] !== this )
window.aImageExpanders[i].reduce();
}
}
The toggle
method is called by the onclick handler of the full size image. All this
function does is to reverse the direction of the image so the users can change
their minds if they desire. It is not necessary to start the animation because
this function can only be called while the animation is already running. So all
that is required is to change the this.bExpand flag and force all other
ImageExpander objects to reduce.
ImageExpander.prototype.expand = function()
{
// set direction of expansion.
this.bExpand = true;
// set all other images to reduce
for ( var i in window.aImageExpanders )
if ( window.aImageExpanders[i] !==
this )
window.aImageExpanders[i].reduce();
// if not loaded, don't continue just yet
if ( !this.oDiv ) return;
// hide the thumbnail
this.oThumb.style.visibility = "hidden";
// calculate initial dimensions
this.x = this.oThumb.offsetLeft;
this.y = this.oThumb.offsetTop;
this.w = this.oThumb.clientWidth;
this.h = this.oThumb.clientHeight;
this.oDiv.style.left = this.x + "px";
this.oDiv.style.top = this.y + "px";
this.oImg.style.width = this.w + "px";
this.oImg.style.height = this.h + "px";
this.oDiv.style.visibility = "visible";
this.oImg.style.visibility = "visible";
// start the animation engine.
if ( !this.bTicks )
{
this.bTicks = true;
var pThis = this;
window.setTimeout(function(){pThis.tick();},25);
}
}
The expand
method first sets the this.bExpand flag to true so that the animation engine
knows which direction it is going and then forces all other ImageExpander
objects to reduce. It is possible that this method may be called before the
image has downloaded so a check is done at this point and the method is exited
if not ready. Otherwise the thumbnail is hidden (although it will continue to
take up browser space) and the initial position and size of the full size image
are set to match the thumbnail. Then the animation engine is started to manage
the expansion of the image to full size.
ImageExpander.prototype.reduce = function()
{
// set direction of expansion.
this.bExpand = false;
}
All that
is needed to reduce an image is to set the this.bExpand flag to false. If the
full size image is visible, then the animation engine will already be running
and changing this flag will control its direction. If the thumbnail image is
visible, then there is nothing to do as the image is already reduced as far as
it should go.
The
animation engine is contained in a single method of the ImageExpander object
called tick. As its name suggests, this function is called repeatedly at fast
enough intervals to make the image movements it controls appear to be smooth.
ImageExpander.prototype.tick = function()
{
// calculate screen dimensions
var cw = document.body.clientWidth;
var ch = document.body.clientHeight;
var cx = document.body.scrollLeft + cw / 2;
var cy = document.body.scrollTop + ch / 2;
The first
thing the tick does is to calculate the browser page dimensions – at least that
part that is visible to the user.
The next
task is to calculate the target size and position; this will depend on the
direction of the animation defined by the this.bExpand flag.
If
the image is expanding then the original dimensions of the image are taken and
then reduced to fit the page if necessary:
// calculate target
var tw,th,tx,ty;
if ( this.bExpand )
{
// start with the full size
dimensions
tw = this.bigWidth;
th = this.bigHeight;
// reduce to fit the screen
if ( tw > cw )
{
th *= cw / tw;
tw = cw;
}
if ( th > ch )
{
tw *= ch / th;
th = ch;
}
// then center it on the page
tx = cx - tw / 2;
ty = cy - th / 2;
}
Otherwise
the current size and location of the thumbnail image are used. Note that if the
browser window changes size, then the actual position of the thumbnail can
change accordingly, so this must be calculated for each tick.
else
{
tw = this.smallWidth;
th = this.smallHeight;
tx = this.oThumb.offsetLeft;
ty = this.oThumb.offsetTop;
}
Once the
target size and position are calculated, we can use some algorithm to move the
full size image towards the target. In the following code, each dimension;
left, top, width and height are moved 10% closer to its target. While 10% might
sound like a lot, the size of movement will reduce as the target comes closer,
so the effect is to start with an initial burst of speed and then slowing down
as it approaches the target position and size.
The
algorithm below is contained inside a nested function which is assigned to the
variable fMove. This function operates on each dimension and does two things:
it calculates the value 10% closer to the target and counts the dimensions that
a getting very close to the target value – less than three pixels away. This count
is declared outside the function but it is still accessible as nested functions
have access to the context in which they are defined.
// move 10% closer to target
var nHit = 0;
var fMove = function(n,tn)
{
var dn = tn - n;
if ( Math.abs(dn) < 3 )
{
nHit++;
return tn;
}
else
{
return n + dn / 10;
}
}
this.x = fMove(this.x, tx);
this.y = fMove(this.y, ty);
this.w = fMove(this.w, tw);
this.h = fMove(this.h, th);
this.oDiv.style.left = this.x + "px";
this.oDiv.style.top = this.y + "px";
this.oImg.style.width = this.w + "px";
this.oImg.style.height = this.h + "px";
If the
image is reducing and all four dimensions (left, top, width and height) have
hit their target, then the animation engine is stopped by switching off the
this.bTicks flag, hiding the full size image and showing the thumbnail again.
Expanding images stay active in case the user changes the size of the browser
window.
// if reducing and size/position is a match, stop the
tick
if ( !this.bExpand && (nHit == 4) )
{
this.oImg.style.visibility =
"hidden";
this.oDiv.style.visibility =
"hidden";
this.oThumb.style.visibility =
"visible";
this.bTicks = false;
}
Finally,
if still active, the animation engine schedules another tick a short time in
the future.
if ( this.bTicks )
{
var pThis = this;
window.setTimeout(function(){pThis.tick();},25);
}
}
Now, with
this code in place, all that is left is to design the layout of the web page,
taking advantage of the extra space resulting from the smaller thumbnail
images. Remember to enclose each thumbnail in a link tag to cater for those
users who do not have JavaScript.
Conclusion
In this
article I have presented a JavaScript class that will manage a pair of images;
a full-sized image and its thumbnail. When the user clicks on the thumbnail,
the ImageExpander code will enlarge the image until it either fills the browser
or reaches its full size – whichever comes first. A second click on the image
or clicking on another thumbnail reduces the image back to its original
position.
No comments:
Post a Comment