Conteo regresivo con estilo con un temporizador de terminal de aeropuerto
() translation by (you can also view the original English article)
En este tutorial haremos un temporizador de conteo regresivo reutilizable con una fecha objetivo dinámica que se puede establecer a través de XML. Animaremos los números que se voltean hacia abajo en el estilo de un viejo tablero de estado de aeropuerto o estación de tren. Cubriremos el código, la creación de gráficos y la animación.
Cada pocas semanas, revisamos algunas de las publicaciones favoritas de nuestros lectores a lo largo de la historia del sitio. Este tutorial se publicó por primera vez en mayo de 2010.
Paso 1: Configura tu archivo Flash
Crea un nuevo archivo Flash (Actionscript 3) con esta configuración: 500x300, fondo negro y 30 fps.



Paso 2: Crea el MovieClip digit_bottom
Crea un nuevo MovieClip llamado 'digit_bottom' y dibuja dentro de él un rectángulo redondeado de aproximadamente 36px de ancho por 50px de alto. (Una forma rápida de dibujar un rectángulo con dimensiones precisas es seleccionar la herramienta rectángulo y hacer Alt-clic en el escenario).
Dale al rectángulo un relleno de gradiente de #111111 (arriba) a #333333 (abajo) y un contorno de 2 px de color #333333.



Paso 3: Coloca el rectángulo
Coloca el rectángulo de manera que el punto de registro del MovieClip (el pequeño '+') esté exactamente a mitad de camino entre la parte superior e inferior y el borde izquierdo. Si hiciste tu rectángulo de 50px de alto, entonces el valor y debe ser -25.



Paso 4: Añade el número
Crea una nueva capa y añade un campo de texto dinámico llamado "t_num". Elige una fuente que tenga un aire de aeropuerto o estación de tren (como Helvetica, DIN o Interstate). Yo estoy usando Helvetica Bold.
Establece el Formato de Párrafo como centrado, y recuerda incrustar las fuentes para los números 0-9.
Coloca el campo de texto de manera que esté centrado en el rectángulo de fondo.



Vamos a utilizar este MovieClip como base para otro gráfico, así que tómate un momento para asegurarte de que se ve bien.
Paso 5: Añade una máscara
Crea una nueva capa en la línea de tiempo del MovieClip digit_bottom y llámala 'máscara'. Copia el rectángulo redondeado y pega en su lugar en la capa de la máscara (Edición > Pegar en su lugar, o Comando-Mayúsculas-V).
Selecciona la mitad superior del rectángulo de la máscara y elimínala.
Haz clic con el botón derecho del ratón en la capa de la máscara, elige Máscara y asegúrate de que está enmascarando todas las capas que están por debajo de ella.



Paso 6: Crea el MovieClip digit_top
Ve a la Biblioteca, duplica el MovieClip digit_bottom y nombra la nueva copia como 'digit_top'.
Este MovieClip será prácticamente idéntico al clip digit_bottom, salvo que la máscara mostrará la mitad superior de los gráficos en lugar de la inferior.
Elimina los gráficos que se encuentran en la capa de la máscara. Copia el rectángulo redondeado y de nuevo Pega en su lugar en la capa de la máscara. Esta vez selecciona la mitad inferior y elimínala.
El único otro ajuste que podrías hacer aquí es ajustar el color del texto y el sombreado del rectángulo redondeado del fondo. Hice los gráficos en mi clip digit_top un poco más oscuro para simular la luz que viene de la parte superior.

Paso 7: Crea el MovieClip de Dígitos
Crea un nuevo MovieClip llamado 'Digit'. Arrastra los MovieClips digit_top y digit_bottom y colócalos en 0,0. Dales los nombres de instancia 'top1' y 'bottom1'.
Ahora copia ambos MovieClips (digit_top y digit_bottom), crea una nueva capa y pega en el lugar una copia de cada uno. Nombra las nuevas copias como 'top2' y 'bottom2'.
Ahora deberías tener 4 MovieClips dentro de tu Digit MovieClip: 2 copias de digit_top y 2 copias de digit_bottom. Te explicaré por qué lo estamos configurando así en el siguiente paso.
Paso 8: Estrategia de animación
Tenemos que hacer un poco de trucos de animación para conseguir el efecto de números volteados que queremos. Echa un vistazo al diagrama de abajo de nuestro Digit MovieClip (lo estoy renderizando en 3D para que puedas ver las capas más fácilmente):



