Overlaying Lightbox with the HTML5 Canvas Element

Here’s how to use the HTML5 <canvas> element in conjunction with Lokesh Dhakar’s excellent Lightbox2 script, to dynamically highlight a part of a static image, in this case individual offices on a company’s floor plan.

I was making a “Team” page for a client recently. The purpose of this page was to list the contact details for each member of the client’s (not-so-small) team. The client also wanted each team member’s contact details to include their office number, which when clicked, would display an image showing a floor plan of their department, with the exact location of the member’s office highlighted in red.

The client’s idea was to use a different image for each member. Although this would essentially work, it would mean a whole bunch of almost identical images and it didn’t really seem like the best solution to me. I therefore decided to take just the one image of the floor plan, display it in a Lightbox and overlay it with a dynamically generated HTML5 canvas element, which would display the correct office location for each member.

Adding a Lightbox

I started off by downloading Lokesh Dhakar’s excellent Lightbox2. I then made a simple script that displayed a member’s contact details. I made the member’s office number a link, that when clicked, opened the client’s floor plan in a Lightbox.

You can see this in action here.

After that I needed to write some JavaScript to the following:

  • When the page has loaded, it should check if the browser supports the canvas element
  • If it does, it should find all of the links to the floor plan and attach an event handler to them, so that when they are clicked a specific function fires
  • This function should receive the coordinates of a specific office as parameters
  • It should then create a canvas element
  • Draw a rectangle around the appropriate office
  • Super impose the canvas element on the floor plan, which by this time is being displayed in a Lightbox

The first step is fairly easy. I used jQuery’s $(document).ready function, as I am including jQuery anyway (Lightbox2 requires it).

function isCanvasSupported(){
  var elem = document.createElement('canvas');
  return !!(elem.getContext && elem.getContext('2d'));
}

$(document).ready(function() {
  if(isCanvasSupported()){
    alert("Yay!");
  }
});

I found the function to check for the canvas element here: http://stackoverflow.com/questions/2745432/best-way-to-detect-that-html5-canvas-is-not-supported

To attach an event listener to all of the links pointing to the floor plan, I decided to wrap them in a div tag, which I gave the class of highlight_room, and then use jQuery’s click method

$(".highlight_room a").on("click", function() {
  // do stuff
});

So, now it started getting tricky. I had a few problems generating a canvas element on the fly and getting it to behave as I thought it should, so I cheated and hard-coded it into the page. I set its display property to none and its background image to that of the floor plan.

<canvas width="750"
        height="548"
        class="plan"
        style="display:none; background-image: url(plan.jpg);">
</canvas>

I then fetch the canvas element using jQuery.

var canvas = $(".plan");

To get the coordinates of the rectangle to draw on the canvas, I used an image map in DreamWeaver. I highlighted each office individually and took the coordinates from DreamWeaver’s output. For example, for an image map covering room 395, DW outputs:

<map name="Map">
  <area shape="rect" coords="158,82,35,65" href="#">
</map>

For more about creating image maps in DW, see here: http://visibleranking.com/create-image-map-in-dreamweaver.php

Once I had all of the coordinates I needed, I included them as global variables at the top of my JavaScript:

var room_336 = [409,397,68,36];
var room_339 = [409,302,68,36];
...
var room_595 = [220,82,35,65];
var room_598 = [252,82,36,65];

Then, for every link pointing to the floor plan, I added a class to it, making the name of the class dependent on the office to be highlighted.

<a href="images/plan.jpg" rel="lightbox" class="room_593">01/593</a>

Finally, I parse this class reference in my JavaScript to point to one of the previously-declared global variables.

var coord = window[$(this).attr("class")];

Drawing on the Canvas

Now I was in a position to draw a rectangle around the appropriate office on the canvas element. I did this with a function called highlightRoom to which I passed the four coordinates as parameters. This function also includes a call to clearCanvas which pretty much does what you’d expect. I found it here: http://stackoverflow.com/questions/2142535/how-to-clear-the-canvas-for-redrawing

function clearCanvas(context, canvas) {
  context.clearRect(0, 0, canvas.width, canvas.height);
  var w = canvas.width;
  canvas.width = 1;
  canvas.width = w;
}

function highlightRoom(x, y, h, w){
  var can = $('canvas')[0];
  var ctx = can.getContext('2d');
  clearCanvas(ctx, can);
  ctx.strokeStyle = '#f00';
  ctx.lineWidth = 5;
  ctx.lineJoin = 'round';
  ctx.strokeRect(x,y,h,w);
}

