SEMANA 10
Programación creativa, gráficos generativos e interactividad en el navegador con p5.js.
p5.js es una librería de JavaScript para la programación creativa. Nació como una reinterpretación web del lenguaje Processing, con el objetivo de hacer accesible el código a artistas, diseñadores y cualquier persona que quiera aprender.
Funciona dibujando píxel a píxel en un elemento <canvas> de HTML5. Todo ocurre en el navegador, sin necesidad de instalaciones.
Filosofía de p5.js: el código no es solo una herramienta para resolver problemas técnicos — es también un medio de expresión artística y creativa. p5.js fue diseñada por y para personas que quieren explorar ese espacio.
<!-- Opción 1: CDN (más fácil para empezar) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.3/p5.min.js"></script>
<!-- Opción 2: npm (proyectos con bundler) -->
// npm install p5
import p5 from 'p5';
// Un sketch p5.js siempre tiene dos funciones principales:
function setup() {
// Se ejecuta UNA sola vez al inicio
createCanvas(400, 400); // ancho, alto en píxeles
}
function draw() {
// Se ejecuta en BUCLE (60 fps por defecto)
background(220); // limpiar el canvas cada frame
ellipse(200, 200, 80, 80); // x, y, ancho, alto
}
Coordenadas en p5.js: el punto (0, 0) está en la esquina superior izquierda. El eje X crece hacia la derecha y el eje Y crece hacia abajo. Esto es diferente al sistema de coordenadas matemático, donde Y crece hacia arriba.
Estos dos son los pilares de cualquier sketch. Entender cuándo se ejecuta cada uno es fundamental.
| Función | Cuándo se ejecuta | Para qué se usa |
|---|---|---|
setup() |
Una sola vez, al cargar el sketch | Crear el canvas, definir tamaño, modo de color, cargar recursos |
draw() |
En bucle, ~60 veces por segundo | Dibujar formas, actualizar posiciones, leer mouse/teclado |
preload() |
Antes de setup() | Cargar imágenes, fuentes y archivos de datos |
mousePressed() |
Al hacer clic | Reaccionar al clic del ratón |
keyPressed() |
Al presionar una tecla | Reaccionar al teclado |
windowResized() |
Al cambiar el tamaño de la ventana | Ajustar el canvas al nuevo tamaño |
function setup() {
createCanvas(400, 300);
frameRate(30); // limitar a 30 fps
colorMode(HSB); // modo de color: Hue, Saturation, Brightness
noStroke(); // quitar contornos globalmente
}
function draw() {
background(0, 0, 15); // fondo oscuro en modo HSB
// frameCount: número de frames transcurridos desde el inicio
let x = (frameCount * 2) % width;
fill(frameCount % 360, 80, 90);
ellipse(x, height / 2, 40, 40);
}
function mousePressed() {
// Se llama automáticamente cada vez que se hace clic
console.log(`Clic en: ${mouseX}, ${mouseY}`);
}
p5.js tiene funciones para dibujar las formas geométricas más comunes. Todos los parámetros se miden en píxeles.
// PUNTO
point(x, y);
// LÍNEA
line(x1, y1, x2, y2);
// RECTÁNGULO — por defecto desde la esquina superior izquierda
rect(x, y, ancho, alto);
rect(x, y, ancho, alto, radio); // con bordes redondeados
rectMode(CENTER); // x,y pasan a ser el centro
// ELIPSE / CÍRCULO — el origen es siempre el centro
ellipse(x, y, ancho, alto);
circle(x, y, diametro); // shorthand
// TRIÁNGULO
triangle(x1, y1, x2, y2, x3, y3);
// CUADRILÁTERO (4 puntos)
quad(x1,y1, x2,y2, x3,y3, x4,y4);
// ARCO — startAngle y endAngle en radianes
arc(x, y, ancho, alto, 0, PI); // semicírculo inferior
arc(x, y, ancho, alto, 0, TWO_PI, PIE); // modo pastel
// FORMA PERSONALIZADA (vértices libres)
beginShape();
vertex(50, 20);
vertex(150, 20);
vertex(100, 120);
endShape(CLOSE);
// rectMode afecta cómo se interpreta (x, y)
rectMode(CORNER); // defecto: (x,y) = esquina superior izquierda
rectMode(CENTER); // (x,y) = centro del rectángulo
rectMode(CORNERS); // (x1,y1) y (x2,y2) = esquinas opuestas
// ellipseMode — igual que rectMode pero para elipses
ellipseMode(CENTER); // defecto: (x,y) = centro
ellipseMode(CORNER); // (x,y) = esquina superior izquierda
p5.js acepta colores en múltiples formatos: escala de grises, RGB, RGBA, hexadecimal, y en modo HSB/HSL.
// Escala de grises (0–255)
fill(128); // gris medio
fill(255); // blanco
fill(0); // negro
// RGB (0–255 por canal)
fill(255, 100, 50); // naranja
// RGBA — cuarto parámetro = alpha (0 transparente, 255 opaco)
fill(255, 100, 50, 150); // naranja semitransparente
// Hexadecimal
fill('#e8a020');
// colorMode(HSB) — Hue(0-360), Saturation(0-100), Brightness(0-100)
colorMode(HSB);
fill(200, 80, 90); // azul brillante
// stroke → color del contorno
stroke(255, 0, 0); // contorno rojo
strokeWeight(3); // grosor del contorno en píxeles
noStroke(); // quitar el contorno
noFill(); // quitar el relleno (solo contorno)
// color() crea un objeto de color que se puede guardar
let rojo = color(255, 50, 50);
let azul = color(50, 100, 255);
// lerpColor → interpola entre dos colores
// 0.0 = primer color, 1.0 = segundo color
let mezcla = lerpColor(rojo, azul, 0.5); // → morado
// map() → convierte un valor de un rango a otro
// muy útil para mapear posición → color
let matiz = map(mouseX, 0, width, 0, 360);
La animación en p5.js surge del bucle draw(). Cada vez que se ejecuta, podemos actualizar variables de estado y redibujar el canvas con nuevas posiciones o colores.
// Las variables FUERA de draw() persisten entre frames
let x = 0;
let velocidad = 2;
function draw() {
background(20); // limpiar cada frame
fill(255);
circle(x, height / 2, 40);
x += velocidad; // mover a la derecha
// Rebotar en los bordes
if (x > width || x < 0) {
velocidad *= -1; // invertir dirección
}
}
// sin() y cos() → movimiento ondulatorio / circular
let angulo = 0;
function draw() {
background(20);
// Movimiento senoidal → sube y baja suavemente
let y = height / 2 + sin(angulo) * 80;
circle(width / 2, y, 40);
angulo += 0.05; // velocidad de la onda
}
// Otras funciones útiles
random(0, 100); // número aleatorio entre 0 y 100
noise(x); // Perlin noise — más suave que random()
map(valor, min1, max1, min2, max2); // reasignar rangos
lerp(a, b, t); // interpolación lineal (t entre 0 y 1)
dist(x1, y1, x2, y2); // distancia entre dos puntos
constrain(val, min, max); // limitar un valor a un rango
function draw() {
// En vez de limpiar el fondo completamente,
// dibujamos un rectángulo semitransparente encima.
// Los frames anteriores se desvanecen gradualmente.
fill(20, 20, 20, 25); // RGBA: último número = alpha
noStroke();
rect(0, 0, width, height);
// Ahora dibujas tu forma — deja una estela
fill(255, 100, 100);
circle(mouseX, mouseY, 20);
}
p5.js expone variables globales y funciones callback que se actualizan automáticamente con la entrada del usuario.
| Variable / Función | Descripción |
|---|---|
mouseX / mouseY | Posición actual del cursor en el canvas |
pmouseX / pmouseY | Posición del cursor en el frame anterior |
mouseIsPressed | true mientras se mantiene el botón presionado |
mouseButton | LEFT, RIGHT o CENTER |
mousePressed() | Callback: se llama al hacer clic |
mouseReleased() | Callback: al soltar el botón |
mouseMoved() | Callback: al mover el cursor sin clic |
mouseDragged() | Callback: al mover con botón presionado |
keyIsPressed | true mientras se mantiene una tecla |
key | La tecla presionada (ej: 'a', 'Enter') |
keyCode | Código numérico (ENTER, BACKSPACE, UP_ARROW…) |
keyPressed() | Callback: al presionar una tecla |
let historial = []; // guardar posiciones anteriores
function draw() {
background(20, 20, 20, 30); // estela suave
// mouseIsPressed → dibuja solo mientras se hace clic
if (mouseIsPressed) {
historial.push({ x: mouseX, y: mouseY });
if (historial.length > 60) historial.shift();
}
// Dibujar el historial con tamaño decreciente
historial.forEach((p, i) => {
let t = i / historial.length;
fill(lerpColor(color(237, 34, 93), color(255, 200, 50), t));
noStroke();
circle(p.x, p.y, t * 20);
});
}
function keyPressed() {
if (key === 'c') historial = []; // limpiar con la tecla C
}
function draw() {
background(20);
translate(width / 2, height / 2); // mover el origen al centro
for (let i = 0; i < 8; i++) {
rotate(TWO_PI / 8); // rotar 45° en cada iteración
fill(i * 40, 180, 200);
rect(40, 0, 60, 20);
}
}
// push() y pop() guardan y restauran el estado de transformación
function draw() {
push(); // guardar estado
translate(100, 100);
rotate(frameCount * 0.02);
rect(-25, -25, 50, 50);
pop(); // restaurar estado
}
Este proyecto combina todos los conceptos de la semana: coordenadas, formas, color HSB, animación con sin() y noise(), interactividad con el ratón y transformaciones.
Conceptos aplicados: colorMode(HSB) · noise() para movimiento orgánico · map() para convertir rangos · mouseX/mouseY · push()/pop() para transformaciones independientes · efecto de estela con fill() semitransparente · frameCount como motor de tiempo
Generativo interactivo — mueve el cursor para influir en el campo de fuerza
let particulas = [];
let velocidad = 4;
let pausa = false;
function setup() {
createCanvas(600, 360);
colorMode(HSB, 360, 100, 100, 100);
for (let i = 0; i < 80; i++) {
particulas.push(new Particula());
}
}
function draw() {
if (pausa) return;
// Estela: fondo semitransparente
fill(240, 20, 12, 18);
noStroke();
rect(0, 0, width, height);
particulas.forEach(p => {
p.actualizar();
p.dibujar();
});
}
class Particula {
constructor() { this.reset(); }
reset() {
this.x = random(width);
this.y = random(height);
this.matiz = random(360);
this.offset = random(1000); // semilla de noise única
this.vida = random(100, 255);
}
actualizar() {
// Perlin noise para dirección orgánica
let angulo = noise(
this.x * 0.003,
this.y * 0.003,
frameCount * 0.005
) * TWO_PI * 3;
// Influencia del ratón
let d = dist(this.x, this.y, mouseX, mouseY);
if (d < 80) angulo += (PI / d) * 10;
this.x += cos(angulo) * velocidad;
this.y += sin(angulo) * velocidad;
this.vida -= 0.5;
if (this.vida <= 0 || this.x < 0 || this.x > width
|| this.y < 0 || this.y > height) {
this.reset();
}
}
dibujar() {
fill(this.matiz, 80, 95, this.vida / 2);
noStroke();
circle(this.x, this.y, 4);
}
}