Animación Paso 1:
Comenzamos con el clip bottom2 volteado (usando la propiedad scaleY) y colocado detrás del clip top2. En este punto, los dos clips visibles son top2 y bottom1. Los números de estos dos clips se corresponden entre sí, por lo que forman un dígito completo.
Animación Paso 2:
Ahora volteamos hacia abajo el clip top2 hasta el centro del dígito. En este punto la escalaY será cero, por lo que el clip no será visible. Al mismo tiempo, también volteamos hacia abajo el clip inferior2, pero este lo voltearemos hasta el fondo. Como está detrás de top2, no se mostrará hasta que pase por la mitad. Ahora los dos clips visibles son top1 y bottom1. Los números de estos dos clips no coinciden, pero no pasa nada porque este paso solo dura un breve momento.
Animación Paso 3:
El clip top2 se queda en el centro mientras bottom2 sigue cayendo hasta el fondo. Una vez que está en su lugar, los números de los clips visibles (top1 y bottom2) vuelven a coincidir para formar un dígito completo.
Animación Paso 4:
En este punto, vamos a volver a colocar las posiciones de los 2 clips ocultos para prepararnos para el siguiente giro. Fíjate en que los clips están en las mismas posiciones que en el paso 1, solo que invertidos.
Paso 9: Crea el MovieClip del reloj
Ahora que tenemos el MovieClip de Dígitos individuales configurado, vamos a construir el reloj.
Crea un nuevo MovieClip en el escenario llamado 'Clock' con el nombre de instancia 'clock'. Dentro del nuevo MovieClip coloca 9 copias de tu MovieClip de Dígitos; 2 para los segundos, 2 para los minutos, 2 para las horas y 3 para los días. Dale a cada dígito un nombre de instancia. De izquierda a derecha, nombra "dígito0", "dígito1", "dígito2", etc.
Añade unos dos puntos para separar los MovieClips y las etiquetas de cada sección. El diseño depende de ti. He añadido un rectángulo redondeado oscuro como fondo para mi reloj.
Por último, añade un campo de texto dinámico llamado 't_date'. Aquí es donde mostraremos la fecha objetivo para la que el reloj está haciendo la cuenta atrás. Recuerda incrustar la fuente para este campo de texto si no estás usando una fuente del sistema.



Paso 10: Crea la clase Digit
Crea un nuevo archivo de Actionscript llamado 'Digit.as' y añade este código para crear el shell vacío para la clase:
1 |
package { |
2 |
|
3 |
import flash.display.MovieClip; |
4 |
|
5 |
public class Digit extends MovieClip { |
6 |
private const TOP:int = 0; |
7 |
private const BOTTOM:int = 1; |
8 |
|
9 |
private var _currentDigit:Array; |
10 |
private var _nextDigit:Array; |
11 |
private var _number:String = "0"; |
12 |
|
13 |
// CONSTRUCTOR
|
14 |
public function Digit() { |
15 |
_currentDigit = new Array( top1, bottom1 ); |
16 |
_nextDigit = new Array( top2, bottom2 ); |
17 |
|
18 |
}
|
19 |
|
20 |
}
|
21 |
|
22 |
}
|
Esto no hace mucho todavía. Tenemos un par de arrays para mantener los 2 conjuntos de MovieClips digit_top y digit_bottom. He establecido 2 constantes, TOP y BOTTOM para ayudar a mantener el seguimiento de los clips superior e inferior dentro de esos arrays. La variable _number mantendrá el dígito que está en pantalla en cualquier momento.
(Nota: Estoy usando el guión bajo en mis nombres de variables para indicar variables privadas).
Encuentra tu Digit MovieClip en la Biblioteca y asígnale esta clase en los ajustes de Vinculación.



