Wednesday, May 21, 2014

Not-So-Simple Mouse Animations in SVG


I am curious about SVG animation in Polymer. I already have a little animation working in my <x-pizza> custom pizza builder element:



When new toppings are added, they glide in from the left and float down onto the pizza. I do not want to categorize the implementation of this animation as ugly, so let's just call it… involved. I have two separate window.requestAnimationFrame() timers running to translate the X and Y coordinates of the group containing the toppings. The solution may be involved, but it works brilliantly—low overhead and quite smooth.

Still, I would like to improve my SVG animation skills so tonight I try something a little simpler: animating toppings on mouse-over. In particular, I would like the topping to pop-up with a drop shadow when the discerning pizza buyer hovers over the individual topping.

I have come across JavaScript callbacks inside Inkscape:



But I'll be darned if I can figure out what to put in those fields. Besides, I am currently coding in Dart, so that seems less than ideal.

Instead, I define two different layers for this particular SVG. The “rest” state layer, which I name “down”:



And the on-hover layer, which I name “up”:



When I save this as plain (not Inkscape formatted) SVG, I leave the “eye” icon on the Up layer closed so that the image will default to the down/rest state. Checking the saved SVG, this is indeed styled with style="display:none" on the Up layer:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
   xmlns:svg="http://www.w3.org/2000/svg"
   xmlns="http://www.w3.org/2000/svg"
   version="1.1"
   width="32"
   height="32">
  <defs>
    <filter
       color-interpolation-filters="sRGB"
       id="filter3790">
      <!-- drop shadow filter definition here... -->
    </filter>
  </defs>
  <g
     id="down"
     style="display:inline">
    <path
       d="M 22,12 A 10,10 0 1 1 2,12 10,10 0 1 1 22,12 z"
       transform="translate(2.8577037,1.6074583)"
       style="fill:#d40000;fill-opacity:1;stroke:#000000;stroke-width:1.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
  </g>
  <g
     id="up"
     style="display:none">
    <path
       d="M 22,12 A 10,10 0 1 1 2,12 10,10 0 1 1 22,12 z"
       style="fill:#d40000;fill-opacity:1;stroke:#000000;stroke-width:1.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;filter:url(#filter3790)" />
  </g>
</svg>
I had to manually edit those id attributes.

To animate, I then add some mouse stream event listeners:
  _svgImage(basename) => new GElement()..append(
    new ImageElement()
      ..setAttributeNS(
          'http://www.w3.org/1999/xlink',
          'href',
          '/packages/svg_example/images/$basename.svg'
        )
      ..setAttribute('width', '32')
      ..setAttribute('height', '32')
      ..onMouseOver.listen((e){
          this.query('#up').style.display = '';
          this.query('#down').style.display = 'none';
        })
      ..onMouseLeave.listen((e){
          this.query('#up').style.display = 'none';
          this.query('#down').style.display = '';
        })
   );
The problem with that approach is that the up and down layers are in the image that is loaded into the ImageElement object:
    new ImageElement()
      ..setAttributeNS(
          'http://www.w3.org/1999/xlink',
          'href',
          '/packages/svg_example/images/$basename.svg'
        )
        // ...
As such, when I query for the up and down layers, they are not in the ImageElement that I am dynamically creating. They are in the SVG image file that is loaded by virtue of that href attribute. And I cannot figure out how to dynamically gain access to it.

On a lark, I try adding onmouseover attributes to the static SVG file. I also try defining <style> tags within the static SVG. Neither seems to have an effect when loaded from an ImageElement—both work when the static SVG is loaded directly, but not in my application code. So I would seem to be at an impasse.

There must be some way to get at the underlying SVG when loaded as an <image>. I will continue digging for it tomorrow.



Day #70

1 comment:

  1. Why are you using the ImageElement for SVG? The correct Dart class is SvgSvgElement.
    Try this code (it works for me):

    void toolbutton_OnClick(MouseEvent event) {

    SvgSvgElement svg_target = event.currentTarget;

    //SWITCH BETWEEN THE DOWN AND THE UP STATE OF THE SVG BUTTON
    if(svg_target.getElementById('up').attributes['style'] == 'display:none')
    {
    svg_target.getElementById('down').attributes['style'] = 'display:none';
    svg_target.getElementById('up').attributes['style'] = 'display:inline';
    }
    else
    {
    svg_target.getElementById('down').attributes['style'] = 'display:inline';
    svg_target.getElementById('up').attributes['style'] = 'display:none';
    }


    }

    ReplyDelete