Introducción a la Arquitectura de Move.js
Move.js es una biblioteca de JavaScript diseñada para simplificar la creación de animaciones CSS3 complejas mediante una API fluida y encadenable. En lugar de manipular directamente las propiedades de estilo en cada fotograma, la librería aprovecha las transiciones y transformaciones nativas de CSS3, garantizando un rendimiento óptimo acelerado por hardware.
Estructura del Motor de Animación
El código fuente original de la biblioteca utiliza un sistema de módulos antiguo y funciones de empaquetado extensas. Para comprender su lógica interna, a continuación se presenta una refactorización moderna del núcleo de la clase Move utilizando sintaxis ES6. Esta implementación demuestra cómo se almacenan en búfer las transformaciones y propiedades antes de aplicarlas al DOM.
class MotorAnimacion {
constructor(selector) {
this.elemento = typeof selector === 'string'
? document.querySelector(selector)
: selector;
if (!this.elemento) throw new Error('Elemento DOM no encontrado');
this.propiedadesCss = {};
this.transformaciones = [];
this.duracion = 500; // ms por defecto
this.retardo = 0;
this.curvaAceleracion = 'ease';
}
_establecerPropiedadVendor(propiedad, valor) {
const prefijos = ['-webkit-', '-moz-', '-ms-', '-o-', ''];
prefijos.forEach(prefijo => {
this.propiedadesCss[`${prefijo}${propiedad}`] = valor;
});
return this;
}
agregarTransformacion(valor) {
this.transformaciones.push(valor);
return this;
}
trasladar(x, y = 0) {
return this.agregarTransformacion(`translate3d(${x}px, ${y}px, 0)`);
}
rotar(grados) {
return this.agregarTransformacion(`rotate(${grados}deg)`);
}
escalar(factorX, factorY = factorX) {
return this.agregarTransformacion(`scale(${factorX}, ${factorY})`);
}
configurarDuracion(tiempo) {
this.duracion = typeof tiempo === 'string' ? parseFloat(tiempo) * 1000 : tiempo;
return this._establecerPropiedadVendor('transition-duration', `${this.duracion}ms`);
}
configurarEase(tipo) {
this.curvaAceleracion = tipo;
return this._establecerPropiedadVendor('transition-timing-function', tipo);
}
ejecutar(callback) {
if (this.transformaciones.length > 0) {
this._establecerPropiedadVendor('transform', this.transformaciones.join(' '));
}
// Aplicar propiedades al DOM
Object.keys(this.propiedadesCss).forEach(prop => {
this.elemento.style.setProperty(prop, this.propiedadesCss[prop]);
});
if (typeof callback === 'function') {
this.elemento.addEventListener('transitionend', callback, { once: true });
}
return this;
}
}
// Función de factoría global
const animar = (selector) => new MotorAnimacion(selector);
Manipulación de Propiedades CSS
La API permite modificar propiedades estándar de CSS. Es fundamental proporcionar las unidades correctas (como px, em, %) al utilizar el método de asignación directa, mientras que los métodos incrementales aceptan valores numéricos.
const cajaObjetivo = document.querySelector('.caja-animada');
// Asignación absoluta (requiere unidad de medida)
animar(cajaObjetivo)
.set('margin-left', '250px')
.set('background-color', '#3498db')
.ejecutar();
// Modificación relativa (incremento y decremento)
// El motor calcula el valor actual y suma/resta la cantidad numérica
animar(cajaObjetivo)
.add('margin-left', 50) // Suma 50px al valor actual
.sub('padding-top', 10) // Resta 10px al valor actual
.ejecutar();
Transformaciones Espaciales
Las transformaciones CSS3 se gestionan mediante métodos dedicados que se acumulan en un búfer interno y se aplican conjuntamente para evitar sobrescrituras.
// Traslación en ejes X e Y
animar('.elemento-movil')
.trasladar(300, 150) // Equivalente a translate3d(300px, 150px, 0)
.ejecutar();
// Rotación y Escalado combinados
animar('.elemento-movil')
.rotar(45) // 45 grados
.escalar(1.5, 2.0) // 1.5x en X, 2.0x en Y
.ejecutar();
// Inclinación (Skew)
animar('.elemento-movil')
.skew(30, 15) // Inclinación en X e Y
.ejecutar();
Control de Tiempo y Curvas de Aceleración
El comportamiento temporal de la animación se define mediante la duración, el retardo y la función de temporización (easing).
animar('.tarjeta')
.trasladar(400, 0)
.configurarDuracion('2.5s') // Acepta milisegundos o strings con 's'
.delay(500) // Retardo antes de iniciar
.configurarEase('cubic-bezier(0.68, -0.55, 0.265, 1.55)') // Efecto rebote
.ejecutar();
Secuenciación de Animaciones
Para crear coreografías complejas, la biblioteca permite dividir las animaciones en etapas secuenciales. El método then() crea un nuevo contexto de animación que se ejecuta tras finalizar el anterior, mientras que pop() permite regresar al contexto padre.
const animacionRetorno = animar('.navegador')
.set('background-color', '#ffffff')
.trasladar(0, 0);
animar('.navegador')
.set('background-color', '#e74c3c')
.trasladar(500, 0)
.escalar(0.8)
.then(animacionRetorno) // Encadena la animación de retorno
.ejecutar();
// Secuenciación anidada con pop()
animar('.icono')
.rotar(90)
.then()
.escalar(1.5)
.set('opacity', '0.5')
.then()
.trasladar(0, 100)
.pop() // Regresa al nivel de escalar
.pop() // Regresa al nivel de rotar
.ejecutar();
Integración con CSS Keyframes
Además de las transiciones, es posible invocar animaciones definidas en hojas de estilo CSS mediante el método animate(), pasando un objeto de configuración para sus parámetros.
/* Definición en CSS:
@keyframes pulsoNeon {
0% { box-shadow: 0 0 5px #0ff; }
50% { box-shadow: 0 0 20px #0ff; }
100% { box-shadow: 0 0 5px #0ff; }
}
*/
animar('.boton-accion')
.animate('pulsoNeon', {
duration: '2s',
'iteration-count': 'infinite',
'direction': 'alternate'
})
.ejecutar();
Resolución de Problemas: Transiciones Omitidas en Secuencias
Un problema común al encadenar transformaciones inmediatas es que el navegador puede fusionar los cambios de estado, omitiendo la transición visual y saltando directamente al estado final. Esto ocurre porque el motor de renderizado no detecta un cambio de estado intermedio válido para iniciar la transición CSS.
La solución técnica consiste en forzar un micro-retardo o un reflujo del DOM entre las etapas de la secuencia.
// Comportamiento erróneo: La segunda transición no se renderiza
animar('.panel-deslizante')
.trasladar(500, 0)
.then()
.trasladar(0, 0) // Salta instantáneamente al origen
.pop()
.ejecutar();
// Solución: Inyección de un retardo mínimo para forzar el ciclo de renderizado
animar('.panel-deslizante')
.trasladar(500, 0)
.then()
.delay(1) // Retardo de 1ms fuerza al navegador a procesar el frame anterior
.trasladar(0, 0) // La transición ahora se ejecuta correctamente
.pop()
.ejecutar();