Paso 11: Importa la biblioteca TweenLite
Vamos a utilizar la biblioteca TweenLite para animar nuestro Digit MovieClip.
Descarga la versión AS3 de la biblioteca TweenLite aquí.
Coloca la carpeta 'com' en el mismo directorio que tu archivo principal de Flash (o en tu ruta de origen, si has configurado una ruta de clases diferente).
Añade estas dos líneas en la parte superior de tu clase Digit, justo debajo de la importación de MovieClip:
1 |
import com.greensock.* |
2 |
import com.greensock.easing.* |
Apenas vamos a rascar la superficie de lo que TweenLite puede hacer en este tutorial. Para más información, consulta la documentación de TweenLite.
Paso 12: Programa la animación flipTo
Añade esta función a tu clase Digit:
1 |
public function flipTo(num:String):void { |
2 |
_number = num; |
3 |
_nextDigit[TOP].t_num.text = num; |
4 |
_nextDigit[BOTTOM].t_num.text = num; |
5 |
|
6 |
// flip down the top of the digit to the halfway point
|
7 |
TweenLite.to(_currentDigit[TOP], .15, {scaleY: 0, ease: Linear.easeNone}); |
8 |
// flip the next digit bottom down
|
9 |
TweenLite.to(_nextDigit[BOTTOM], .3, {scaleY:1, onComplete: flipComplete, ease: Bounce.easeOut}); |
10 |
}
|
Esto es lo que ocurre, línea por línea:
- Esta función acepta una cadena de caracteres que contendrá el dígito al que vamos a cambiar. La primera línea solo establece nuestra variable _number para mantener ese dígito.
- A continuación, establecemos los campos de texto en los MovieClips TOP y BOTTOM en nuestro array _nextDigit para mostrar ese mismo dígito.
- Luego usamos TweenLite para interpolar la propiedad scaleY del MovieClip TOP del _currentDigit a 0. Esto da el efecto de que está 'cayendo' hacia el centro del dígito.
- La última línea es otra interpolación, esta vez animando el clip BOTTOM del _nextDigit desde la parte superior del dígito hasta la parte inferior. Nuevamente usamos la propiedad scaleY para simular este efecto, pero esta vez de -1 a 1. Como está interpolando el doble que el clip TOP, le damos el doble de tiempo (.3 segundos en lugar de .15). Cuando esta interpolación termine, llamará a una función llamada 'flipComplete'. Escribiremos esa función en el siguiente paso.
Vuelve a mirar el diagrama del paso 8 si estás confundido con la animación aquí.
Paso 13: Añade la función flipComplete()
Añade esta función a la clase Digit justo debajo de la función flipTo:
1 |
private function flipComplete():void { |
2 |
// swap digits
|
3 |
var next:Array = _currentDigit; |
4 |
_currentDigit = _nextDigit; |
5 |
_nextDigit = next; |
6 |
|
7 |
// reset layering
|
8 |
reset(); |
9 |
}
|
Una vez que la animación de volteo se completa, ejecutaremos esta función. Intercambia los arrays _currentDigit y _nextDigit. Una vez hecho esto, llama a una función llamada 'reset' para restablecer las capas y posiciones del clip para la siguiente vuelta. Vamos a escribir esa función ahora.
Paso 14: Añade la función reset()
Añade esta función a la clase Digit:
1 |
private function reset():void { |
2 |
addChild(_nextDigit[BOTTOM]); |
3 |
addChild(_currentDigit[TOP]); |
4 |
|
5 |
// flip up the next bottom to be behind the current top
|
6 |
_nextDigit[BOTTOM].scaleY = -1; |
7 |
_nextDigit[TOP].scaleY = 1; |
8 |
}
|
Las dos primeras líneas de esta función colocan el _nextDigit BOTTOM y luego el _currentDigit TOP en la parte superior de la lista de visualización. Normalmente utilizo addChild() para hacer esto porque requiere menos escritura que utilizar setChildIndex().
Después de que los clips se vuelvan a estratificar, establecemos las propiedades scaleY para que estén listos para el siguiente giro. Esto significa cambiar _nextDigit[BOTTOM] de 1 a -1 y _nextDigit[TOP] de 0 a 1.
De nuevo, consulta el diagrama del paso 8 si te pierdes.
Paso 15: Añade al Constructor
Una cosa que olvidamos hacer es posicionar los clips correctamente para la primera animación de volteo. Podemos hacerlo fácilmente añadiendo una llamada a la función de reinicio justo en el constructor de la clase Digit:
1 |
// CONSTRUCTOR
|
2 |
public function Digit() { |
3 |
_currentDigit = new Array( top1, bottom1 ); |
4 |
_nextDigit = new Array ( top2, bottom2 ); |
5 |
|
6 |
reset(); |
7 |
}
|
Paso 16: Añade la función number()
Una última cosa que necesitaremos en nuestra clase Digit es una forma de acceder a la variable privada _number desde fuera de la clase. Añadiremos una simple función de acceso:
1 |
public function get number():String { |
2 |
return _number; |
3 |
}
|
Paso 17: Crea la clase reloj
Crea un nuevo archivo ActionScript llamado 'Clock.as'. Pega este código:
1 |
package { |
2 |
|
3 |
import flash.display.MovieClip; |
4 |
import flash.events.TimerEvent; |
5 |
import flash.media.Sound; |
6 |
import flash.utils.Timer; |
7 |
|
8 |
public class Clock extends MovieClip { |
9 |
private var _clockTimer:Timer; |
10 |
private var _targetDate:Date; |
11 |
|
12 |
// CONSTRUCTOR
|
13 |
public function Clock() { |
14 |
|
15 |
}
|
16 |
|
17 |
}
|
18 |
|
19 |
}
|
No hay mucho aquí todavía. Solo importando algunas de las clases que necesitaremos. También tengo un par de variables privadas. _clockTimer contará los segundos por nosotros, y _targetDate mantendrá la fecha hasta la que estamos contando.
Paso 18: Añade la función set()
Añade esta función a la clase Reloj justo debajo del constructor:
1 |
// set the target date and start the countdown timer
|
2 |
public function set(date:Date):void { |
3 |
_targetDate = date; |
4 |
|
5 |
_clockTimer = new Timer(1000) // tick every second (1000 milliseconds) |
6 |
_clockTimer.addEventListener(TimerEvent.TIMER, update); |
7 |
_clockTimer.start(); |
8 |
|
9 |
// display the target date above the clock
|
10 |
t_date.text = _targetDate.toLocaleString().toUpperCase(); |
11 |
|
12 |
// update the clock once here so it starts with the correct time
|
13 |
update(); |
14 |
}
|
Esta es la función que usaremos para establecer la fecha objetivo del reloj. Acepta una fecha (por supuesto) y la asigna a la variable _targetDate. A continuación, instala nuestro _clockTimer. El _clockTimer llamará a la función de actualización una vez por segundo para actualizar los dígitos.
Después de iniciar el temporizador, la función establece el texto t_date con la fecha objetivo. La función toLocaleString() garantiza que la fecha se muestre en la zona horaria local del usuario.
La última línea de esta función llama a update una vez para ajustar el reloj a la hora adecuada. De lo contrario, mostraría "000 00:00:00" durante un segundo hasta el primer evento del temporizador.
Paso 19: Añade la función update()
Esta función es un poco larga porque es donde se hace la mayor parte del trabajo. Añádela a tu clase Reloj:
1 |
private function update(e:TimerEvent = null):void { |
2 |
var now:Date = new Date(); // get the current time |
3 |
|
4 |
// find the difference (in ms) between the target and now
|
5 |
var diff:Number = _targetDate.valueOf() - now.valueOf(); |
6 |
if(diff <=0){ |
7 |
// TIME'S UP!
|
8 |
// do something cool here
|
9 |
_clockTimer.stop(); |
10 |
_clockTimer.removeEventListener(TimerEvent.TIMER, update); |
11 |
diff = 0; |
12 |
}
|
13 |
|
14 |
// convert to seconds
|
15 |
diff = Math.round(diff/1000); |
16 |
|
17 |
// number of days
|
18 |
var days:int = Math.floor(diff/ (24 * 60 * 60)); |
19 |
diff -= days*(24 * 60 * 60 ); |
20 |
|
21 |
// number of hours
|
22 |
var hours:int = Math.floor(diff / (60 * 60)) |
23 |
diff -= hours*60 * 60; |
24 |
|
25 |
// number of minutes
|
26 |
var min:int = Math.floor(diff/ 60); |
27 |
diff -= min*60; |
28 |
|
29 |
// seconds are all that remain
|
30 |
var sec:int = diff; |
31 |
|
32 |
// create an array of strings to hold the number for each value
|
33 |
var diffArr:Array = new Array(String(days), String(hours), String(min), String(sec)); |
34 |
var diffString:String = "" |
35 |
var len:int = 3; // the first value (days) has 3 digits. All the rest have 2 |
36 |
for each(var s:String in diffArr){ |
37 |
// pad the string with a leading zero if needed
|
38 |
while(s.length < len){ |
39 |
s = "0"+s; |
40 |
}
|
41 |
|
42 |
len = 2; // all the other values are 2 digits in length |
43 |
diffString += s; // add the padded string to the diffString |
44 |
}
|
45 |
|
46 |
// go through each character in the diffString and set the corresponding digit
|
47 |
for(var i:int = 0; i< diffString.length; i++){ |
48 |
if(diffString.substr(i, 1) != this["digit"+i].number){ |
49 |
this["digit"+i].flipTo(diffString.substr(i, 1)); |
50 |
}
|
51 |
}
|
52 |
|
53 |
}
|
Esta función acepta un TimerEvent como parámetro. El valor por defecto de este parámetro es null. Esto nos permite llamar a la función sin enviar un parámetro, como estamos haciendo en la función set.
La primera línea de esta función obtiene la fecha y hora actuales como un objeto Date. A continuación, encontramos la diferencia entre la fecha actual y la fecha objetivo (línea 37). Si la diferencia es 0 o menos, entonces ha pasado la fecha objetivo, por lo que detenemos el _clockTimer (líneas 38-44).
Dado que la diferencia de tiempo entre el momento actual y el objetivo se calcula en milisegundos, tenemos que convertirla en una pantalla legible de días, horas, minutos y segundos (líneas 46-62). La matemática aquí es bastante simple siempre y cuando sepas que hay 1000 milisegundos en un segundo, 60 segundos en un minuto, 60 minutos en una hora y 24 horas en un día.
En la línea 65 almacenamos todos esos valores como elementos en un array. A partir de la línea 68 recorremos cada elemento y lo añadimos a una cadena de caracteres llamada 'diffString'. Al hacer esto también añadimos ceros a la izquierda donde sea necesario (línea 71). Así que si nuestros valores para el reloj fueran 30 días, 5 horas, 56 minutos y 6 segundos la diffString se vería así "030055606".
Lo último que hace esta función es un bucle a través de los caracteres de la diffString (usando el método charAt()). Para cada carácter de la cadena comprobamos si es diferente del número que se muestra actualmente en el dígito correspondiente. Esto es fácil debido a la forma en que nombramos nuestras instancias de dígitos. Si el número no es el mismo que el que se muestra actualmente, le decimos a ese dígito que cambie al número de la diffString.
Paso 20: Añade el sonido
Encuentra (o crea) un buen sonido de tic-tac que se reproduzca cada vez que el reloj se actualice. Imprímelo en la biblioteca de tu archivo Flash y establece el nombre de la clase como 'TickSound' en los ajustes de enlace.



