@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio

Moviendo cosas

LottieFiles

@carmenansio
@carmenansio

Animaciones con

JavaScript

@carmenansio
@carmenansio

Casos donde CSS no es suficiente

(interacciones avanzadas, personalización dinámica, <canvas>, WebGL, etc.).

Stack clave

para animaciones JS

@carmenansio
@carmenansio
@carmenansio
@carmenansio

Animaciones con

<canvas>

@carmenansio
@carmenansio

Canvas API

La API de Canvas proporciona un área de dibujo en la web. Con el contexto 2D, permite gráficos y animaciones con JavaScript.

 

Para 3D, usa WebGL, que aprovecha la GPU para

renderizado avanzado

Contexto 2D

CanvasRenderingContext2D

@carmenansio
@carmenansio
<canvas id="myCanvas"></canvas>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

ctx.fillStyle = 'blue';
ctx.fillRect(50, 50, 100, 100); 
// Dibuja un cuadrado azul
@carmenansio
@carmenansio

WebGL (Web Graphics Library)

WebGL permite el renderizado de gráficos 3D directamente en el navegador utilizando la GPU. Se usa frecuentemente en juegos y visualizaciones avanzadas.

Contexto 3D

(WebGLRenderingContext o WebGL2RenderingContext)

@carmenansio
@carmenansio
<canvas id="myCanvas"></canvas>
const canvas = document.getElementById('myCanvas');
const gl = canvas.getContext('webgl'); // Se obtiene el contexto WebGL

// Configura el color de fondo y limpia el canvas
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
  • Usa shaders (GPU Pixeles)

  • (X, Y, Z).

  • Iluminación

  • Sombras

  • Materiales

@carmenansio
@carmenansio

Three.js abstrae toda esa complejidad y proporciona una API más fácil de usar.

@carmenansio
@carmenansio
// Crear la escena
const scene = new THREE.Scene();

// Crear la cámara (perspectiva)
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);

// Crear el renderizador WebGL
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// Crear un cubo
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

// Posicionar la cámara
camera.position.z = 5;

// Animación
function animate() {
    requestAnimationFrame(animate);
    cube.rotation.x += 0.01;
    cube.rotation.y += 0.01;
    renderer.render(scene, camera);
}
animate();
@carmenansio
// configuración three.js
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
	75,
	window.innerWidth / window.innerHeight,
	0.1,
	1000
);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById("container").appendChild(renderer.domElement);

// configuración cubo de Rubik
const rubikCube = new THREE.Group();
scene.add(rubikCube);

const cubieSize = 0.9;

@carmenansio
// configurar Tweakpane
const pane = new Tweakpane.Pane();
const params = {
	rotationSpeedX: 0.01,
	rotationSpeedY: 0.01,
	rotationSpeedZ: 0.01
};
pane.addInput(params, "rotationSpeedX", { min: 0, max: 0.1, step: 0.01 });
pane.addInput(params, "rotationSpeedY", { min: 0, max: 0.1, step: 0.01 });
pane.addInput(params, "rotationSpeedZ", { min: 0, max: 0.1, step: 0.01 });
pane.addButton({ title: "Reset Cube" }).on("click", () => {
	rubikCube.rotation.set(0, 0, 0);
});
@carmenansio
@carmenansio

Efectos interactivos

texto interactivo

@carmenansio
@carmenansio
@carmenansio

Comencemos por

saber que es una animación

@carmenansio
@carmenansio

No vemos los objetos moverse

Nuestros ojos capturan unos 150 snapshots por segundo y el cerebro los procesa como un flujo continuo, interpolando los cambios entre cada imagen para generar la ilusión de movimiento.

🧠

El problema

Las animaciones suelen correr a 60 FPS

@carmenansio

Si los cálculos necesarios para un frame superan ese tiempo, la animación puede perder fluidez.

@carmenansio

Esto ocurre con cálculos complejos, como manipular demasiados elementos del DOM o realizar operaciones pesadas en tiempo real.

@carmenansio
@carmenansio

requestAnimationFrame

Ejecuta código sincronizado con los frames renderizados para animaciones fluidas y eficientes.

🧠

