Sunday, April 21, 2013

Animating SVG with JavaScript

‹prev | My Chain | next›

I donated to the Batch Icon Set used in this post. You should too!

I was able to add and SVG icon to the ICE Code Editor yesterday:



Today I would to add simple animation to it. Rather than start with the icon being large and dark, I would prefer that it be a lighter grey and a bit smaller. When the reader moves the mouse pointer over it, then it should grow to a larger size with a darker coloring.

My initial thought is to use some CSS3 to animate the icon on :hover (and when the mouse is no longer hovering over the icon). I cannot seem to get that working when I try applying the styles directly to the SVG. So instead, I insert a containing <div> tag to which I can apply CSS3 styles and which can hold the SVG element:
Embedded.prototype.addControls = function() {
  var el = document.createElement('div');
  document.body.appendChild(el);

  el.style.position = 'absolute';
  el.style.bottom = '20px';
  el.style.right = '40px';
  el.style.cursor = 'pointer';
  el.style.width = '25px';
  el.style.height = '25px';
  this.editor.editor_el.appendChild(el);
  // ...
};
This seems to work, except that I am not going to get my nice CSS3 animations on-hover by placing styles directly on that element. So I remove the explicit style property settings and instead add manual CSS to the bottom of the method (borrow from a StackOverflow solution):
Embedded.prototype.addControls = function() {
  var el = document.createElement('div');
  el.className = 'icon';
  document.body.appendChild(el);
  this.editor.editor_el.appendChild(el);
  // ...
  var head = document.getElementsByTagName('head')[0];
  var style = document.createElement('style');
  style.type = 'text/css';

  var declarations = document.createTextNode(
    '.icon {' +
      'position: absolute; ' +
      'cursor: pointer; ' +
      'fill: rgba(204, 204, 204, 0.8); ' +
      'width: 25px; ' +
      'height: 25px; ' +
      'bottom: 10px; ' +
      'right: 30px; ' +
      'transition: all 0.2s ease-in-out; ' +
    '} ' +
    '.icon:hover {' +
      'fill: rgba(0, 0, 0, 0.8); ' +
      'width: 32px; ' +
      'height: 32px; ' +
      'bottom: 6px; ' +
      'right: 26px; ' +
    '}'
  );

  style.appendChild(declarations);
  head.appendChild(style);
};
In ordinary web pages, I might add this as a separate CSS stylesheet. Since I am building an embeddable widget, I opt to build the CSS in JavaScript—position, color, size and all.

The most important aspect of this is the transition property. I am using all so that all CSS properties will transition at the same speed when switching from .icon to .icon:hover. Also of note is the use of the fill property, which tells the SVG element what color to use when drawing itself. I change the bottom and right absolute positioning of the containing <div> on hover to give the appearance that the animation originates from the center of the icon.

The entire method, container element, SVG element, and CSS is then:
Embedded.prototype.addControls = function() {
  var el = document.createElement('div');
  el.className = 'icon';
  document.body.appendChild(el);

  this.editor.editor_el.appendChild(el);

  var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  svg.setAttribute('viewBox', '0 0 48 48');

  var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  path.setAttribute('d', 'M 11.742,7.50 L 20.121,15.879 C 21.294,17.052 21.294,18.948 20.121,20.121 C 18.948,21.294 17.052,21.294 15.879,20.121 L 7.50,11.742 L 5.121,14.121 C 4.548,14.694 3.78,15.00 3.00,15.00 C 2.613,15.00 2.223,14.925 1.851,14.772 C 0.732,14.307 0.00,13.212 0.00,12.00 L 0.00,3.00 C 0.00,1.344 1.344,0.00 3.00,0.00 L 12.00,0.00 C 13.212,0.00 14.307,0.732 14.772,1.851 C 15.237,2.973 14.979,4.263 14.121,5.121 L 11.742,7.50 ZM 45.00,0.00 C 46.659,0.00 48.00,1.344 48.00,3.00 L 48.00,12.00 C 48.00,13.212 47.268,14.307 46.149,14.772 C 45.777,14.925 45.387,15.00 45.00,15.00 C 44.22,15.00 43.452,14.694 42.879,14.121 L 40.50,11.742 L 32.121,20.121 C 30.948,21.294 29.052,21.294 27.879,20.121 C 26.706,18.948 26.706,17.052 27.879,15.879 L 36.258,7.50 L 33.879,5.121 C 33.021,4.263 32.766,2.973 33.228,1.851 C 33.69,0.732 34.788,0.00 36.00,0.00 L 45.00,0.00 ZM 15.879,27.879 C 17.052,26.706 18.948,26.706 20.121,27.879 C 21.294,29.052 21.294,30.948 20.121,32.121 L 11.742,40.50 L 14.121,42.879 C 14.979,43.737 15.237,45.027 14.772,46.149 C 14.307,47.268 13.212,48.00 12.00,48.00 L 3.00,48.00 C 1.344,48.00 0.00,46.659 0.00,45.00 L 0.00,36.00 C 0.00,34.788 0.732,33.69 1.851,33.228 C 2.223,33.075 2.613,33.00 3.00,33.00 C 3.78,33.00 4.548,33.306 5.121,33.879 L 7.50,36.258 L 15.879,27.879 ZM 46.149,33.228 C 47.268,33.69 48.00,34.788 48.00,36.00 L 48.00,45.00 C 48.00,46.659 46.659,48.00 45.00,48.00 L 36.00,48.00 C 34.788,48.00 33.69,47.268 33.228,46.149 C 32.766,45.027 33.021,43.737 33.879,42.879 L 36.258,40.50 L 27.879,32.121 C 26.706,30.948 26.706,29.052 27.879,27.879 C 29.052,26.706 30.948,26.706 32.121,27.879 L 40.50,36.258 L 42.879,33.879 C 43.452,33.306 44.22,33.00 45.00,33.00 C 45.387,33.00 45.777,33.075 46.149,33.228 Z');

  svg.appendChild(path);

  el.appendChild(svg);

  var head = document.getElementsByTagName('head')[0];
  var style = document.createElement('style');
  style.type = 'text/css';

  var declarations = document.createTextNode(
    '.icon {' +
      'position: absolute; ' +
      'cursor: pointer; ' +
      'fill: rgba(204, 204, 204, 0.8); ' +
      'width: 25px; ' +
      'height: 25px; ' +
      'bottom: 10px; ' +
      'right: 30px; ' +
      'transition: all 0.2s ease-in-out; ' +
    '} ' +
    '.icon:hover {' +
      'fill: rgba(0, 0, 0, 0.8); ' +
      'width: 32px; ' +
      'height: 32px; ' +
      'bottom: 6px; ' +
      'right: 26px; ' +
    '}'
  );

  style.appendChild(declarations);
  head.appendChild(style);
};
And the effect is quite pleasant. It looks nice when small:



And it looks nice when the pointer is hovering over the element:



Best of all, it works in both Firefox and Chrome (IE's inability to perform WebGL makes it a non-starter for this).

I had thought to explore SVG's built-in animation as well, but this approach seems quite solid.

Day #728

No comments:

Post a Comment