More Articles
RSS

Mar 2023

90s abstract shapes

Here I create some generative aesthetic art by randomly placing shapes to look like something from Saved By The Bell.

<html>
  <head>
    <meta charset="utf-8">
    <style media="screen">
      * { box-sizing: border-box; }
      html, body { height: 100%; margin: 0; overflow: hidden;  }
      #root { width: 100%; height: 100%; overflow: hidden; position: relative; }
      #root { background-color: rgb(0,95,255) }
      button { position: absolute; left: 30px; bottom: 30px; z-index: 1; }
      .shape { transform-origin: center; position: absolute; filter: drop-shadow(3px 3px 0px #000); }
    </style>
    <script type="text/javascript">
      const createNode = (options) => {
        const node = document.createElement(options.tag);
        if(options.className) node.setAttribute('class',options.className);
        if(options.innerHTML) node.innerHTML = options.innerHTML;
        if(options.attributes) Object.keys(options.attributes).forEach(key => node.setAttribute(key,options.attributes[key]) );
        if(options.style) Object.keys(options.style).forEach(key => node.style[key] = options.style[key]);
        if(options.event_listeners) Object.keys(options.event_listeners).forEach(key => node.addEventListener(key,options.event_listeners[key]) )
        options.root.appendChild(node);
        return node;
      }
      class App {
        constructor(){
          document.addEventListener('DOMContentLoaded',this.handleReady.bind(this));
        }
        handleReady(){
          this.els = {};
          this.els.root = document.getElementById('root');
          this.els.root.innerHTML = ``;
          this.els.redraw = createNode({ root: this.els.root, tag: `button`, innerHTML: `Redraw`, event_listeners: { click: () => {
            this.handleReady();
          } } })
          this.reserved = [];
          this.width = window.innerWidth;
          this.height = window.innerHeight;
          this.els
          this.shapes = {
            circle: {
              svg: `<svg width="16" height="16" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M7.5 0a7.5 7.5 0 1 0 0 15 7.5 7.5 0 0 0 0-15Z" /></svg>`,
              amount: 3
            },
            wavy_line: {
              svg: `<svg width="16" height="16" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M27 23c-2.589 0-4.005-2.549-5.374-5.014C20.537 16.026 19.411 14 18 14c-1.412 0-2.537 2.026-3.626 3.986C13.004 20.451 11.588 23 9 23c-2.65 0-3.853-2.706-4.914-5.094C3.038 15.546 2.256 14 1 14a1 1 0 0 1 0-2c2.65 0 3.853 2.706 4.914 5.094C6.962 19.453 7.744 21 9 21c1.412 0 2.537-2.026 3.626-3.986C13.996 14.549 15.412 12 18 12c2.589 0 4.005 2.549 5.374 5.014C24.463 18.974 25.589 21 27 21c1.256 0 2.037-1.547 3.086-3.906C31.147 14.706 32.351 12 35 12a1 1 0 1 1 0 2c-1.256 0-2.037 1.546-3.086 3.906C30.853 20.294 29.649 23 27 23z"/></svg>`,
              amount: 2
            },
            triangle: {
              svg: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 120 120"><path fill="currentColor" d="M.233 106.52 60 3l59.768 103.52z"/></svg>`,
              amount: 4
            },
            macaroni: {
              svg: `<svg width="16" height="16" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M16 4a8 8 0 1 0 0 16v-3a5 5 0 0 1 0-10V4Z" /></svg>`,
              amount: 2
            },
            dots: {
              svg: `<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M3.5 2.25a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0zm11.5 0a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0zm-1.25 7a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5zm1.25 4.5a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0zM2.25 9.25a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5zm7-1.25a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0zM8 15a1.25 1.25 0 1 0 0-2.5A1.25 1.25 0 0 0 8 15zM9.25 2.25a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0zM2.25 15a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5z" /></svg>`,
              amount: 2
            },
          };
          for(const key of Object.keys(this.shapes)){
            for(let i=0; i<this.shapes[key].amount; i++){
              this.createShape(this.shapes[key].svg);
            }
          }
        }
        createShape(svg){
          let left = Math.floor(Math.random() * this.width);
          let top = Math.floor(Math.random() * this.height);
          let scale = Math.floor(Math.random() * 10);
          const rotate = Math.floor(Math.random() * 360);
          const r = Math.floor(Math.random() * 255);
          const g = Math.floor(Math.random() * 255);
          const b = Math.floor(Math.random() * 255);
          const el_shape = createNode({ root: this.els.root, tag: 'div', className: `shape`, style: { left: `${left}px`, top: `${top}px` } });
          const el_svg = createNode({ root: el_shape, tag: 'div', className: `svg`, innerHTML: svg, style: { color: `rgb(${r},${g},${b})`, transform: `scale(${scale}) rotate(${rotate}deg)` } });
          this.reserved.push({ left: left, top: top, size: scale * 16 });
        }
      }
      const app = new App();
    </script>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

In this example, I found some random SVG paths around the internet for use here. I put mine in a shapes object as a string along with how many I want to render.

I then loop through them and use the random function heavily. Math.random generates a random decimal number between 0 and 1. By multiplying by a max amount I can get a number between 0 and the max. I use two container tags to store the shape and the SVG elements in order to apply a drop-shadow that isn't affected by the rotation.