@carmenansio
// Mal ejemplo: muchas actualizaciones directas
for (let i = 0; i < 1000; i++) {
  element.style.width = i + "px";
}

// Buen ejemplo: uso de batch
let width = 0;
requestAnimationFrame(function update() {
  element.style.width = width + "px";
  if (width < 1000) {
    width++;
    requestAnimationFrame(update);
  }
});

Matemáticas

en acción

@carmenansio
@carmenansio
@carmenansio

1.

Configuración inicial.

@carmenansio
@carmenansio
const canvas = document.getElementById("fluid");
const gl = canvas.getContext("webgl2");

// Obtiene el elemento canvas y crea un contexto WebGL2
// WebGL2 es la API moderna para gráficos 3D en el navegador

2.

Vertex Shaders.

@carmenansio
@carmenansio
in vec2 position;
void main() {
    gl_Position = vec4(position, 0.0, 1.0);
}

// Es muy simple
// Solo posiciona los vértices en la pantalla
// Crea un cuadrado que cubre toda la pantalla

3.

Fragment Shaders.

@carmenansio
@carmenansio
uniform vec2 resolution;
uniform float time;
uniform vec2 mouse;

// Los uniforms son variables que podemos controlar desde JavaScript

// resolution: tamaño de la pantalla
// time: tiempo transcurrido
// mouse: posición del cursor

4.

Función Palette.

@carmenansio
@carmenansio
vec3 palette(float t) {
    vec3 a = vec3(0.5, 0.5, 0.5);
    vec3 b = vec3(0.5, 0.5, 0.5);
    vec3 c = vec3(1.0, 1.0, 1.0);
    vec3 d = vec3(0.263, 0.416, 0.557);
    return a + b * cos(6.28318 * (c * t + d));
}

// Genera colores suaves que cambian con el tiempo
// Usa funciones trigonométricas para crear transiciones suaves

5.

Loop Principal del Fragment Shader.

@carmenansio
@carmenansio
for(float i = 0.0; i < 4.0; i++) {
    uv = fract(uv * 1.5) - 0.5;
    float d = length(uv) * exp(-length(uv0));
    vec3 col = palette(length(uv0) + i*.4 + time*.4);
    d = sin(d*8. + time)/8.;
    d = abs(d);
    d = pow(0.01 / d, 1.2);
    finalColor += col * d;
}

// Crea el efecto fluido principal
// Cada iteración añade una capa al efecto
// Usa matemáticas para crear patrones orgánicos

6.

Interactividad con el Mouse.

@carmenansio
@carmenansio
function updateMouse(x, y) {
    const rect = canvas.getBoundingClientRect();
    mouseX = (x - rect.left) * window.devicePixelRatio;
    mouseY = canvas.height - (y - rect.top) * window.devicePixelRatio;
}

// Actualiza la posición del mouse
// Considera la densidad de píxeles del dispositivo
// Invierte el eje Y para coincidir con las coordenadas de WebGL

7.

Loop de Renderizado.

@carmenansio
@carmenansio
function render() {
    const time = (Date.now() - startTime) * 0.001;
    gl.uniform2f(resolutionLocation, canvas.width, canvas.height);
    gl.uniform1f(timeLocation, time);
    gl.uniform2f(mouseLocation, mouseX, mouseY);
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
    requestAnimationFrame(render);
}

// Actualiza los uniforms cada frame
// Dibuja la escena
// Usa requestAnimationFrame para mantener una animación suave
@carmenansio

Showcase

@carmenansio

  ➜  Local:   http://localhost:5173/

Mi mayor problema

siempre me preguntan lo mismo

@carmenansio
@carmenansio
@carmenansio

Lo bonito

también puede ser funcional

@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio

Partículas

ejemplo paso a paso