Añade la variable _tickSound a la parte superior de tu clase Clock justo debajo de las otras dos variables:
1 |
private var _clockTimer:Timer; |
2 |
private var _targetDate:Date; |
3 |
private var _tickSound:Sound = new TickSound(); |
Y reproducir el sonido dentro de la función update:
1 |
_tickSound.play(); |
Paso 21: Añade la clase documental principal
Nuestro temporizador de cuenta atrás está completo, solo necesitamos alguna forma de establecer la fecha objetivo. Crea un nuevo archivo Actionscript llamado 'Main.as' con este código:
1 |
package { |
2 |
|
3 |
import flash.display.MovieClip; |
4 |
|
5 |
public class Main extends MovieClip { |
6 |
|
7 |
public function Main() { |
8 |
|
9 |
// set the target date for the clock
|
10 |
var targetDate:Date = new Date(); |
11 |
targetDate.setTime( Date.UTC(2010, 4, 28, 20, 00) ); |
12 |
clock.set(targetDate); |
13 |
}
|
14 |
}
|
15 |
|
16 |
}
|
Todo lo que esto hace es establecer la fecha objetivo para la instancia del Reloj en el Escenario. Estoy usando setTime() y Date.UTC() para convertir la fecha a código de tiempo universal. De este modo, la fecha será correcta cuando se convierta a la hora local en el ordenador del usuario. Además, recuerda que los meses se basan en el cero. Así, el mes 4 es en realidad mayo, no abril.
En tu archivo Flash establece la clase Documento como 'Principal'.

