Símbolos animados (1)
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.