Linuslabo
Linuslabo

Reputation: 1628

SVG start and edit animateTransform by Javascript

I would like to transform an element on a SVG canvas by a transformation matrix (that is decided on the fly). I can do it with JQuery-SVG animate() but it doesn't result smooth at all. So I thought to use the native SVG animateTransform, and the question is:

thanks in advance :D

Upvotes: 5

Views: 8774

Answers (2)

Linuslabo
Linuslabo

Reputation: 1628

Thanks for your answer! Since i wanted to use native SVG animations I found this solution (still not working perfectly). This is a sort of version of the unexisting animateTransform(attributeName="transform" type="matrix")

NOTE: i keep the svg transformation for each element in group.transform, and group.transform.matrix() returns just the transformation matrix of that element.

I first add these elements to the element i want to animate:

<animateTransform id="canvTranslate" begin="indefinite" attributeName="transform" type="translate" to="" dur="1s" additive="sum" fill="freeze"/>
<animateTransform id="canvRotate" begin="indefinite" attributeName="transform" type="rotate" to="" dur="1s" additive="sum" fill="freeze"/>
<animateTransform id="canvScale" begin="indefinite" attributeName="transform" type="scale" to="" dur="1s" additive="sum" fill="freeze"/>

Then i do:

 var tMatrix = transformation.matrix(); //this is the transformation i want to obtain
    var cMatrix = group.transform.matrix(); //this is the actual CTM of the element

    //getting the animations
    var animTrans = document.getElementById('canvTranslate');
    var animRotaz = document.getElementById('canvRotate');
    var animScale = document.getElementById('canvScale');

    //setting duration (it's got somewhere before)
    animTrans.setAttribute('dur', duration/1000+'s');
    animRotaz.setAttribute('dur', duration/1000+'s');
    animScale.setAttribute('dur', duration/1000+'s');

    //calculating the 'from' attribute
    var transX = cMatrix.e;
    var transY = cMatrix.f;
    var scaleX = Math.sqrt(Math.pow(cMatrix.a, 2)+Math.pow(cMatrix.b, 2));
        var rotate = Math.atan(cMatrix.c/cMatrix.d);

    animTrans.setAttribute('from', transX+','+transY);
    animRotaz.setAttribute('from', -rotate*180/Math.PI);
    animScale.setAttribute('from', scaleX);
    //end 'from'

    //calculating the 'to' attribute to set
    var transX = tMatrix.e;
    var transY = tMatrix.f;
    var scaleX = Math.sqrt(Math.pow(tMatrix.a, 2)+Math.pow(tMatrix.b, 2));
    var rotate = Math.atan(tMatrix.c/tMatrix.d);

    animTrans.setAttribute('to', transX+','+transY);
    animRotaz.setAttribute('to', -rotate*180/Math.PI);
    animScale.setAttribute('to', scaleX);
    //end 'to'

    animTrans.beginElement();
    animRotaz.beginElement();
    animScale.beginElement();

group.transform = transformation; and finally, to update the transform attribute of the element:

    setTimeout(function(){ //i will change this somehow better :)

        //this is a problematic step. with it animations work on Chrome, without it they work good on firefox and opera too
        $(group).attr('transform', 'matrix('+tMatrix.a+','+tMatrix.b+','+tMatrix.c+','+tMatrix.d+','+tMatrix.e+','+tMatrix.f+')');
    }, duration+100);

This final step is the problematic one. I can't understand why with it works fine in Chrome, while the animation ends up much more scaled in Firefox and Opera (where not calling setTimeout works just fine).

Upvotes: 4

bennedich
bennedich

Reputation: 12380

Animations can be done in many different ways.

Adding animate elements to graphic/shape elements is suitable for predefined animations. The "animate elements" presents really short sweet solutions, demo: http://jsfiddle.net/UjuR8

Interactive animations requires more manual solutions with quite some Javascript boilerplate code. You have to create a function render which will be invoked 60 times per second by requestAnimationFrame (see http://paulirish.com/2011/requestanimationframe-for-smart-animating/). In render you can get the "current transformation matrix" (CTM) and apply changes based on that. This is a really small proof of concept: http://jsfiddle.net/PaSD8/.

In a big project I'd recommend wrapping the SVG elements and perhaps do the animations without string concatenation and instead working directly with the matrices and transforms. This is an example class I have lying around:

var SVG, Object2D;

SVG = document.querySelector( 'svg' );

// ...

Object2D = ( function () {
  var proto;

  function Object2D ( domElement ) {
    this.domElement = domElement;
    this.transform  = SVG.createSVGTransform();
    this.matrix     = SVG.createSVGMatrix();
    this.position   = SVG.createSVGPoint();
    this.rotation   = 0;
    this.scale      = 1;
  }


  proto = Object2D.prototype;


  proto.draw = function ( timestamp ) {
    // update scale and position, apply rotation
    var transform = this.transform,
        matrix    = this.matrix,
        position  = this.position,
        rotation  = this.rotation,
        scale     = this.scale;

    matrix.a = scale;
    matrix.d = scale;
    matrix.e = position.x;
    matrix.f = position.y;

    transform.setMatrix( matrix.multiply( rotation ) );
    this.domElement.transform.baseVal.initialize( transform ); // clear then put
  };


  return Object2D;
} )();

Upvotes: 1

Related Questions