Tabla de contenido
Una vez más aquí. Hoy vengo a dejar un pequeño artículo en el que vamos a ver cómo se puede crear un «bonico» efecto de lluvia de palabras desordenadas utilizando la librería Matter.js. Este efecto lo he visto en algunas páginas web desde hace ya algún tiempo, y siempre me había llamado la atención.
Como hace más o menos poco tiempo un «usuario» me ha preguntado con qué plugin podría añadir este efecto a Wordpress, y la verdad es que no supe contestarle. Pues me he puesto a buscar un poco por internet. En esta búsqueda me encontré con la librería Matter.js. Y bueno, con ella, descubrí que se puede crear un efecto como el que este usuario buscaba.
Hoy en día, en el mundo del desarrollo de software, la creatividad y la innovación se combinan buscando crear soluciones que impacten en nuestra forma de interactuar con la tecnología. Por eso, y para eso, vamos a ver este fascinante efecto conocido como «Lluvia de Palabras». El cual combina la programación web con la física, para ofrecer al usuario una experiencia como mínimo curiosa.
La «Lluvia de Palabras» que vamos a ver a continuación, es una aplicación interactiva que despliega un texto en la pantalla, dividiéndolo en palabras y aplicando ciertos efectos para que podamos moverlas y tirarlas por toda la pantalla del navegador. Cada palabra se manipula individualmente, ofreciendo una experiencia visual diferente. Si alguna de las palabras clave definidas se encuentran en el texto, estas se destacan de manera especial.
Un vistazo rápido al código de la lluvia de palabras

