Carmen Ansio
Design Engineer at LottieFiles
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
(interacciones avanzadas, personalización dinámica, <canvas>, WebGL, etc.).
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
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
@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 permite el renderizado de gráficos 3D directamente en el navegador utilizando la GPU. Se usa frecuentemente en juegos y visualizaciones avanzadas.
@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);
@carmenansio
@carmenansio
@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
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
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.
@carmenansio
@carmenansio
@carmenansio
@carmenansio
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);
}
});
@carmenansio
@carmenansio
@carmenansio
@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
@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
@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
@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
@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
@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
@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
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@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
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@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;
}
}
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
@carmenansio
By Carmen Ansio
Slides para JSConf España 2025