Submit your widget

a Canvas Slideshow with JQuery

Created 13 years ago   Views 10793   downloads 2406    Author webair
a Canvas Slideshow with JQuery
View DemoDownload
81
Share |

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:

  1. Clean the canvas
  2. Know where we will place first image
  3. Draw an image
  4. Move by a “white_space”
  5. 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);