Styling Canvas Drawings With CSS

Today I implemented an idea to style canvas elements with CSS, I’ve never personally seen this done, and it’s not always practical, but in some cases it would be very handy. For example a text selection api may be comprised of several “elements”, the text, the caret, the selection range rect etc.

Let’s say by default, our drawing will look the image below:

default text selection

great, looks fine, but what if designers, or even developers for organizational purposes, could tweak this with CSS. Well you can! Suppose we want to support the following css:

  #editor .text {
    font: 20px helvetica, arial, sans-serif;
  }

  #editor .text .selection {
    background: #DFF3FC;
  }

First, the API to access such data will look very similar to how we wrote our css, however we provide the css property as well:

style('#editor .text .selection', 'font-family')

Now for the implementation details, keeping in mind this is not targeting cross-browser support, it’s simply a quick naive implementation, and I should note this does not cover the case of style changes, only initial styles.

So, firstly we check our cache, to prevent several expensive lookups, here style.cache is a property of our style function, if it’s available, we simply return the result.

  function style(selector, prop) {
    var cache = style.cache = style.cache || {}
      , cid = selector + ':' + prop;

    if (cache[cid]) return cache[cid];

    ...

Next up we will naively split the selector into an array so that we can work with it.

    ...
    var parts = selector.split(/ +/)
      , len = parts.length
      , parent = root = document.createElement('div')
      , child
      , part;

   ...

We loop through the “parts”, creating one div per “part”. This will allow us to see what the computed style is for an element, that otherwise, does not exist! because it’s a canvas. The naive implementation I have here, supports ids and classes only, so we check the first character, and assign the id and class respectively.

    ...
    for (var i = 0; i < len; ++i) {
      part = parts[i];
      child = document.createElement('div');
      parent.appendChild(child);
      parent = child;
      if ('#' == part[0]) {
        child.setAttribute('id', part.substr(1));
      } else if ('.' == part[0]) {
        child.setAttribute('class', part.substr(1));
      }
    }
    ...

Finally we append the root element to the document.body so that the styles are applied, get the computed style for the given prop, remove the element, and cache/return our value.

  ...
    document.body.appendChild(root);
    var ret = getComputedStyle(child)[prop];
    document.body.removeChild(root);
    return cache[cid] = ret;
  }

Example Usage

Now let’s try it out, first, the css, providing a light yellow background:

  #chalk .text .selection {
    background: #FAFFDF;
  }

In our JavaScript text selection logic, we should provide a JavaScript API for altering styling as well, however for this example, let’s add it directly:

 ctx.fillStyle = style('#editor .text .selection', 'background-color');
 ctx.fill(...)

Once applied, we get the following result:

resulting canvas style

That’s it! you can apply this trick to assign font sizes and families, foreground and background coloring, border colors, anything you can come up with! if anyone comes up with a robust implementation I would love to see it!

Full Source

Here’s the full implementation:

  function style(selector, prop) {
    var cache = style.cache = style.cache || {}
      , cid = selector + ':' + prop;

    if (cache[cid]) return cache[cid];

    var parts = selector.split(/ +/)
      , len = parts.length
      , parent = root = document.createElement('div')
      , child
      , part;

    for (var i = 0; i < len; ++i) {
      part = parts[i];
      child = document.createElement('div');
      parent.appendChild(child);
      parent = child;
      if ('#' == part[0]) {
        child.setAttribute('id', part.substr(1));
      } else if ('.' == part[0]) {
        child.setAttribute('class', part.substr(1));
      }
    }

    document.body.appendChild(root);
    var ret = getComputedStyle(child)[prop];
    document.body.removeChild(root);
    return cache[cid] = ret;
  }

Notes

  1. vita-cheats reblogged this from tjholowaychuk
  2. softpress reblogged this from tjholowaychuk
  3. jalbertbowdenii reblogged this from tjholowaychuk
  4. matomesc reblogged this from tjholowaychuk
  5. mobiledude reblogged this from tjholowaychuk
  6. tjholowaychuk posted this