Símbolos animados (1)

facebook-svg gplus-svg twitter-svg

Queremos dibujar en SVG un triangulo ( <polygon> ) que apunta como una flecha hacia la derecha y después, al hacer clic, que gire unos 90º en una animación que dura medio segundo ( 500ms ). Ya que se trata de una transformación ( rotate ) utilizamos <animateTransform>.
La animación empieza al hacer clic en el #triangulo ( begin="triangulo.click" ).

  • <svg width= "250" height= "250" viewBox= "0 0 250 250">
  • <polygon id= "triangulo" points= "200,125 87.5,190 87.5,60.0 200,125 " transform= "rotate(0 125 125)" style= "fill :#f00;">
  • <animateTransform id= "transformacion"
  • attributeName= "transform"
  • attributeType= "XML"
  • type= "rotate"
  • from= "0 125 125" to= "90 125 125"
  • begin= "triangulo.click"
  • dur= "500ms"
  • fill= "freeze"/>
  • </polygon>
  • </svg>

Lea más sobre símbolos en SVG

Lo podemos hacer mejor

Al hacer clic en el #triangulo por la segunda vez, queremos que este regrese a la posición inicial, y para esto tenemos que utilizar JavaScript. Ya que estamos modificando el código, no estaría nada mal poner orden en el SVG.

De entrada transformamos el #triangulo en un <symbol> ya que es posible que queramos utilizarlo otra vez, y lo guardamos con las definiciones ( <defs> ). El elemento <symbol> soporta el atributo viewBox y por tanto nos permite generar replicas de dimensiones variables. Queremos que el triangulo sea más pequeño y para esto le damos viewBox="0 0 250 250". Ahora solo falta establecer la altura y la anchura del elemento <use>

<defs>
<symbol id="triangulo" viewBox="0 0 250 250" style="fill:#abcdef;" >
  <polygon  points="200,125 87.5,190 87.5,60.0 200,125" . . ./ >
</symbol>
</defs>

En este momento no podemos ver el triangulo ya que esta con las definiciones. Para verlo tenemos que utilizarlo allá donde lo necesitamos. Para esto echamos mano del elemento <use>. Es a este elemento que animamos con <animateTransform>.

 <use id="use" xlink:href="#triangulo1" x="50" y="50" width="50" height="50" transform="" > 
        <animateTransform id="transformacion" 
            attributeName="transform"
            attributeType="XML"
            type="rotate"
            from="" to=""
            begin="use.click"
            dur="500ms"
            fill="freeze"/>
 </use>

Lea más sobre símbolos en SVG

El JavaScript a estas alturas es muy básico. Utilizamos el método setAtribute() para establecer el valor de los atributos transform de <use> y from y to de <animateTransform>. Por lo tanto podemos dejar en blanco el valor de estos atributos.

  • var triangulo1 = document.getElementById("triangulo1");
  • triangulo1.setAttribute("transform", "rotate(0 75 75");
  • triangulo1.addEventListener("click", function(){
  •    
  • var anim = document.getElementById("transformacion1");   
  • var from = anim.getAttribute("from");
  •    if(from == "0 75 75"){
  •    anim.setAttribute("from", "90 75 75");
  •    anim.setAttribute("to", "0 75 75");
  •    }else{
  •    anim.setAttribute("from", "0 75 75");
  •    anim.setAttribute("to", "90 75 75");
  •    }
  • });

Si miramos con atención el código, podemos ver que hemos cambiado también el valor de las coordenadas del centro de rotación de rotate(0 125 125) a rotate(0 75 75).
Como lo hemos calculado:
La sintaxis es: rotate(a cx cy) donde a es el ángulo de rotación, cx es la coordenada en x del centro de rotación y cy es la coordenada en y.
cx
El elemento <use> se encuentra a x="50" del origen del llienzo SVG.
El elemento <use> tiene una anchura width="50"
Por lo cual la coordenada en x del centro de rotación cx = x +  width/2, que en este caso es 75.
cy
De la misma manera deducimos que cy = y + height/2 o sea también 75.