After this, all that remained to be done now, was to swap out the image of the floor plan with the modified canvas element, whenever the user clicks on a link to a member’s office.

To do this we can target the Lightbox2 div with the class of lb-container and use jQuery’s html method to swap out its contents. It is prudent to wait a short while before doing this, so that Lightbox2 has the chance to create these elements, before we try and access their content.

setTimeout(function(){
  clearInterval(imageLoaded);
}, 5000);

imageLoaded = setInterval(function(){
  if(imageReady()){
    $(".lb-image").hide();
    $(".lb-container").prepend(canvas);
    canvas.css("display", "block");
  }
}, 100);

Clearing the timer after 5 seconds, guarantees that it won’t keep running if the image fails to load.

The finished script looks like this:

<!doctype html>
  <html lang="en-us">
  <head>
  <meta charset="utf-8">
    <title>Lightbox 2 with <canvas> overlay</title>
    <link rel="stylesheet" href="css/lightbox.css" />
    <style>
      .plan{
        display:none;
        background-image: url(images/plan.jpg);
      }
    </style>
  </head>

  <body>
    <div class="highlight_room">
      <p>
        Prof. Dr. E. Wriston<br>
        Head of Department<br>
        Tel.: +44 (0)123 / 45-6789<br>
        Fax: +44 (0)123 / 45-6780 <br>
        Office:
          <a href="images/plan.jpg" rel="lightbox" class="room_593">
            01/593
          </a><br>
      </p>

      <p>
        Prof. Dr. B. Longboard<br>
        Second in command<br>
        Tel.: +44 (0)123 / 45-6789<br>
        Fax: +44 (0)123 / 45-6780 <br>
        Office:
          <a href="images/plan.jpg" rel="lightbox" class="room_336">
            01/336
          </a><br>
      </p>
    </div>

    <canvas width="750" height="548" class="plan"></canvas>

    <script src="js/jquery-3.3.1.min.js"></script>
    <script src="js/lightbox.js"></script>
    <script>
      var room_336 = [409,397,68,36];
      var room_339 = [409,302,68,36];
      var room_340 = [409,269,68,36];
      var room_342 = [409,207,68,36];
      var room_343 = [409,176,68,36];
      var room_348 = [379,82,35,65];
      var room_394 = [585,461,35,65];
      var room_588 = [92,82,35,65];
      var room_591 = [126,82,35,65];
      var room_593 = [158,82,35,65];
      var room_595 = [220,82,35,65];
      var room_598 = [252,82,36,65];
      var imageLoaded;

      function isCanvasSupported(){
        var elem = document.createElement('canvas');
        return !!(elem.getContext && elem.getContext('2d'));
      }

      function clearCanvas(context, canvas) {
        context.clearRect(0, 0, canvas.width, canvas.height);
        var w = canvas.width;
        canvas.width = 1;
        canvas.width = w;
      }

      function highlightRoom(x, y, h, w){
        var can = $('canvas')[0];
        var ctx = can.getContext('2d');
        clearCanvas(ctx, can);
        ctx.strokeStyle = '#f00';
        ctx.lineWidth = 5;
        ctx.lineJoin = 'round';
        ctx.strokeRect(x,y,h,w);
      }

      function imageReady(){
        if ($("img.lb-image").is(":visible")){
          clearInterval(imageLoaded);
        }

        return $("img.lb-image").is(":visible");
      }

      $(document).ready(function() {
        if(isCanvasSupported()){
          $(".highlight_room a").on("click", function() {
            var canvas = $(".plan");
            var coord = window[$(this).attr("class")];
            highlightRoom(coord[0], coord[1], coord[2], coord[3]);

            setTimeout(function(){
              clearInterval(imageLoaded);
            }, 5000);

            imageLoaded = setInterval(function(){
              if(imageReady()){
                $(".lb-image").hide();
                $(".lb-container").prepend(canvas);
                canvas.css("display", "block");
              }
            }, 100);
          });
        }
      });
    </script>
  </body>
</html>

You can see it in action here: example 2


This post currently has 2 responses

  1. indiana says:

    Great solution! But unfortuntely, if I update either jquery to v1.9.x or lightbox to v2.7.1, the result will get failed or weird…

Comments are closed!