How I coded my lightbox

The short version is that I used jQuery and CSS. It is all good and dandy to separate interactivity from presentation but then you must make them work together.

The long version of this story is stored on its github repository, and its demo. Just allow me to add a few comments along the way.

Enter the lighbox


For the HTML I made a decision for HTML5, using <figure> and <figcaption> for the dinamic content. That left me with a simple framework to display and present on the page. The js generates or updates this basic structure:

<figure class="lightbox">

<iframe id="lbitem" class="video" width="X" height="Y" 
src="" frameborder="0">

This is a caption


That is of course, in the case of a youtube video. An image is even simpler, with an img tag and switching class="video" for class= "image". The .lighbox class identifies the figure as an lighbox element so the CSS can tell it from other figures. Also the video dimensions are generated through CSS, while there is no need for that in the images.

The Javascript

I will assume it is a safe bet that you are aware of JS and jQuery basics. If not, please learn that first. That said, the complete implementation is available on the repository, so let me focus on the main points.

$("img").click(function(event) {
var is_video = $(this).hasClass('youtube');

} else {


I went for a functional approach, dividing my app into small, reusable functions.

The magic starts with this nameless function. It queries for an image element, checks if it is a video and then calls either the display figure and video or display figure and image functions accordingly. Both are very similar, yet the video is slightly more complex so I will only comment on that one.

function display_figure_and_video(thumbnail){
var src = 
var caption = 
var finished_iframe_element = 
var fig_caption = 
var dimensions = 
	get_video_dimensions();  /* width, height */

finished_iframe_element = 
	finished_iframe_element.replace('xxx', dimensions[0])
		.replace('yyy', dimensions[1])
		.replace('zzz', src);
fig_caption = 

$( $("body") )
.append( FIG_BEGIN + 
	finished_iframe_element + 
	fig_caption + FIG_END)
.append( RARROW )
.append( LARROW )
.prependTo( $("body") );



So this function takes the source attribute of the thumbnail image and then replaces its path with a string constant '' and deletes the .jpg tail. The figcaption is generated from the alt attribute of its thumbnail.

Then, it calls the get_video_dimensions() which returns an array containing the values for its width and height, based on the current width of the window while preserving its aspect ratio. That function follows right below.

function get_video_dimensions(){
var video_width = 1280;
var video_height = 720;
var aspect_ratio = video_height / video_width;
var win_width = $(window).width();

if (win_width < 800) {
	video_width =  win_width;
	video_height = video_width * aspect_ratio;
} else {
    if (win_width < 1680) {
          video_width =  win_width * 0.8;
          video_height = video_width * aspect_ratio;

return [video_width, video_height];

Once get_video_dimensions() gets its work done, all that remains is to append the generated html to the body. CSS does the rest, adding an almost opaque background. The right and left arrows are also prependend; finally the no_scroll_bar is prepended to the body elmenet to prevent, with CSS help, the scroll bar from appearing, as it should be useless in a lightbox.

Navigating the lightbox

We have the lightbox displaying; one video or image getting our full focus. Now we want to check the next image. A click on the arrows will trigger the display_new_item(next) function, with next equal to true for the right arrow1 or false for the left arrow2. This function checks if we are displaying an iframe or an image and also if the next item will be an image or video and does some adjustments based on that information.

In essence, all that it takes, is to gather the information for the next item and replace the text for the caption, and the all other attributes as necesarry. Nothing too fancy!

$($("body")).on('click', '#rarrow', function() {

$($("body")).on('click', '#larrow', function() {

function display_new_item(next){
var src = $('#lbitem').attr('src');
var is_video_displayed = $('#lbitem').hasClass('video');
var query = '';
var new_item = '';

if (is_video_displayed) {
src = src.replace(YOUTUBE_SOURCE_FIRST_PART,"img/thumbs/") + ".jpg";
} else {
src = src.slice(0, 3) + "/thumbs" + src.slice(3);


query = "img[src|='" + src + "']";
console.log('query', query);

new_item = $(query).next();
} else {
new_item = $(query).prev();

if (new_item.hasClass('youtube')){
} else {		

function display_new_image(new_item){
var is_video_displayed = $('#lbitem').hasClass('video');
var new_caption = '';
var new_src = '';
var finished_image_element;

if (is_video_displayed){
/* replace iframe with image */

new_src = new_item.attr('src').replace('thumbs/','');
new_caption = new_item.attr('alt');

" + new_caption + "
"); finished_image_element = IMAGE_ELEMENT.replace('xxx',new_src); $('#lbitem').replaceWith(finished_image_element); } else { new_src = new_item.attr('src').replace('thumbs/',''); new_caption = new_item.attr('alt'); $('figcaption').replaceWith("
" + new_caption + "
"); $('#lbitem').attr('src', new_src); } }

Last update: 2016/04/13