HowTo make a Canvas Slideshow with JQuery
This mini howto explain how i have created this simple jquery plugin so you can easily modify it if needed!
You can find the documentation about the plugin at This Post
First of all we create our "jquery enclosure" since we want to make sure we are using jquery and not other library!
(function($) {})(jQuery);
All standard till now, let's start with the structure.
Commonly i first decide what basic method (or function) i'll need and so i create the skeleton of my app.
We'll need a main function that makes it all start, let's call it "canvasSlideshow", we will need a function to draw in our canvas and respectively a function to draw the outer box of the image, the image description and the slideshow controll, let's call them "draw_box", "draw_text" and "draw_control".
This is our structure till now:
(function($) { $.fn.canvasSlideshow = function() { }; $.fn.draw = function() { }; $.fn.draw_box = function() { }; $.fn.draw_text = function() { }; $.fn.draw_control = function() { }; })(jQuery);
To controll our app we will need some variables, let's see...
- We will need to know our canvas size, so "cnv_width" and "cnv_height"
- We will need to know the list of image to show, we can store it in an "imgs" array.
- We need to know how many image we want to show in a simgle shot and the image size, so "num_elem", "img_width" and "img_height"
- We need to know at which speed our app will move, to know this we need 2 params : one for distance between steps and one to know the single frame time.
I called them "speed" and "step"
We can put all this parameters in an Hash and maybe store some default value.
$.fn.canvasSlideshow.defaults = { img_width: 200, //image width img_height: 150, //image height num_elem: 3, //number of element to show in 1 screen speed: 2000, //time distance between each step step: 20 //step time };
Now we can start build our main function: "$.fn.canvasSlideshow".
As you can see i have not put all the variables inside the default block, this is cause some of them can be taken directly from DOM or mathematically.
To do so as our main function start we will first merge our defaults with the params the user will give us and then extend the params with some other variables.
I have used "_this" to identify the canvas and i assume the image list is inside the canvas as child elements.
The "canvas_context" variables will be used to store the canvas context.
$.fn.canvasSlideshow = function(options) { var _this = $(this), opts = $.extend({}, $.fn.canvasSlideshow.defaults, options), context = $.fn.extend(opts, { imgs: _this.find('img'),//images array cnv_width: _this.width(), //canvas width cnv_height: _this.height(), //canvas height canvas_context: _this[0].getContext('2d'), white_space: 0 // white space between images }); };
As you can see i've added a "white_space" var, it's use is to know the distance between 2 images, let's calculate it:
context.white_space = Math.floor((context.cnv_width - (context.img_width+2*context.border_size)*context.num_elem)/(context.num_elem));
Next we set the starting position for X and Y axis (better to add them to params too), and hide all the canvas child for browser not compatible. This the situation by now:
$.fn.canvasSlideshow = function(options) { var _this = $(this), opts = $.extend({}, $.fn.canvasSlideshow.defaults, options), context = $.fn.extend(opts, { imgs: _this.find('img'),//images array cnv_width: _this.width(), //canvas width cnv_height: _this.height(), //canvas height canvas_context: _this[0].getContext('2d'), white_space: 0, // white space between images start_pos_x: 0, pos_y: 0 }); context.white_space = Math.floor((context.cnv_width - (context.img_width+2*context.border_size)*context.num_elem)/(context.num_elem)); context.start_pos_x = context.white_space; context.pos_y = 2*context.border_size; _this.children().hide(); }
Ok, it's time to build the real core of our application, the function that will control the movement of our images. I choose to call it "canvas_slideshow.start" i'll put it in a var and bind it to our canvas element.
var load_canvas_slideshow = function() { _this.bind('canvas_slideshow.start', function(){ }); };
This function need to know something while it execute:
- How many times it was called before
- What's the current position
- What's the current image
So it can know if we need to move the image or to wait for the next step. When it's time to move the frame it'll set call the "draw" method. When the frame will be at the desired position it'll reset the params to initial value to prepare the next movement.
_this.bind('canvas_slideshow.start', function(){ context.actual_step++; if (context.actual_step >= (context.speed/context.step)) { context.start_pos_x -= 4; $.fn.draw(context); context.moving = 1; } if (-context.start_pos_x >= context.img_width+5) { context.actual_step = 0; context.start_pos_x = context.white_space -10; context.act_index++; context.moving = 0; if (context.act_index == context.imgs.length) context.act_index = 0; } });
I added the moving var just to know if we are in the middle of an animation or not, for future reference and the "act_index" to know which image is the first one to draw.
Now we need to call this function repetedly, i'll store it in context, we need also to call "load_canvas_slideshow" as the app javascript is loaded.
slideshow_interval = setInterval(function(){_this.trigger('canvas_slideshow.start');},context.step); load_canvas_slideshow();
This our app so far:
(function($) { var slideshow_interval; $.fn.canvasSlideshow = function(options) { var _this = $(this), opts = $.extend({}, $.fn.canvasSlideshow.defaults, options), context = $.fn.extend(opts, { imgs: _this.find('img'),//images array cnv_width: _this.width(), //canvas width cnv_height: _this.height(), //canvas height canvas_context: _this[0].getContext('2d'),, act_index: 0, actual_step: 0, white_space: 0, // white space between images start_pos_x: 0, pos_y: 0, moving: 0, slideshow_interval: null }); context.white_space = Math.floor((context.cnv_width - (context.img_width+2*context.border_size)*context.num_elem)/(context.num_elem)); context.start_pos_x = context.white_space; context.pos_y = 2*context.border_size; _this.children().hide(); var load_canvas_slideshow = function() { _this.bind('canvas_slideshow.start', function(){ context.actual_step++; if (context.actual_step >= (context.speed/context.step)) { context.start_pos_x -= 4; $.fn.draw(context); context.moving = 1; } if (-context.start_pos_x >= context.img_width+5) { context.actual_step = 0; context.start_pos_x = context.white_space -10; context.act_index++; context.moving = 0; if (context.act_index == context.imgs.length) context.act_index = 0; } }); slideshow_interval = setInterval(function(){ _this.trigger('canvas_slideshow.start'); },context.step); load_canvas_slideshow(); }; } $.fn.draw = function() { }; $.fn.draw_box = function() { }; $.fn.draw_text = function() { }; $.fn.draw_control = function() { }; $.fn.canvasSlideshow.defaults = { img_width: 200, //image width img_height: 150, //image height num_elem: 3, //number of element to show in 1 screen speed: 2000, //time distance between each step step: 20 //step time }; })(jQuery);
Time for the draw function, this the thing we need to do:
- Clean the canvas
- Know where we will place first image
- Draw an image
- Move by a "white_space"
- Cicle 3 and 4 till "num_elem"
This is it:
$.fn.draw = function(context) { context.canvas_context.clearRect(0, 0, context.cnv_width, context.cnv_height); context.pos_x = context.start_pos_x; for (i=context.act_index;i<=(context.act_index+context.num_elem+1);i++) { current_i = (i >= context.imgs.length) ? i-context.imgs.length : i; if (context.pos_x < context.cnv_width) { context.canvas_context.drawImage( context.imgs[current_i], context.pos_x, context.pos_y, context.img_width, context.img_height); } context.pos_x += (context.white_space + context.img_width); } };
We just add a call to "load_canvas_slideshow" to have a working app, but we have not finished, we want to add many more feature!
So let's add a box around our image to make it look nice, we will need 3 more var to manage this one for shadow size "shadow_size", one for border size "border_size" and one to know if we have to show the box or not "show_box" (we will add this to default params):
$.fn.draw_box = function(context) { context.canvas_context.shadowBlur = 10; context.canvas_context.shadowColor = "#cccccc"; context.canvas_context.shadowOffsetX = context.shadow_size; context.canvas_context.shadowOffsetY = context.shadow_size; context.canvas_context.fillStyle = '#000000'; context.canvas_context.fillRect(context.pos_x-context.border_size, context.pos_y-context.border_size, (context.img_width + 2*context.border_size), (context.img_height + 2*context.border_size)); context.canvas_context.shadowBlur = 0; context.canvas_context.shadowOffsetX = 0; context.canvas_context.shadowOffsetY = 0; };
Let's call this function before drawing our image:
if (context.pos_x < context.cnv_width) { if (context.show_box == true) $.fn.draw_box(context); context.canvas_context.drawImage( context.imgs[current_i], context.pos_x, context.pos_y, context.img_width, context.img_height); }
Well why make a box if we don't write the image description?
We will put it just after the image, but we need to split it at least in 2 if it's too long, we will use the image alt attribute for text and a new params called "show_description" to enable or disable it.
$.fn.draw_text = function(context) { context.canvas_context.shadowBlur = 20; context.canvas_context.shadowColor = "#ffffff"; context.canvas_context.shadowOffsetX = 2; context.canvas_context.shadowOffsetY = 1; description_text = context.imgs[current_i].alt; context.canvas_context.font = "Times new roman"; context.canvas_context.textAlign = "center"; context.canvas_context.textBaseline = "middle"; context.canvas_context.fillStyle = '#ffffff'; metrics = context.canvas_context.measureText(description_text); if (metrics.width > context.img_width) { context.canvas_context.fillText(description_text.slice(0,Math.floor((description_text.length)/2)), context.pos_x+Math.floor(context.img_width/2), (context.pos_y+context.img_height+10), context.img_width); context.canvas_context.fillText(description_text.slice(Math.floor((description_text.length)/2)), context.pos_x+Math.floor(context.img_width/2), (context.pos_y+context.img_height+30), context.img_width); } else context.canvas_context.fillText(description_text, context.pos_x+Math.floor(context.img_width/2), (context.pos_y+context.img_height+10), context.img_width); context.canvas_context.shadowBlur = 0; context.canvas_context.shadowOffsetX = 0; context.canvas_context.shadowOffsetY = 0; };
All together adding the check for description text inside the "draw_box" and the "draw" function.
(function($) { var slideshow_interval; $.fn.canvasSlideshow = function(options) { var _this = $(this), opts = $.extend({}, $.fn.canvasSlideshow.defaults, options), context = $.fn.extend(opts, { imgs: _this.find('img'),//images array cnv_width: _this.width(), //canvas width cnv_height: _this.height(), //canvas height canvas_context: _this[0].getContext('2d'),, act_index: 0, actual_step: 0, white_space: 0, // white space between images start_pos_x: 0, pos_y: 0, moving: 0, slideshow_interval: null }); context.white_space = Math.floor((context.cnv_width - (context.img_width+2*context.border_size)*context.num_elem)/(context.num_elem)); context.start_pos_x = context.white_space; context.pos_y = 2*context.border_size; _this.children().hide(); var load_canvas_slideshow = function() { _this.bind('canvas_slideshow.start', function(){ context.actual_step++; if (context.actual_step >= (context.speed/context.step)) { context.start_pos_x -= 4; $.fn.draw(context); context.moving = 1; } if (-context.start_pos_x >= context.img_width+5) { context.actual_step = 0; context.start_pos_x = context.white_space -10; context.act_index++; context.moving = 0; if (context.act_index == context.imgs.length) context.act_index = 0; } }); slideshow_interval = setInterval(function(){ _this.trigger('canvas_slideshow.start'); },context.step); load_canvas_slideshow(); }; } $.fn.draw = function(context) { context.canvas_context.clearRect(0, 0, context.cnv_width, context.cnv_height); context.pos_x = context.start_pos_x; for (i=context.act_index;i<=(context.act_index+context.num_elem+1);i++) { current_i = (i >= context.imgs.length) ? i-context.imgs.length : i; if (context.pos_x < context.cnv_width) { if (context.show_box == true) $.fn.draw_box(context); context.canvas_context.drawImage(context.imgs[current_i], context.pos_x, context.pos_y, context.img_width, context.img_height); if (context.show_description == true) $.fn.draw_text(context); } context.pos_x += (context.white_space + context.img_width); } }; $.fn.draw_box = function(context) { context.canvas_context.shadowBlur = 10; context.canvas_context.shadowColor = "#cccccc"; context.canvas_context.shadowOffsetX = context.shadow_size; context.canvas_context.shadowOffsetY = context.shadow_size; context.canvas_context.fillStyle = '#000000'; context.canvas_context.fillRect(context.pos_x-context.border_size, context.pos_y-context.border_size, (context.img_width + 2*context.border_size), (context.img_height + 2*context.border_size + ((context.show_description == true) ? 40 : 0))); context.canvas_context.shadowBlur = 0; context.canvas_context.shadowOffsetX = 0; context.canvas_context.shadowOffsetY = 0; }; $.fn.draw_text = function(context) { context.canvas_context.shadowBlur = 20; context.canvas_context.shadowColor = "#ffffff"; context.canvas_context.shadowOffsetX = 2; context.canvas_context.shadowOffsetY = 1; description_text = context.imgs[current_i].alt; context.canvas_context.font = "Times new roman"; context.canvas_context.textAlign = "center"; context.canvas_context.textBaseline = "middle"; context.canvas_context.fillStyle = '#ffffff'; metrics = context.canvas_context.measureText(description_text); if (metrics.width > context.img_width) { context.canvas_context.fillText(description_text.slice(0,Math.floor((description_text.length)/2)), context.pos_x+Math.floor(context.img_width/2), (context.pos_y+context.img_height+10), context.img_width); context.canvas_context.fillText(description_text.slice(Math.floor((description_text.length)/2)), context.pos_x+Math.floor(context.img_width/2), (context.pos_y+context.img_height+30), context.img_width); } else context.canvas_context.fillText(description_text, context.pos_x+Math.floor(context.img_width/2), (context.pos_y+context.img_height+10), context.img_width); context.canvas_context.shadowBlur = 0; context.canvas_context.shadowOffsetX = 0; context.canvas_context.shadowOffsetY = 0; }; $.fn.draw_control = function() { }; $.fn.canvasSlideshow.defaults = { img_width: 200, //image width img_height: 150, //image height border_size: 5, //border size shadow_size: 5, //shadow size num_elem: 3, //number of element to show in 1 screen show_description: true, // show the alt tag as description? show_box: true, // show the outer box? speed: 2000, //time distance between each step step: 20 //step time }; })(jQuery);
We will see how to add the draw_control, the waiting while loading and the onclick events next week!
You can donwload the plugin at Download Canvas Slideshow. And see a demo just under this!