El código que impulsa esta aplicación está compuesto por dos funciones principales: splitWords
y renderCanvas
. A continuación, vamos a echar un vistazo a cada una de ellas para entender cómo funcionan.
const splitWords = () => { try { const textNode = document.querySelector(".text"); const text = textNode.textContent; const newDomElements = text.split(" ").map((text) => { const highlighted = text.startsWith(`código`) || text.startsWith(`artículos`) || text.startsWith(`entreunosyceros`); return `<span class="word ${highlighted ? "highlighted" : null}">${text}</span>`; }); textNode.innerHTML = newDomElements.join(""); } catch (error) { console.error("Error en splitWords:", error); } };
La función splitWords
se encarga de dividir el texto contenido en un elemento HTML con la clase «text» en palabras individuales. Luego, cada palabra se envuelve en un elemento <span>
para permitir su manipulación individual. Si la palabra comienza con ciertos términos clave, se resalta con una clase especial.
Función renderCanvas
const renderCanvas = () => { try { // ... (código Matter.js para la simulación física) // Importamos los objetos Matter necesarios const Engine = Matter.Engine; const Render = Matter.Render; const World = Matter.World; const Bodies = Matter.Bodies; const Runner = Matter.Runner; // Definimos parámetros y tamaño del lienzo const params = { isStatic: true, render: { fillStyle: "transparent" } }; const canvasSize = { width: window.innerWidth, height: window.innerHeight }; // Crear un motor y un renderizador const engine = Engine.create({}); const render = Render.create({ element: document.body, engine: engine, options: { ...canvasSize, background: "transparent", wireframes: false } }); // Crear cuerpos físicos (piso, paredes, etc.) const floor = Bodies.rectangle( canvasSize.width / 2, canvasSize.height, canvasSize.width, 50, params ); const wall1 = Bodies.rectangle( 0, canvasSize.height / 2, 50, canvasSize.height, params ); const wall2 = Bodies.rectangle( canvasSize.width, canvasSize.height / 2, 50, canvasSize.height, params ); const top = Bodies.rectangle( canvasSize.width / 2, 0, canvasSize.width, 50, params ); const wordElements = document.querySelectorAll(".word"); const wordBodies = [...wordElements].map((elemRef) => { const width = elemRef.offsetWidth; const height = elemRef.offsetHeight; return { body: Matter.Bodies.rectangle(canvasSize.width / 2, 0, width, height, { render: { fillStyle: "transparent" } }), elem: elemRef, render() { const { x, y } = this.body.position; this.elem.style.top = `${y - 20}px`; this.elem.style.left = `${x - width / 2}px`; this.elem.style.transform = `rotate(${this.body.angle}rad)`; } }; }); // Obtener elementos de palabras y crear cuerpos físicos asociados const mouse = Matter.Mouse.create(document.body); const mouseConstraint = Matter.MouseConstraint.create(engine, { mouse, constraint: { stiffness: 0.2, render: { visible: false } } }); mouse.element.removeEventListener("mousewheel", mouse.mousewheel); mouse.element.removeEventListener("DOMMouseScroll", mouse.mousewheel); // Añadir cuerpos a World y ejecutar el motor de simulación World.add(engine.world, [ floor, ...wordBodies.map((box) => box.body), wall1, wall2, top, mouseConstraint ]); render.mouse = mouse; Runner.run(engine); Render.run(render); // Función de animación para renderizar los cuerpos (function rerender() { try { wordBodies.forEach((element) => { element.render(); }); Matter.Engine.update(engine); requestAnimationFrame(rerender); } catch (error) { console.error("Error en rerender:", error); } })(); } catch (error) { console.error("Error en renderCanvas:", error); } };
La función renderCanvas
utiliza la biblioteca Matter.js para crear una simulación física que interactúa con las palabras. Define un lienzo físico donde las palabras y otros elementos interactúan. Para lograr esto, utiliza conceptos como motores, renderizadores y cuerpos físicos que interactúan entre sí.
El código de la aplicación
El código de la aplicación es el siguiente. Hay que tener en cuenta que en este código falta la librería Matter.js. El archivo de esta librería se puede descargar desde el repositorio en GitHub que se indica al final del artículo.
Index.html
En el archivo index vamos a definir el texto que queremos que se muestre en pantalla. Además se va a mostrar un título generado por CSS.
<!DOCTYPE html> <html lang="es" > <head> <meta charset="UTF-8"> <title>Lluvia de palabras</title> <link rel="stylesheet" href="./style.css"> </head> <body> <main> <a href="https://entreunosyceros.net/about/" title="about entreunosyceros" target="_blank"><div class="titulo" aria-label="Hecho en entreunosyceros.net"></div></a> <!--Texto--> <div class="text">Desarrollador de software con más de 10 años de experiencia, he desarrollado una base sólida en la creación de soluciones tecnológicas innovadoras y eficientes. Mi pasión por la tecnología y el emprendimiento me llevó crear entreunosyceros, donde actualmente escribo artículos. Estoy constantemente impulsado a superar los límites y generar un impacto positivo en la industria. Cuando no estoy creando código, disfruto explorando mi lado creativo con la horticultura, la cocina, la música y la naturaleza.</div> </main> <!--scripts--> <script src="./matter.min.js"></script> <script src="./script.js"></script> </body> </html>
Style.css
Con estas reglas CSS que vamos a ver a continuación, daremos aspecto y colores a las palabras y al fondo de pantalla. Al igual que a las palabras destacadas y al título.
body { margin: 0; padding: 0; background: #f1f1f1; color: transparent; font-family: 'Times New Roman', 'Courier New', Courier, monospace; width: 100vw; height: 100vh; overflow: hidden; } * { user-select: none; } .word { position: absolute; cursor: grab; font-size: 30px; color: #3b5fff; } .word.highlighted { font-weight: bold; color: rgb(0, 32, 11); } a { text-decoration: none; color: black; display: block; padding: 1rem; } /*Título*/ .titulo { box-sizing: border-box; padding: 10px; border: none; font: normal 38px / normal "Warnes", Helvetica, sans-serif; color: rgba(255, 255, 255, 1); text-align: center; white-space: pre; text-shadow: 0 0 10px rgb(0, 17, 255), 0 0 20px rgb(76, 0, 255), 0 0 30px rgb(55, 0, 255), 0 0 40px #727272, 0 0 70px #727272, 0 0 80px #4e4e4e, 0 0 100px #2b2b2b; -webkit-transition: all 200ms cubic-bezier(0.42, 0, 0.58, 1); -moz-transition: all 200ms cubic-bezier(0.42, 0, 0.58, 1); -o-transition: all 200ms cubic-bezier(0.42, 0, 0.58, 1); transition: all 200ms cubic-bezier(0.42, 0, 0.58, 1); width: 100%; margin-top: 10px; margin-bottom: 10px; } /* Estilo para el título cuando se le hace hover */ .titulo:hover { text-shadow: 0 0 10px rgb(73, 73, 73), 0 0 20px rgb(56, 56, 56), 0 0 30px rgb(48, 48, 48), 0 0 40px #5c5c5c, 0 0 70px #000000, 0 0 80px #000000, 0 0 100px #000000; } /* Texto del título */ .titulo::after { content: "Lluvia de palabras"; }
Script.js

En este archivo .js será donde ocurrirá toda la magia y en donde trabajaremos con la librería Matter.js. También será el lugar en el que indicaremos las palabras destacadas, las cuales tendrán un aspecto diferente, en caso de que se encuentren en el texto que indicamos en el archivo .html.
// Esta función toma el texto de un elemento con clase "text", // divide el texto en palabras, y envuelve cada palabra en un elemento <span>. // Si una palabra comienza con ciertos términos, la resalta con una clase "highlighted". const splitWords = () => { try { // Obtener el nodo de texto con la clase "text" const textNode = document.querySelector(".text"); const text = textNode.textContent; // Crear un nuevo array de elementos DOM a partir del texto const newDomElements = text.split(" ").map((text) => { const highlighted = text.startsWith(`código`) || text.startsWith(`artículos`) || text.startsWith(`entreunosyceros`); return `<span class="word ${highlighted ? "highlighted" : null }">${text}</span>`; }); // Reemplazar el contenido del nodo de texto con los nuevos elementos textNode.innerHTML = newDomElements.join(""); } catch (error) { console.error("Error en splitWords:", error); } }; // Esta función inicializa un motor de física (Matter.js) y crea un entorno de simulación. const renderCanvas = () => { try { // Importar objetos Matter necesarios const Engine = Matter.Engine; const Render = Matter.Render; const World = Matter.World; const Bodies = Matter.Bodies; const Runner = Matter.Runner; // Definir parámetros y tamaño del lienzo const params = { isStatic: true, render: { fillStyle: "transparent" } }; const canvasSize = { width: window.innerWidth, height: window.innerHeight }; // Crear un motor y un renderizador const engine = Engine.create({}); const render = Render.create({ element: document.body, engine: engine, options: { ...canvasSize, background: "transparent", wireframes: false } }); // Crear cuerpos físicos (piso, paredes, etc.) const floor = Bodies.rectangle( canvasSize.width / 2, canvasSize.height, canvasSize.width, 50, params ); const wall1 = Bodies.rectangle( 0, canvasSize.height / 2, 50, canvasSize.height, params ); const wall2 = Bodies.rectangle( canvasSize.width, canvasSize.height / 2, 50, canvasSize.height, params ); const top = Bodies.rectangle( canvasSize.width / 2, 0, canvasSize.width, 50, params ); const wordElements = document.querySelectorAll(".word"); const wordBodies = [...wordElements].map((elemRef) => { const width = elemRef.offsetWidth; const height = elemRef.offsetHeight; return { body: Matter.Bodies.rectangle(canvasSize.width / 2, 0, width, height, { render: { fillStyle: "transparent" } }), elem: elemRef, render() { const { x, y } = this.body.position; this.elem.style.top = `${y - 20}px`; this.elem.style.left = `${x - width / 2}px`; this.elem.style.transform = `rotate(${this.body.angle}rad)`; } }; }); // Obtener elementos de palabras y crear cuerpos físicos asociados const mouse = Matter.Mouse.create(document.body); const mouseConstraint = Matter.MouseConstraint.create(engine, { mouse, constraint: { stiffness: 0.2, render: { visible: false } } }); mouse.element.removeEventListener("mousewheel", mouse.mousewheel); mouse.element.removeEventListener("DOMMouseScroll", mouse.mousewheel); // Añadir cuerpos a World y ejecutar el motor de simulación World.add(engine.world, [ floor, ...wordBodies.map((box) => box.body), wall1, wall2, top, mouseConstraint ]); render.mouse = mouse; Runner.run(engine); Render.run(render); // Función de animación para renderizar los cuerpos (function rerender() { try { wordBodies.forEach((element) => { element.render(); }); Matter.Engine.update(engine); requestAnimationFrame(rerender); } catch (error) { console.error("Error en rerender:", error); } })(); } catch (error) { console.error("Error en renderCanvas:", error); } }; // Este manejador de eventos recarga la página cuando se detecta un cambio en el tamaño de la ventana. const resizeHandler = () => { try { location.reload(); } catch (error) { console.error("Error en resizeHandler:", error); } }; // Cuando el contenido de la ventana se haya cargado completamente, // ejecuta las funciones splitWords() y renderCanvas(), // y agrega un escucha de eventos para cambios en el tamaño de la ventana. window.addEventListener("DOMContentLoaded", (event) => { try { splitWords(); renderCanvas(); window.addEventListener("resize", resizeHandler); } catch (error) { console.error("Error en DOMContentLoaded:", error); } });
La «Lluvia de Palabras» es un ejemplo un poco diferente, en el que se puede ver cómo la creatividad y la programación se unen para crear experiencias únicas. Al entender el código detrás de esta aplicación, podemos apreciar cómo la física y la programación se combinan para ofrecer una experiencia visual cautivadora.
El código de la aplicación, junto con la librería Matter.js, necesaria para que esto funcione, se puede descargar desde el repositorio en GitHub en el que he alojado el proyecto.
Para terminar, solo me queda decir que esto así tal cual no se puede publicar en Wordpress, pero no será muy complicado adaptarlo para mostrarlo en una página. Y así envitar tener que añadir más plugins a nuestra instalación de Wordpress.
2 Comentarios
Muchas Felicidades, Excelente
:)