Recordamos que tanto los atributos x e y como los atributos width y height de <use> son opcionales. Si estos atributos faltan SVG considera que su valor es 0, pero JavaScript no lo entiende. Si busca uno de estos atributos y no lo encuentra, da error. Por lo cual antes de calcular el valor de cx y cy tenemos que asegurarnos que el elemento <use> tiene estos atributos ( use.hasAttribute("x") ).

Todo esto traducido a JavaScript es:

    if(use.hasAttribute("x")){
       var use_x = Number(use.getAttribute("x"));
       }else{
       var use_x = 0;}
    if(use.hasAttribute("y")){
       var use_y = Number(use.getAttribute("y"));
       }else{
       var use_y = 0;}
    if(use.hasAttribute("width")){
       var use_w = Number(use.getAttribute("width"));
       }else{
       var use_w = 0;}
    if(use.hasAttribute("height")){
       var use_h = Number(use.getAttribute("height"));
       }else{
       var use_h = 0;}
    var cx = use_x + use_w/2;
    var cy = use_y + use_h/2;
  

Esto está bien, pero nos repetimos demasiado. Lo podemos hacer mejor:

// el array de los atributos que buscamos
var attrRy = new Array("x", "width", "y",  "height");
// el array de los valores que queremos calcular
var valAttr = new Array();
// un bucle for que recorre los elementos de attrRy
for( var j = 0;j < attrRy.length; j++){
	if(use.hasAttribute(attrRy[j])){
    	valAttr[j] = Number(use.getAttribute(attrRy[j]));
        }else{
        valAttr[j] = 0;}	
}
// calcula el valor de los atributos cx y cy
var cx = valAttr[0] + valAttr[1]/2;
var cy = valAttr[2] + valAttr[3]/2;

Y ya que estamos en ello podemos modificar de nuevo el JavaScript.

  • var use = document.getElementById("use");
  • var valAttr = new Array();
  • var attrRy = new Array("x", "width", "y", "height");
  • for( var j = 0;j<attrRy.length; j++){
  •    if(use.hasAttribute(attrRy[j])){
  •       valAttr[j] = Number(use.getAttribute(attrRy[j]));
  •       }else{
  •       valAttr[j] = 0;}   
  •    }
  • var cx = valAttr[0] + valAttr[1]/2;
  • var cy = valAttr[2] + valAttr[3]/2;
  • use.setAttribute("transform", "rotate(0 "+cx+" "+cy+")");
  • use.addEventListener("click", function(){
  •    
  • var anim = document.getElementById("transformacion1");   
  • var from = anim.getAttribute("from");
  •    if(from =="0 "+cx+" "+cy){
  •    anim.setAttribute("from", "90 "+cx+" "+cy);
  •    anim.setAttribute("to", "0 "+cx+" "+cy);
  •    }else{
  •    anim.setAttribute("from", "0 "+cx+" "+cy);
  •    anim.setAttribute("to", "90 "+cx+" "+cy);
  •    }
  • });

En el SVG ya podemos prescindir de los valores transform de <use> y from y to de <animateTransform>. JavaScript se encargará de establecer el valor de estos atributos.

  • <svg width= "150" height= "150" viewBox= "0 0 150 150" style= "border:1px solid #d9d9d9;">
  • <defs>
  • <symbol id= "triangulo1" viewBox= "0 0 250 250" style= "fill :#abcdef;" >
  • <polygon points= "200,125 87.5,190 87.5,60.0 200,125" style= "fill :#f00;"></polygon>
  • </symbol>
  • </defs>
  • <use id= "use" xlink:href= "#triangulo1" x= "50" y= "50" width= "50" height= "50" transform= "" >
  • <animateTransform id= "transformacion1"
  • attributeName= "transform"
  • attributeType= "XML"
  • type= "rotate"
  • from= "" to= ""
  • begin= "use.click"
  • dur= "500ms"
  • fill= "freeze"/>
  • </use>
  • </svg>

Por favor: haz clic en el triángulo rojo.

A continuación veremos cómo podemos animar múltiples imágenes SVG.