Tales of a Developer Advocate

Developer Relations @ Google

  • Using Canvas to create beautiful custom markers in Google Maps

    • 14 Oct 2010
    • 4 Responses
    •  views
    • canvas css3 google maps html5 javascript
    • Edit
    • Delete
    • Tags
    • Autopost

    I have been playing with Google Maps a little recently and I want to dynamically create a set of Markers without resorting to a server.  I noticed that the Marker class can take a string URL as a parameter, so it seemed to make sense that if it was a url to an icon, it could also be a dataUri to an image that I can control via a canvas.

    To cut a long prologue short, it works.  More importantly I was quite pleased with the effect I generated and this is what this post is all about.

    The markers that I wanted to create had to be rectangular, with rounded corners, distinct colours and a number right in the centre.

    The first problem was how to create rounded rounded.  I didn’t have a lot of time for this but I used a lot of the code from http://js-bits.blogspot.com/2010/07/canvas-rounded-corner-rectangles.html as inspiration.  This simply draws a nice rounded rectangle.

    The next problem to solve is how to create a nice range of colours that aren’t the same but are consistent.  This is where the new support for hsla (hue, saturation and luminance) support via CSS3 really pays off in a big way.  Using RGB there is no simple way to find a suite of colours that have the same tone but are uniquely distinct; with the HSL colour model if you keep the S and L values the same but modify the hue (H) you can produce a wide spectrum of colours that are of the same tone. See the Example image.

    This creates an image that is a single colour, but it doesn’t look amazingly nice.  I decided to add a gradient to give it one of those effects you see all over the web at the moment (I am not sure of the name, its not a Gel Button, but it is close….ish).  The question is, how do you choose a colour for the gradient?  The answer was pretty simple in the end: use the same Hue and Saturation values as the first colour but reduce the Luminance by a couple of percentage.  The overall effect is pretty nice I think.

    Centring the text was pretty simple too. The canvas API provides a nice little method called MeasureText, this allows you to get the pixel width of any arbitrary string (using the font-size and family you specify). From this it is a simple case of finding the centre of the text and the centre of the canvas and subtracting one from the other.

    The final problem is pretty simple to solve.  How do you get the canvas image as a url?  canvas.toDataURL() will return an image url that can be attached to any “src” or css “url()” property.

    It must be noted that this will only work with browsers that can render canvas elements.

    The final code is follows for posterity:

    var ButtonFactory = (function() {
      var width = 25;
      var height = 25;
      return new function() {
    
        var h = 1;
        var s = 78; // constant saturation
        var l = 63; // constant luminance
        var a = 1;
    
        var getColor = function(val, range) {
          h = Math.floor((360 / range) * val);
    
          return "hsla(" + h +"," + s + "%," + l +"%," + a +")";
        };
    
        var getColor1 = function() {
          return "hsla(" + h +"," + s + "%," + (l - 30) +"%," + a +")";
        };
    
        // draws a rounded rectangle
        var drawRect = function(context, x, y, width, height) {
          var radius = 10
          context.beginPath();
          context.moveTo(x + radius, y);
          context.lineTo(x + width - radius, y);
          context.quadraticCurveTo(x + width, y, x + width, y + radius);
          context.lineTo(x + width, y + height - radius);
          context.quadraticCurveTo(x + width, y + height, x + width -
          radius, y + height);
          context.lineTo(x + radius, y + height);
          context.quadraticCurveTo(x, y + height, x, y + height - radius);
          context.lineTo(x, y + radius);
          context.quadraticCurveTo(x, y, x + radius, y);
          context.closePath();
        }
    
        this.createCanvas = function(label, range) {
          var canvas = document.createElement("canvas");
          canvas.width = width;
          canvas.height = height;
    
          var context = canvas.getContext("2d");
    
          var val = parseInt(label);
    
          context.clearRect(0,0,width,height);
    
          var grad = context.createLinearGradient(0, 0, 0, height);
    
          var color0 = getColor(val, range);
    
          grad.addColorStop(0, color0);
          grad.addColorStop(1, getColor1());
    
          context.fillStyle = grad;
          context.strokeStyle = color0;
    
          drawRect(context, 0, 0, width, height);
          context.fill();
          context.stroke();
    
          context.fillStyle = "white";
          context.strokeStyle = "black"
    
          // Render Label
          context.font = "bold 12pt Arial";
          context.textBaseline  = "top";
    
          var textWidth = context.measureText(label);
    
          // centre the text.
          context.fillText(label,
            Math.floor((width / 2) - (textWidth.width / 2)),
            4
          );
    
          return canvas;
    
        };
    
        this.create = function(label, range) {
          var canvas = this.createCanvas(label, range);
          return canvas.toDataURL();
        };
      }
    })();

    Example usage:

    for(var i = 1; i<100; i++) {
      document.body.appendChild(ButtonFactory.createCanvas(i,99));
    }
    • Tweet
  • Using HTML5 Canvas with Drag and Drop (setDragImage)

    • 11 Aug 2010
    • 0 Responses
    •  views
    • canvas dnd drag and drop html5 javascript
    • Edit
    • Delete
    • Tags
    • Autopost

    Using HTML5 Canvas with Drag and Drop (setDragImage)

    I know a lot of people complain about Drag and Drop (DnD), but it is not all that bad. At least we have a platform to work off for the future.

    We have recently seen a lot of improvements to Gmail which now includes additional support for Dragging in to Gmail to upload your docs and files, and also dragging attachments out of Gmail to the user file system. I like both of these pieces of functionality, they are subtle but introduce

    One thing that I see from the jQuery UI world is that it makes DnD very easy to implement but also very pretty. Whilst HTML5 DnD has support for attaching any arbitrary element to the drag operation, in practice I have never seen a browser implement it, so we are left to our own devices on what we want to show to the user during a drag operation.

    I have produced a very (rough) sample – http://html5samples.appspot.com/canvasToDrag.html. There is still a lot of work for me to do with my sample (it needs to be a lot prettier). It is very simple to use, select the negatives that you wish to “develop” and drag them into the development area. You will see that there is a “fan” effect on the drag icon. I must warn that this only works on Webkit browsers (so Chrome and Safari – actually, the drop doesn’t work in Safari yet)

    This is all enabled through DataURI’s. I take the selected images and add them to the canvas (adding a little rotation and scaling for effect). Once the canvas is completed I simply call .toDataURL() on the canvas element and add it to a temporary image element. This image element is then used as in the call to the setDragImage method

    // Code inside the dragStart event.
    
    var uris = []; // A list of img uris, so I know what to display after the drag
    
    var img = document.createElement("img");
    img.src = canvas.toDataURL();
    
    e.dataTransfer.setData("text/plain", "Text to drag");
    e.dataTransfer.setData("text/uri-list", uris.join("\n"));
    e.dataTransfer.setDragImage(img, 128,128);

    It is pretty simple, and can allow you to do some nice effects. Things that I can’t do in HTML5 Drag and Drop is animate the element being dragged. I would love to be able animate the canvas as the user drags the elements around the screen, maybe adding a bit of inertia to the photographs as they are dragged around.

    • Tweet
  • DOM TreeWalker

    • 8 Aug 2010
    • 1 Response
    •  views
    • html javascript
    • Edit
    • Delete
    • Tags
    • Autopost

    I really wanted to get a reference to Walker Texas Ranger in to the title, but I really couldn’t think of anything that cool. If you can think of a great Chuck Norris reference leave a comment, I am all chins!

    It always amazes me that there is so much to HTML that is still not being exploited by developers.

    One pattern I see regularly is recursive descent through the DOM to find particular TEXT nodes that contain a given string so that the container element can be manipulated.

    It is not that recursion is slow, if your DOM is complex enough you could hit stack overflow errors (although it is pretty unlikely), it is that there are a lot of edge cases when parsing the DOM that you need to code in.

    A little known DOM function is available that makes developing applications that need to scan the DOM easy. It is called Tree Walker, created through the createTreeWalker function on the document.

    You can create a tree walker very quickly using the following Javascript:

    document.createTreeWalker(document.body, NODE_FILTER.SHOW_TEXT, function(node) { return NodeFilter.FILTER_ACCEPT; }, false);
    
    while(treeWalker.nextNode()) console.log(treeWalker.currentNode);

    The above code is given a root node of document.body, a filter of what to show (only Text Nodes in our case), and a function that returns if the node should be returned (essentially a filter).

    An interesting point to note is that the Filter function is only called when iterating over the treeWalker.

    This is actually a really cool feature, the currentNode property of the Tree Walker contains DOM objects, so you can start to do some really advanced processing, you could highlight the current node, replace its text or remove it – really anything you want. This is significantly simpler than managing the recursion yourself.

    As a more concrete example, lets use this to find all twitter user names on a page and then automatically make these a twitter link. It could be done using recursion pretty simply, but I need something fun to show you.

    var re = new RegExp(); // This isn't accurate RE
    re.compile("@([A-Za-z0-9_]*)");
    var walker = document.createTreeWalker(
      document.body,
      NodeFilter.SHOW_TEXT,
      function(node) {
        var matches = node.textContent.match(re);
    
        if(matches) { 
          return NodeFilter.FILTER_ACCEPT;
        } else {
          return NodeFilter.FILTER_SKIP;
        }
      },
      false);
    
    var nodes = [];
    
    while(walker.nextNode()) {
      nodes.push(walker.currentNode);
    }
    
    for(var i = 0; node=nodes[i] ; i++) {
      node.parentNode.innerHTML = node.parentNode.innerHTML.replace(re, "@$1") }

    A live example is on my sample site

    The theory is, that User-Agents can optimize the access to the DOM better than you can recursively descend through the DOM. So, where would I use this? The first thing that springs to mind is that it is ideal for Chrome extensions. Many Chrome extensions traverse the DOM looking for pieces of text, or particular patterns inside nodes that aren’t available via CSS Selectors.

    More information can be found on Mozilla’s Developer site

    • Tweet
  • About

    I help developers build really cool products on the Web.

    I work for Google as a Developer Advocate in London, specializing in Chrome, HTML5 and the Chrome Web Store.

    231036 Views
  • Archive

    • 2012 (5)
      • February (5)
    • 2011 (30)
      • July (1)
      • June (4)
      • May (10)
      • April (4)
      • March (1)
      • February (5)
      • January (5)
    • 2010 (35)
      • December (15)
      • November (5)
      • October (2)
      • August (5)
      • July (8)
    • 2009 (1)
      • January (1)
    • 2008 (8)
      • December (1)
      • April (2)
      • March (3)
      • February (2)
    • 2007 (17)
      • December (1)
      • September (1)
      • August (5)
      • May (1)
      • March (2)
      • February (4)
      • January (3)
    • 2006 (153)
      • November (3)
      • October (7)
      • September (11)
      • August (9)
      • July (9)
      • June (14)
      • May (11)
      • April (32)
      • March (23)
      • February (26)
      • January (8)
    • 2005 (274)
      • December (7)
      • November (43)
      • October (63)
      • September (54)
      • August (66)
      • July (13)
      • June (10)
      • May (10)
      • April (6)
      • January (2)
    • 2004 (1)
      • August (1)

    Get Updates

    Subscribe via RSS
    TwitterFriendfeedLinkedIn