Tabla de contenido
Una vez más aquí. Hoy estoy aquí para dejar un artículo relacionado con el uso de la API requestAnimationFrame con JavaScript, que nos permitirá echar un ojo al mundo de la programación «creativa» y crear una malla interactiva de ondas. En las siguientes líneas vamos a explorar un proyecto realizado con esta API, el cual demuestra algunas cosas fascinantes que se pueden crear con ella.
En el corazón de esta experiencia se encuentra el lenguaje de programación JavaScript. Este lenguaje, ampliamente conocido por su versatilidad y capacidad para interactuar con páginas web, se convierte en una herramienta poderosa cuando se trata de animaciones y gráficos interactivos.
El código de la malla interactiva de ondas
Este proyecto se basa en un código JavaScript que se encarga de generar y manipular una malla de partículas que forman la base de la visualización. Utiliza la API requestAnimationFrame
para asegurar una animación fluida y eficiente, proporcionando una experiencia envolvente para el usuario.
El código de este ejemplo se estructura en varias secciones que cumplen funciones específicas. Primero, se configuran variables esenciales como el número de cuadrículas por fotograma, el ancho y alto del lienzo, y parámetros de movimiento y velocidad. Luego, se define una clase Part
que representa cada partícula en la malla. Cada partícula tiene propiedades como posición, velocidad y referencias a su índice en la cuadrícula.
Todo esto se representa en un archivo HTML, el cual es acompañado por algunas reglas CSS para que todo se vea como debe. A continuación vamos a ver el códigio que compone este ejemplo. Este código se divide en tres archivos, el HTML, otro CSS y el que crea todo el entramado, el archivo JS.
Archivo index.html
Dentro de este archivo será donde se creará el canvas en el que se va a dibujar la malla que se va a representar. Además añadiremos también un texto que se mostrará en el centro de la pantalla.
<!DOCTYPE html> <html lang="es" > <head> <meta charset="UTF-8"> <title>Malla Temporal</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/modernizr/2.8.3/modernizr.min.js" type="text/javascript"></script> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css"> <link rel="stylesheet" href="./style.css"> </head> <body> <canvas id="canv" width="2265" height="1465"></canvas> <div class="centered-text" id="texto">entreunosyceros.net</div> <!-- Hacer clic y arrastrar, hacer clic y mantener pulsado o hacer clic, mantener pulsado y después arrastrar --> <script src="./script.js"></script> </body> </html>
Archivo style.css
En este archivo colocaremos las pocas reglas CSS necesarias para visualizar correctamente esta malla interactiva de ondas.
body { width: 100%; overflow: hidden; cursor:move; } /* Texto */ .centered-text { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 48px; font-weight: bold; color: white; text-shadow: 2px 2px 10px rgba(0, 0, 0, 0.8); z-index: 1; opacity: 0.3; }
Archivo script.js
El archivo script.js
contiene el código esencial para el funcionamiento de la visualización. Utiliza la API requestAnimationFrame
para lograr una animación fluida y eficiente. El código se encarga de calcular y actualizar la posición y velocidad de una serie de partículas que forman una malla.
Las partículas interactúan entre sí y responden a la posición del ratón, lo que permite a los usuarios manipular el comportamiento de la malla. Además, se incorpora una función de onda que crea patrones dinámicos en tiempo real.
// Establece una función para solicitar animación de fotograma window.requestAnimFrame = (function(callback) { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60); }; })(); // Definición de variables var _x = 2265; // Ancho x (ancho del lienzo) var _y = 1465; // Alto y (alto del lienzo) var w = _x / gnum; // Ancho de cuadrícula var h = _y / gnum; // Alto de cuadrícula var gnum = 95; // Número de cuadrículas por fotograma var $; // Contexto del lienzo var parts; // Partículas var frm = 0; // Valor de fotograma var msX = 0; // Coordenada x del ratón var msY = 0; // Coordenada y del ratón var msdn = false; // Bandera de clic del ratón var P1 = 0.0005; // Punto uno var P2 = 0.01; // Punto dos var ŭ = 0; // Actualización de color var n = 0.98; // Valor n para más adelante var n_vel = 0.03; // Velocidad n // Definición de la clase Part var Part = function() { this.x = 0; // Posición x this.y = 0; // Posición y this.ind_x = 0; // Índice en x this.ind_y = 0; // Índice en y this.vx = 0; // Velocidad en x this.vy = 0; // Velocidad en y }; // Función de fotograma para las partículas Part.prototype.frame = function() { // Si está en el borde de la cuadrícula, no hace nada if (this.ind_x == 0 || this.ind_x == gnum - 1 || this.ind_y == 0 || this.ind_y == gnum - 1) { return; } var ax = 0; // Ángulo en x var ay = 0; // Ángulo en y // off_dx, off_dy = distancia de desplazamiento x, y var off_dx = this.ind_x * w - this.x; var off_dy = this.ind_y * h - this.y; ax = P1 * off_dx; ay = P1 * off_dy; ax -= P2 * (this.x - parts[this.ind_x - 1][this.ind_y].x); ay -= P2 * (this.y - parts[this.ind_x - 1][this.ind_y].y); ax -= P2 * (this.x - parts[this.ind_x + 1][this.ind_y].x); ay -= P2 * (this.y - parts[this.ind_x + 1][this.ind_y].y); ax -= P2 * (this.x - parts[this.ind_x][this.ind_y - 1].x); ay -= P2 * (this.y - parts[this.ind_x][this.ind_y - 1].y); ax -= P2 * (this.x - parts[this.ind_x][this.ind_y + 1].x); ay -= P2 * (this.y - parts[this.ind_x][this.ind_y + 1].y); this.vx += (ax - this.vx * n_vel); this.vy += (ay - this.vy * n_vel); this.x += this.vx * n; this.y += this.vy * n; if (msdn) { var dx = this.x - msX; var dy = this.y - msY; var ɋ = Math.sqrt(dx * dx + dy * dy); if (ɋ < 50) { ɋ = ɋ < 10 ? 10 : ɋ; this.x -= dx / ɋ * 5; this.y -= dy / ɋ * 5; } } }; // Función para inicializar las partículas function go() { parts = []; // Array de partículas for (var i = 0; i < gnum; i++) { parts.push([]); for (var j = 0; j < gnum; j++) { var p = new Part(); p.ind_x = i; p.ind_y = j; p.x = i * w; p.y = j * h; parts[i][j] = p; } } } // Función para dibujar la cuadrícula function draw() { ŭ += 0.4; // Incrementa el valor de ŭ para cambiar los colores más rápido $.strokeStyle = "hsla(" + (ŭ % 360) + ",100%,50%,1)"; $.beginPath(); for (var i = 0; i < gnum - 1; i += 1) { for (var j = 0; j < gnum - 1; j += 1) { var p1 = parts[i][j]; var p2 = parts[i][j + 1]; var p3 = parts[i + 1][j + 1]; var p4 = parts[i + 1][j]; draw_each(p1, p2, p3, p4); } } $.stroke(); } // Función para mover las partículas function mv_part() { for (var i = 0; i < gnum; i++) { for (var j = 0; j < gnum; j++) { var p = parts[i][j]; p.frame(); } } } // Función para dibujar cada elemento en el array function draw_each(p1, p2, p3, p4) { $.moveTo(p1.x, p1.y); $.lineTo(p2.x, p2.y); $.moveTo(p1.x, p1.y); $.lineTo(p4.x, p4.y); if (p1.ind_x == gnum - 2) { $.moveTo(p3.x, p3.y); $.lineTo(p4.x, p4.y); } if (p1.ind_y == gnum - 2) { $.moveTo(p3.x, p3.y); $.lineTo(p2.x, p2.y); } } // Función para llamar a las funciones y ejecutar la animación function calls() { $.fillStyle = "hsla(0, 5%, 5%, .1)"; $.fillRect(0, 0, _x, _y); mv_part(); draw(); frm++; } // Función para crear el efecto de onda function wave(x, y) { var wv = Math.sin(x / wv * xw); var wave = Math.sin(0.2 * w * frm + y * yw) * w; return wave; } // Inicialización del lienzo y configuración de eventos del ratón var c = document.getElementById('canv'); var $ = c.getContext('2d'); $.fillStyle = "hsla(0, 5%, 5%, .1)"; $.fillRect(0, 0, _x, _y); function resize() { if (c.width < window.innerWidth) { c.width = window.innerWidth; } if (c.height < window.innerHeight) { c.height = window.innerHeight; } } // Llamada a la función go al cargar la página window.requestAnimFrame(go); // Event listeners para el ratón document.addEventListener('mousemove', MSMV, false); document.addEventListener('mousedown', MSDN, false); document.addEventListener('mouseup', MSUP, false); // Se activa cuando se presiona el botón del ratón function MSDN(e) { msdn = true; } // Se activa cuando se mueve el ratón. Calcula la posición del ratón dentro del lienzo (msX y msY) en relación con el área del lienzo y el evento del ratón (e) function MSMV(e) { var rect = e.target.getBoundingClientRect(); msX = e.clientX - rect.left; msY = e.clientY - rect.top; } // Se activa cuando se libera el botón del ratón function MSUP(e) { msdn = false; } // Función para ejecutar la animación window.onload = function() { run(); function run() { window.requestAnimFrame(calls); window.requestAnimFrame(run, 33); } resize(); };
Características generales
Lo que hace de este ejemplo algo especial es su nivel de interactividad. Los usuarios tienen la capacidad de influir directamente en el comportamiento de las partículas a través de su interacción con el ratón. Al hacer clic y arrastrar, pueden observar cómo las partículas reaccionan y se desplazan en consecuencia. Esto brinda una sensación de control directo sobre la experiencia visual, convirtiendo al espectador en parte integral de la obra de arte en constante evolución.
Una característica destacada de este proyecto es la capacidad de aplicar fuerzas adicionales a las partículas. Mantener presionado el botón del ratón introduce una fuerza adicional en el sistema, permitiendo una mayor manipulación y creación de patrones visuales únicos. Este juego de fuerzas crea una experiencia dinámica y cautivadora que va más allá de una simple observación pasiva.
Otro componente clave de esta visualización es la introducción de una función de onda. Esto agrega un elemento adicional de dinamismo a la experiencia. Las ondas viajan a través de la malla, creando patrones cambiantes y fluidos que aportan una sensación de movimiento y vida a la escena.
Este proyecto no solo ofrece una experiencia visual interesante, sino que también invita a los usuarios a explorar su propia creatividad. La combinación de interactividad y opciones de personalización vía código, permite a los usuarios experimentar y crear sus propios patrones visuales únicos.
Para terminar, solo queda decir que este ejemplo también se puede descargar desde el repositorio en GitHub en el que lo he alojado. Además este proyecto se puede probar en el siguiente enlace.