From our sponsor: Meco is a distraction-free space for reading and discovering newsletters, separate from the inbox.
After we got a lot of great feedback for our image galleries we decided to follow some of the suggestions and create a gallery that uses the Flickr API. The aim was to build a bottom photobar that one can easily integrate into a website. It is hidden initially and slides up when the handle is clicked. First, the photo sets are shown and when one of them is chosen, all its images can be viewed as thumbnails. When a thumbnail is clicked, a full image view appears as an overlay.
For our demo we used the awesome photostream by tibchris.
To get familiar with the Flickr API, you can find some information on the Flickr Services website.
So, let’s begin!
Tiny break: 📬 Want to stay up to date with frontend and trends in web design? Subscribe and get our Collective newsletter twice a tweek.
The Markup
The HTML consists of one main wrapper for the whole photobar. Inside of that we will have the div for the full image view and the div for the photobar with the album thumbnail wrapper and the images thumbnail wrapper. Besides that, we will also have our visible handle and a div for the overlay:
<!-- main wrapper: flickr_photobar --> <div class="flickr_photobar"> <!-- overlay for the full image view --> <div id="flickr_overlay" class="overlay" style="display:none;"></div> <!-- full image view --> <div id="flickr_photopreview" class="photopreview" style="display:none;"> <div class="preview_wrapper"> <div class="preview"> <div class="loading"></div> <div id="preview_close" class="close"></div> <span id="large_phototitle"></span> <!-- here we will insert the image--> <a href="#" class="img_next"></a> <a href="#" class="img_prev"></a> </div> </div> </div> <!-- the bottom photobar --> <div id="photobar" class="photobar"> <!-- the thumbnail view of the albums/sets --> <div class="thumbs albums" id="sets"> <a href="#" class="prev"></a> <div class="thumbsWrapper"> <ul></ul> </div> <a href="#" class="next"></a> </div> <!-- the thumbnail view of the images of a set --> <div class="thumbs images" id="images" style="bottom:-125px;"> <a href="#" class="prev"></a> <div class="thumbsWrapper"> <ul></ul> </div> <a href="#" class="next"></a> <!-- the right handle for the info--> <span class="images_toggle"> Set: <span id="setName"></span> <a id="images_toggle">Back to Sets</a> </span> </div> <!-- the left handle for the main photobar --> <a id="flickr_toggle" class="toggle"> Flickr Photostream <span style="visibility:hidden;" class="loading_small"></span> </a> </div> </div>
As you can see, a lot of elements will be hidden in the beginning. In the JavaScript function we will control the visibility and appearance of these elements.
Let’s have a look at the styling.
The CSS
Since we want this gallery to work as an integrated part of any website, we will give most of the elements a fixed position. That means, we will add them on top of everything else in the website. If you come across some problems concerning other items on you website that are on top of this gallery, you might want to adjust the z-indexes.
Ok, so let’s define some general styles first:
.flickr_photobar{ font-family:Arial,Helvetica,sans-serif; text-transform:uppercase; font-size:11px; } .flickr_photobar a{ outline:none; } .flickr_photobar a:hover{ outline:none; }
Since we will be abusing a lot of link elements for other purposes than links (OK, don’t beat me up now, I’ll try to get rid of that habit), we want to remove the outline, so that the ugly dotted line does not appear in Firefox.
The photobar div will have the following style:
.photobar{ position:fixed; bottom:-96px; left:0px; width:100%; height:95px; }
We will hide the photobar by setting it’s bottom to a negative value. The toggle (or handle) will still be visible because we will pull it up by setting it’s top value to a negative value. We will look into that class later.
The thumbs class will be applied to both, the div of the album (or set) thumbnails and the div of the image thumbnails belonging to the respective sets:
.thumbs{ position:absolute; bottom:0px; left:0px; width:100%; height:95px; border-top:1px solid #222; background-color:#3D3D3D; }
The styling of the previous and next arrows will be as follows:
.thumbs a.prev, .thumbs a.next{ width:20px; height:83px; position:absolute; top:4px; margin:0px; z-index:10; border:1px solid #222; -moz-box-shadow:0px 0px 1px #777 inset; -webkit-box-shadow:0px 0px 1px #777 inset; box-shadow:0px 0px 1px #777 inset; } .thumbs a.prev:hover, .thumbs a.next:hover{ background-color:#404040; } .thumbs a.prev{ left:0px; background:#333 url(../prev.png) no-repeat center center; } .thumbs a.next{ right:0px; background:#333 url(../next.png) no-repeat center center; }
With the inset property in the box shadow, we can create a nice effect: if you have a dark background and add a light inset box shadow, the element will look slightly embossed.
The wrapper for the thumbs is going to have the following style:
.thumbs .thumbsWrapper{ height:95px; left:22px; right:22px; overflow:hidden; position:absolute; top:0; }
The unordered list for the thumbnails will be positioned absolutely and we will hide any overflow. It’s width is going to be calculated dynamically and it depends on the amount of thumbnails.
.thumbs ul{ list-style:none; margin:0px; padding:0px; height:90px; overflow:hidden; position:absolute; left:0px; top:0px; } .thumbs ul li a{ position:relative; float:left; margin:6px 2px 0px 2px; color:#fff; text-shadow:1px 1px 1px #000; text-decoration:none; height:81px; width:81px; }
We will give the albums/sets thumbnails a dark border and the images thumbnails a light border:
.albums ul li a img{ border:3px solid #111111; -moz-box-shadow:1px 1px 3px #000; -webkit-box-shadow:1px 1px 3px #000; box-shadow:1px 1px 3px #000; } .images ul li a img{ border:3px solid #f9f9f9; -moz-box-shadow:1px 1px 3px #000; -webkit-box-shadow:1px 1px 3px #000; box-shadow:1px 1px 3px #000; }
The description for each set or image is going to appear on top of each thumbnail. We will make it slightly transparent and not allow the text to overflow. While in webkit browsers we can use text-overflow:ellipsis (which cuts off too long words and adds two dots) we need to set overflow:hidden for the other browsers:
.thumbs a span{ position:absolute; bottom:3px; left:3px; right:3px; background-color:#333; font-size:9px; padding:2px 2px; border-top:1px solid #111; display:none; text-align:center; overflow:hidden; text-overflow:ellipsis; max-height:70px; opacity:0.8; filter:progid:DXImageTransform.Microsoft.Alpha(opacity=80); }
This is how we actually show the description span – when we hover over the image link:
.thumbs a:hover span{ display:block; }
The handle for the images that appears on the right side and the main handle for the photobar will have the following style:
span.images_toggle{ position:absolute; top:-26px; right:20px; background-color:#3D3D3D; border:1px solid #222; color:#EEEEEE; font-size:10px; padding:0px 6px 0px 12px; height:24px; line-height:24px; text-transform:uppercase; text-shadow:1px 1px 2px #000; -moz-box-shadow:0px -1px 3px #ccc; -webkit-box-shadow:0px -1px 3px #ccc; box-shadow:0px -1px 3px #ccc; -moz-border-radius:5px 5px 0px 0px; -webkit-border-top-left-radius:5px; -webkit-border-top-right-radius:5px; border-top-left-radius:5px; border-top-right-radius:5px; } span.images_toggle a{ background-color:#222; border:1px solid #000; cursor:pointer; line-height:16px; padding:0px 5px; -moz-border-radius:5px; -webkit-border-radius:5px; border-radius:5px; } span.images_toggle a:hover{ background-color:#000; } .photobar a.toggle{ position:absolute; top:-26px; left:20px; background-color:#3D3D3D; border:1px solid #222; color:#EEEEEE; font-size:10px; padding:0px 36px 0px 36px; line-height:24px; height:24px; text-transform:uppercase; text-shadow:1px 1px 2px #000; -moz-box-shadow:0px -1px 3px #ccc; -webkit-box-shadow:0px -1px 3px #ccc; box-shadow:0px -1px 3px #ccc; -moz-border-radius:5px 5px 0px 0px; -webkit-border-top-left-radius:5px; -webkit-border-top-right-radius:5px; border-top-left-radius:5px; border-top-right-radius:5px; cursor:pointer; } span.loading_small{ background:transparent url(../loading_small.gif) no-repeat center center; position:absolute; right:10px; top:0px; width:16px; height:24px; } .photobar a.toggle:hover{ background-color:#111; }
The images thumbnails container needs to have a higher z-index than the one of the sets:
.photobar .images{ z-index:20; }
The overlay that appears when we show one full image is going to have the following style:
.flickr_photobar .overlay{ z-index:90; background-color:#000; width:100%; height:100%; position:fixed; top:0px; left:0px; opacity:0.9; filter:progid:DXImageTransform.Microsoft.Alpha(opacity=90); }
We make the position fixed so that when the user scrolls the page, it always stays in the visible area of the page:
.photopreview{ text-align:center; position:fixed; width:100%; height:100%; top:0px; left:0px; z-index:91; }
The following wrappers for the full image view will need the following styling because we want the image to be centered vertically and horizontally:
.photopreview .preview_wrapper{ position:relative; text-align:center; margin:0 auto; } .photopreview .preview{ display:table-cell; text-align:center; width:0px; height:0px; padding-top:25px; vertical-align:middle; } .photopreview .preview img{ vertical-align:middle; background-color:#555; padding:1px; border:8px solid #f9f9f9; -moz-box-shadow:1px 1px 5px #222; -webkit-box-shadow:1px 1px 5px #222; box-shadow:1px 1px 5px #222; }
The description for the image is going to be put into a span that is fixed at the top of the page:
.photopreview .preview span{ background-color: #111111; color:#FFFFFF; height:20px; left:0; line-height:20px; position:fixed; text-align:center; text-shadow:1px 1px 1px #000000; top:0; width:100%; -moz-box-shadow:1px 1px 5px #000000; -webkit-box-shadow:1px 1px 5px #000000; box-shadow:1px 1px 5px #000000; }
The little loading div that appears when an image is loaded is going to have the following style:
.loading{ width:50px; height:50px; position:fixed; top:50%; left:50%; z-index:95; margin:-25px 0px 0px -25px; -moz-border-radius:10px; -webkit-border-radius:20px; border-radius:10px; background:#000 url(../loading.gif) no-repeat center center; opacity:0.8; filter:progid:DXImageTransform.Microsoft.Alpha(opacity=80); }
We will add a close element at the top right corner that allows the user to close the image preview:
.close{ background:#000 url(../close.png) no-repeat center center; cursor:pointer; height:20px; position:fixed; right:-11px; top:0; width:90px; z-index:1000; cursor:pointer; -moz-border-radius:10px; -webkit-border-radius:10px; border-radius:10px; opacity:0.8; filter:progid:DXImageTransform.Microsoft.Alpha(opacity=80); }
The next and previous controls will be styled as follows:
.photopreview a.img_next, .photopreview a.img_prev{ position:fixed; top:50%; height:60px; width:50px; margin-top:-30px; background-color:#000; background-repeat:no-repeat; background-position:center center; } .photopreview a.img_next{ background-image:url(../next.png); -moz-border-radius:20px 0px 0px 20px; -webkit-border-top-left-radius:20px; -webkit-border-bottom-left-radius:20px; border-top-left-radius:20px; border-bottom-left-radius:20px; right:0px; } .photopreview a.img_prev{ background-image:url(../prev.png); -moz-border-radius:0px 20px 20px 0px; -webkit-border-top-right-radius:20px; -webkit-border-bottom-right-radius:20px; border-top-right-radius:20px; border-bottom-right-radius:20px; left:0px; }
And that’s all the style!
Let’s add some JavaScript magic!
The JavaScript
For our script we will be using the jQuery viewport script.
In the following we will show some important snippets of our script since it is a very large script. You can view the whole commented script when you download the ZIP file.
Let’s start at the beginning of the script where we need to define some variables:
var api_key = [your API Key]; var user_id = [your Flickr user ID]; /* use: Square,Thumbnail,Small,Medium or Original for the large image size you want to load! */ var large_image_size = 'Medium'; /* the current Set id / the current Photo id */ var photo_set_id,photo_id; /* the current position of the image being viewed */ var current = -1; var continueNavigation = false; /* flickr API Call to get List of Sets */ var sets_service = 'http://api.flickr.com/services/rest/?&method=flickr.photosets.getList' + '&api_key=' + api_key; var sets_url = sets_service + '&user_id=' + user_id + '&format=json&jsoncallback=?'; /* flickr API Call to get List of Photos from a Set */ var photos_service = 'http://api.flickr.com/services/rest/?&method=flickr.photosets.getPhotos' + '&api_key=' + api_key; /* flickr API Call to get List of Sizes of a Photo */ var large_photo_service = 'http://api.flickr.com/services/rest/?&method=flickr.photos.getSizes' + '&api_key=' + api_key; /* elements caching... */ var $setsContainer = $('#sets').find('ul'); var $photosContainer = $('#images').find('ul'); var $photopreview = $('#flickr_photopreview'); var $flickrOverlay = $('#flickr_overlay'); var $loadingStatus = $('#flickr_toggle').find('.loading_small'); var ul_width,spacefit,fit;
Our first step is to load all the sets of the respective user. The following code we will do that:
/* start: open Flickr Photostream */ $('#flickr_toggle').toggle(function(){ $('#photobar').stop().animate({'bottom':'0px'},200,function(){ if($setsContainer.is(':empty')){ /* if sets not loaded, load them */ LoadSets(); } }); },function(){ /* minimize the main bar, and minimize the photos bar. next time we maximize, the view will be on the sets */ $('#photobar').stop().animate({'bottom':'-96px'},200,function(){ $('#images').css('bottom','-125px'); }); }); /* Loads the User Photo Sets */ function LoadSets(){ $loadingStatus.css('visibility','visible'); $.getJSON(sets_url,function(data){ if(data.stat != 'fail') { var sets_count = data.photosets.photoset.length; /* adapt ul width based on number of results */ ul_width = sets_count * 85 + 85; $setsContainer.css('width',ul_width + 'px'); for(var i = 0; i < sets_count; ++i){ var photoset = data.photosets.photoset[i]; var primary = photoset.primary; var secret = photoset.secret; var server = photoset.server; var farm = photoset.farm; /* source for the small thumbnail */ var photoUrl = 'http://farm'+farm+'.static.flickr.com/'+server+'/'+primary+'_'+secret+'_s.jpg'; var $elem = $('<li />'); var $link = $('<a class="toLoad" href="#" />'); /* save the info of the set in the li element, we will use it later */ $link.data({ 'primary' :primary, 'secret' :secret, 'server' :server, 'farm' :farm, 'photoUrl' :photoUrl, 'setName' :photoset.title._content, 'id' :photoset.id }); $setsContainer.append($elem.append($link)); $link.bind('click',function(e){ var $this = $(this); /* save the current Set id in the photo_set_id variable and load the photos of that Set */ $('#images').stop().animate({'bottom':'0px'},200); if(photo_set_id!=$this.data('id')){ photo_set_id = $this.data('id'); $('#setName').html($this.data('setName')); LoadPhotos(); } e.preventDefault(); }); } /* now we load the images (the ones in the viewport) */ LoadSetsImages(); } }); } /* loads the images of the sets that are in the viewport */ function LoadSetsImages(){ var toLoad = $('.toLoad:in-viewport').size(); if(toLoad > 0) $loadingStatus.css('visibility','visible'); var images_loaded = 0; $('.toLoad:in-viewport').each(function(i){ var $space = $setsContainer.find('.toLoad:first'); var $img = $('<img style="display:none;" />').load(function(){ ++images_loaded; if(images_loaded == toLoad){ $loadingStatus.css('visibility','hidden'); $setsContainer.find('img').fadeIn(); } }).attr('src',$space.data('photoUrl')).attr('alt',$space.data('id')); var $set_name = $('<span />',{'html':$space.data('setName')}); $space.append($set_name).append($img).removeClass('toLoad'); }); }
The next functions are for loading the photos of a specific set that is clicked:
/* Loads the Set's Photos */ function LoadPhotos(){ $photosContainer.empty(); $loadingStatus.css('visibility','visible'); var photos_url = photos_service + '&photoset_id=' + photo_set_id + '&media=photos&format=json&jsoncallback=?'; $.getJSON(photos_url,function(data){ if(data.stat != 'fail') { var photo_count = data.photoset.photo.length; /* adapt ul width based on number of results */ var photo_count_total = photo_count + $photosContainer.children('li').length; ul_width = photo_count_total * 85 + 85; $photosContainer.css('width',ul_width + 'px'); for(var i = 0; i < photo_count; ++i){ var photo = data.photoset.photo[i]; var photoid = photo.id; var secret = photo.secret; var server = photo.server; var farm = photo.farm; var photoUrl = 'http://farm'+farm+'.static.flickr.com/'+server+'/'+photoid+'_'+secret+'_s.jpg'; var $elem = $('<li />'); var $link = $('<a class="toLoad" href="#" />'); $link.data({ 'photoid' :photoid, 'secret' :secret, 'server' :server, 'farm' :farm, 'photoUrl' :photoUrl, 'photo_title' :photo.title }); $photosContainer.append($elem.append($link)); $link.bind('click',function(e){ var $this = $(this); current = $this.parent().index(); photo_id = $this.data('photoid'); LoadLargePhoto(); e.preventDefault(); }); } LoadPhotosImages(); } }); } /* loads the images of the set's photos that are in the viewport */ function LoadPhotosImages(){ var toLoad = $('.toLoad:in-viewport').size(); if(toLoad > 0) $loadingStatus.css('visibility','visible'); var images_loaded = 0; $('.toLoad:in-viewport').each(function(i){ var $space = $photosContainer.find('.toLoad:first'); var $img = $('<img style="display:none;" />').load(function(){ ++images_loaded; if(images_loaded == toLoad){ $loadingStatus.css('visibility','hidden'); $photosContainer.find('img').fadeIn(); /* if we were navigating through the large images set: */ if(continueNavigation){ continueNavigation = false; var $thumb = $photosContainer.find('li:nth-child(' + parseInt(current + 1) + ')').find('img'); photo_id = $thumb.attr('alt'); LoadLargePhoto(); } } }).attr('src',$space.data('photoUrl')) .attr('alt',$space.data('photoid')); var $photo_title = $('<span/>',{'html':$space.data('photo_title')}); $space.append($photo_title).append($img).removeClass('toLoad'); }); }
Now we need a function that loads the full image:
/* loads the main image */ function LoadLargePhoto(){ removeLargeImage(); var $theThumb = $photosContainer.find('li:nth-child(' + parseInt(current + 1) + ')').find('img'); var photo_title = $theThumb.parent().data('photo_title'); var $loading = $photopreview.find('.loading'); $loading.show(); $photopreview.show(); $flickrOverlay.show(); $('#preview_close').show(); var large_photo_url = large_photo_service + '&photo_id=' + photo_id + '&format=json&jsoncallback=?'; $.getJSON(large_photo_url,function(data){ if(data.stat != 'fail') { var count_sizes = data.sizes.size.length; var largest_photo_src; for(var i = 0; i < count_sizes; ++i){ if(data.sizes.size[i].label == large_image_size) largest_photo_src = data.sizes.size[i].source; } $('<img />').load(function(){ var $this = $(this); /* resize the image to fit in the browser's window */ resize($this); $loading.hide(); /*just to make sure there's no image:*/ removeLargeImage(); $photopreview.find('.preview').append($this); $('#large_phototitle').empty().html(photo_title); }).attr('src',largest_photo_src); } }); }
The next functions and events (that we will not discuss in more detail here) control the basic navigation through the thumbnails and the preview images. We also add a resize function that adapts the size of the image to the current viewport. Check out the script in the ZIP file where you can see the comments.
And that’s it! I hope you enjoyed this tutorial and find it useful!
Everything wors fine, but when i copy (use) my API key and ID, the PREV & NEXT button (arrow) in the thumbsbar don’t work.. (sure i have o lot of images in the set…) when i use the original API key and ID from this example.. its OK – i don’t understand it..
@Ernest Just change the $(‘#flickr_toggle’).toggle to $(‘#flickr_toggle’).ready and it loads up without clicking 🙂
Hello, very nice done
say maybe in the future, implementing a the same, only with youtube?
Is it possible to load flickr collections in it, instead of sets?
next and previous scroll buttons dont seem to work in chrome.. the bar does not scroll .. anyone know a fix for this?? Thanks:)
I don’t want to show set as I have just single set of images in flickr. So how can i remove set and show images right away?
Hi, Does anyone know the fix for the Chrome or ff prev and next arrows not sliding as they should and do in IE9.. They dont seem to slide to the next batch of photos..?? anyone else come across this?
Hi, Does anyone know of a fix for Firefox and Google Chrome for this as the navgation bars dont seem to work as the thumbs bar will not slide but it seems to work on IE9.. Any ideas?
I have the same problem as NEAL. Everything wors fine, but when i copy (use) my API key and ID, the PREV & NEXT button (arrow) in the thumbsbar don’t work.. (sure i have o lot of images in the set…) when i use the original API key and ID from this example.. its OK – i don’t understand it.. Do you know a solution to this? Doesnt work in Chrome , safari and firefox.
Just implemented the stuff on my website. Good stuff!
I’m wondering if it’s possible to use a background image instead of the solid colour?
And if so, how?
Wonderful. Thks
In the source code, I noticed there is 2 within the tags, just before and after the prev/next buttons when previewing a photo. I’m wanting to remove these tags, however I cannot find where these tags are located. I don’t see them in the js files or html file.
Any help?
Nevermind, I figured out what the problem was. It was my editor, not the script. I love this script!
I used this for inspiration in my website gallery. Thank you very much!
http://www.nestorandres.com/galeria
if you want your flickr sets of images to be automatically loaded on pageload you need to add one line in jquery.FlickrPhotobar.js file after line 38:
var ul_width,spacefit,fit;
add:
LoadSets();
that’s it 🙂
@Mary Lou,
Great script. Works well, + nice layout.
I needed it to work w/ Picasa … so I modified it based on another snippet I found elsewhere ( http://www.paulvanroekel.nl/picasa-web-restyled/integrator/ ). I’ve got it working in major browsers, -IE9. I’m happy to share.
Hi! Great Job.
On iPad and iPhone there is a problem: when I rotate the device, the plugin doesn’t replace correctly (safari for iPhone/iPad), going over the content.
Hi.. I am just wondering if anybody figured out the problem on FF and chrome as the next and previous in the scrollbar do not seem to work but do in IE9. Any help would be appreciated on this as I am pulling my hair out over it. Also as per the above request.. Is there any way to fix it for Iphone and Ipad as it seems to jump from the bottom of the page..
Thanks
@Stuart Dutton
It works fine for me in Chrome.
Is there anyway to make it drop from the top of the page, instead of the bottom?
Is there a way to just show all of your images instead of sets? I don’t want any sets.
why can’t show out the large image when i change the “medium” to “original”?
change “small” and others is normal worked but “original”…
help me please:)
—————————————————
use:Square,Thumbnail,Small,Medium or Original forthe large image size you want to load!
var large_image_size = ‘Medium’;
Very nice!
Had to change;
<a id=”preview_img_next” href=”#” class=”img_next”></a>
<a id=”preview_img_prev” href=”#” class=”img_prev”></a>
for it to work in FF.
Thanks,
Mike
I used this in my tumblr code and the thumbnails are not loading. Can anyone help me with this?
Thanks
When I set
var large_image_size= ‘Medium’;
to “Original” it doesn’t work.
Any help please?