Si necesitas un repaso sobre el uso de la Clase Documental consulta este Consejo Rápido.
Paso 22: Prueba
Prueba tu película ahora y todo debería funcionar. Intenta cambiar la fecha objetivo en la clase Main y observa cómo cambia la cuenta atrás.
Una posible desventaja de la forma en que hemos configurado esto es que la fecha objetivo está codificada en nuestro SWF. Eso está bien, pero sería genial si pudiéramos cargar dinámicamente la fecha para poder reutilizar la cuenta atrás para diferentes cosas.
Veamos qué podemos hacer al respecto...
Paso 23: Crea el archivo XML
Crea un nuevo archivo XML en la misma carpeta que tu archivo Flash llamado 'targetDate.xml' (un archivo XML es solo un archivo de texto plano). Añade esto al archivo XML:
1 |
<targetDate>
|
2 |
<year>2011</year> |
3 |
<month>3</month> |
4 |
<day>25</day> |
5 |
<hour>20</hour> |
6 |
<minute>21</minute> |
7 |
</targetDate>
|
El uso de este formato para nuestra fecha objetivo está bastante inflado (hay más marcas que datos reales), pero mantendrá las cosas muy claras para los propósitos de este tutorial.
Paso 24: Carga del XML
Ahora hagamos algunos cambios en nuestra clase documental Main. Reemplaza todo en ese archivo con este código:
1 |
package { |
2 |
|
3 |
import flash.display.MovieClip; |
4 |
import flash.net.URLLoader; |
5 |
import flash.net.URLRequest; |
6 |
import flash.events.Event; |
7 |
|
8 |
public class Main extends MovieClip { |
9 |
|
10 |
// CONSTRUCTOR
|
11 |
public function Main() { |
12 |
// load the XML
|
13 |
var xmlLoader:URLLoader = new URLLoader(); |
14 |
xmlLoader.addEventListener(Event.COMPLETE, onDataLoaded); |
15 |
xmlLoader.load( new URLRequest("targetDate.xml") ); |
16 |
|
17 |
}
|
18 |
|
19 |
}
|
20 |
|
21 |
}
|
Notarás que hemos importado algunas clases adicionales para ayudarnos a cargar el archivo XML. En la función constructora estamos creando una nueva instancia de URLLoader para cargar el archivo por nosotros. Adjuntamos un receptor de eventos que llamará a una función llamada 'onDataLoaded' cuando el archivo termine de cargarse.
Paso 25: Añade la función onDataLoaded()
Añade esta función a la clase Main:
1 |
private function onDataLoaded(e:Event):void { |
2 |
var xml:XML = new XML(e.target.data); |
3 |
|
4 |
var targetDate:Date = new Date(); |
5 |
targetDate.setTime(Date.UTC(int(xml.year), int(xml.month), int(xml.day), int(xml.hour), int(xml.minute) )); |
6 |
|
7 |
clock.set(targetDate); |
8 |
}
|
Esta función crea un nuevo objeto XML a partir del archivo que cargamos. A continuación, creamos un nuevo objeto Date a partir de los valores del XML. Volvemos a utilizar setTime() y Date.UTC() para convertir la fecha a código de tiempo universal. La línea final es la misma que antes, solo envía la fecha objetivo a nuestra instancia del Reloj.
Paso 26: Conclusión
Eso es todo para este caso. Sin embargo, hay un par de mejoras que podría hacer:
- Dependiendo de para qué estés usando la cuenta atrás, puede que quieras hacer algo especial para el usuario cuando la cuenta atrás llegue a cero. Esto lo añadirías a la clase Clock en la parte de la función de actualización que comprueba si el temporizador está a cero.
- Como ya he mencionado, el formato de nuestro XML es bastante derrochador tal y como está. Es mejor pasar la fecha como una cadena a través de FlashVars, utilizar un formato de datos diferente (como JSON), o simplemente reformatear el XML para que sea un poco más compacto.
¡Buena suerte! Como siempre, publica un comentario y hazme saber tu opinión.