@carmenansio
@carmenansio
@carmenansio
@carmenansio
<canvas id="canvas"></canvas>
@carmenansio
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
@carmenansio
// Tamaño del canvas
function resize() {
	canvas.width = window.innerWidth;
	canvas.height = window.innerHeight;
}
resize();
window.addEventListener("resize", resize);
@carmenansio
// Configuración del texto
const text = "JSConf";
ctx.font = "bold 120px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
@carmenansio
// Pixeles para el texto
function getTextPixels() {
	ctx.fillStyle = "#fff";
	ctx.fillText(text, canvas.width / 2, canvas.height / 2);
	return ctx.getImageData(0, 0, canvas.width, canvas.height);
}
@carmenansio
// Clase Particula
class Particle {
	constructor(x, y) {
		this.originX = x;
		this.originY = y;
		this.reset();
	}

// ...
@carmenansio
// Generamos un array con 1000 partículas inicializadas con la clase Particle.
const particles = Array.from({ length: 1000 }, () => new Particle());
@carmenansio
// Genera las partículas del texto en pixeles
let particles = [];
function createParticles() {
	particles = [];
	const pixels = getTextPixels();
	const data = pixels.data;

	for (let y = 0; y < canvas.height; y += 4) {
		for (let x = 0; x < canvas.width; x += 4) {
			const index = (y * canvas.width + x) * 4;
			if (data[index] > 128) {
				particles.push(new Particle(x, y));
			}
		}
	}
}
@carmenansio
// Interacción del mouse
let mouse = { x: canvas.width / 2, y: canvas.height / 2 };
canvas.addEventListener("mousemove", (e) => {
	mouse.x = e.clientX;
	mouse.y = e.clientY;
});
@carmenansio
// Animación loop
function animate() {
	ctx.fillStyle = "rgba(0, 0, 0, 0.1)";
	ctx.fillRect(0, 0, canvas.width, canvas.height);

	particles.forEach((particle) => {
		particle.update(mouse);
		particle.draw();
	});

	requestAnimationFrame(animate);
}
@carmenansio
// !important
requestAnimationFrame(animate);
@carmenansio
// Inicializar
createParticles();
animate();
@carmenansio
@carmenansio

Partículas

con formatos gráficos <SVG>

@carmenansio
@carmenansio
@carmenansio

Partículas con

imágenes

@carmenansio

Placeholder

https://picsum.photos/800/600

@carmenansio
@carmenansio
@carmenansio
preload()
setup()
@carmenansio
let img;
let particles = [];
let dispersalSpeed = 2;

function preload() {
    img = loadImage("https://picsum.photos/800/600"); // Imagen externa con Picsum
}

function setup() {
    createCanvas(windowWidth, windowHeight);
    imageMode(CENTER);
    resetParticles();
}
@carmenansio
function resetParticles() {
    particles = [];
    let imgWidth = width * 0.8;
    let imgHeight = (img.height / img.width) * imgWidth;
    
    for (let x = 0; x < img.width; x += 5) {
        for (let y = 0; y < img.height; y += 5) {
            let c = img.get(x, y); // Obtiene el color del píxel
            let screenX = map(x, 0, img.width, width / 2 - imgWidth / 2, width / 2 + imgWidth / 2);
            let screenY = map(y, 0, img.height, height / 2 - imgHeight / 2, height / 2 + imgHeight / 2);
            
            particles.push({
                x: screenX,
                y: screenY,
                targetX: screenX,
                targetY: screenY,
                color: c,
                velocityX: random(-1, 1) * dispersalSpeed,
                velocityY: random(-1, 1) * dispersalSpeed,
            });
        }
    }
}
@carmenansio
function draw() {
    background(0);
    if (displayImage) {
        particles.forEach(p => {
            fill(p.color);
            noStroke();
            ellipse(p.x, p.y, 5, 5);
            
            // Actualización de posición para el efecto de explosión
            p.x += p.velocityX;
            p.y += p.velocityY;
        });
    }
}
@carmenansio
function keyPressed() {
    if (key === ' ') { // Barra espaciadora para iniciar la explosión
        explosionStarted = true;
    }
}

Disclaimer sobre

el resultado

@carmenansio
@carmenansio

Nada sale

a la primera

@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio

Algunos ejemplos

creativos

@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio

Trabajando con

texto

@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio

Landing Page

con texto animado

@carmenansio
@carmenansio

Ejemplos

del mundo real

@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio

Recursos

para seguir aprendiendo

@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio

Trabaja

inteligente

@carmenansio
@carmenansio
@carmenansio

Trabaja

inteligente

@carmenansio

Trabaja

divertido

@carmenansio
@carmenansio
@carmenansio
@carmenansio
Made with Slides.com