Design & Graphics

SVG BLOB & WAVE

Generate smooth organic SVG blobs and multi-layered section waves for websites with live gradient controls and instant code export.

Complexity / Points 6
Randomness / Variance 40%
Smoothness / Curves 70%
Gradient Color 1 & 2
#ff2200
#ff7700
Gradient Angle 135°

        
Developer Reference

Core Algorithm & Standalone Script

Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.

let mode = 'blob';
    let seedPoints = [];
    let currentSvgCode = '';

    const elModeBlob = document.getElementById('btn-mode-blob');
    const elModeWave = document.getElementById('btn-mode-wave');
    const elPoints = document.getElementById('input-points');
    const elRandom = document.getElementById('input-random');
    const elSmooth = document.getElementById('input-smooth');
    const elLayers = document.getElementById('input-layers');
    const elWaveGroup = document.getElementById('group-wave-layers');
    const elC1 = document.getElementById('input-c1');
    const elC2 = document.getElementById('input-c2');
    const elAngle = document.getElementById('input-angle');
    const elAnimate = document.getElementById('input-animate');
    const elShuffle = document.getElementById('btn-shuffle');
    const stage = document.getElementById('preview-stage');
    const codeOut = document.getElementById('code-output');
    const toast = document.getElementById('toast');

    function showToast(msg = 'Copied to clipboard!') {
      toast.textContent = msg;
      toast.classList.add('show');
      setTimeout(() => toast.classList.remove('show'), 2000);
    }

    function generateSeedPoints(numPoints, randomness) {
      seedPoints = [];
      const center = 200;
      const baseRadius = 130;
      const step = (Math.PI * 2) / numPoints;
      const variance = (baseRadius * (randomness / 100));

      for (let i = 0; i < numPoints; i++) {
        const angle = i * step;
        const r = baseRadius + (Math.random() * variance * 2 - variance);
        const x = center + r * Math.cos(angle);
        const y = center + r * Math.sin(angle);
        seedPoints.push({ x, y });
      }
    }

    function pointsToSmoothPath(pts, smoothnessRatio) {
      if (pts.length < 3) return '';
      const len = pts.length;
      let path = `M ${pts[0].x.toFixed(1)},${pts[0].y.toFixed(1)}`;

      for (let i = 0; i < len; i++) {
        const p0 = pts[(i - 1 + len) % len];
        const p1 = pts[i];
        const p2 = pts[(i + 1) % len];
        const p3 = pts[(i + 2) % len];

        const tension = 0.25 * (smoothnessRatio / 100);

        const cp1x = p1.x + (p2.x - p0.x) * tension;
        const cp1y = p1.y + (p2.y - p0.y) * tension;
        const cp2x = p2.x - (p3.x - p1.x) * tension;
        const cp2y = p2.y - (p3.y - p1.y) * tension;

        path += ` C ${cp1x.toFixed(1)},${cp1y.toFixed(1)} ${cp2x.toFixed(1)},${cp2y.toFixed(1)} ${p2.x.toFixed(1)},${p2.y.toFixed(1)}`;
      }
      return path + ' Z';
    }

    function generateWavePath(numPoints, randomness, layerIndex, totalLayers) {
      const width = 600;
      const height = 300;
      const step = width / (numPoints - 1);
      const baseHeight = height * 0.5 + (layerIndex * 30);
      let d = `M 0,${height} L 0,${baseHeight}`;

      for (let i = 1; i < numPoints - 1; i++) {
        const x = i * step;
        const offset = (Math.sin(i + layerIndex) * 30) + ((Math.random() - 0.5) * (randomness * 0.8));
        const y = baseHeight + offset;
        d += ` Q ${x - step / 2},${y - 15} ${x},${y}`;
      }

      d += ` Q ${width - step / 2},${baseHeight} ${width},${baseHeight} L ${width},${height} Z`;
      return d;
    }

    function render() {
      const numPts = parseInt(elPoints.value);
      const rand = parseInt(elRandom.value);
      const smooth = parseInt(elSmooth.value);
      const c1 = elC1.value;
      const c2 = elC2.value;
      const angle = parseInt(elAngle.value);
      const animate = elAnimate.checked;

      document.getElementById('val-points').textContent = numPts;
      document.getElementById('val-random').textContent = rand + '%';
      document.getElementById('val-smooth').textContent = smooth + '%';
      document.getElementById('val-angle').textContent = angle + '°';
      document.getElementById('hex-c1').textContent = c1;
      document.getElementById('hex-c2').textContent = c2;

      let svg = '';

      if (mode === 'blob') {
        if (seedPoints.length !== numPts) {
          generateSeedPoints(numPts, rand);
        }
        const pathD = pointsToSmoothPath(seedPoints, smooth);

        svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" width="360" height="360">
  <defs>
    <linearGradient id="blob-grad" x1="0%" y1="0%" x2="100%" y2="100%" gradientTransform="rotate(${angle})">
      <stop offset="0%" stop-color="${c1}" />
      <stop offset="100%" stop-color="${c2}" />
    </linearGradient>
  </defs>
  <path d="${pathD}" fill="url(#blob-grad)">
    ${animate ? `<animate attributeName="d" dur="6s" repeatCount="indefinite" values="${pathD}; ${pointsToSmoothPath(seedPoints.map(p => ({ x: p.x + Math.sin(p.y)*15, y: p.y + Math.cos(p.x)*15 })), smooth)}; ${pathD}" />` : ''}
  </path>
</svg>`;
      } else {
        const layers = parseInt(elLayers.value);
        document.getElementById('val-layers').textContent = layers;
        let pathsHtml = '';

        for (let l = 0; l < layers; l++) {
          const opacity = (1 - (l * 0.18)).toFixed(2);
          const waveD = generateWavePath(numPts, rand, l, layers);
          pathsHtml += `  <path d="${waveD}" fill="url(#wave-grad)" opacity="${opacity}" />\n`;
        }

        svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 300" width="100%" height="240">
  <defs>
    <linearGradient id="wave-grad" x1="0%" y1="0%" x2="100%" y2="100%" gradientTransform="rotate(${angle})">
      <stop offset="0%" stop-color="${c1}" />
      <stop offset="100%" stop-color="${c2}" />
    </linearGradient>
  </defs>
${pathsHtml}</svg>`;
      }

      currentSvgCode = svg;
      stage.innerHTML = svg;
      codeOut.textContent = svg;
    }

    elModeBlob.addEventListener('click', () => {
      mode = 'blob';
      elModeBlob.classList.add('active');
      elModeWave.classList.remove('active');
      elWaveGroup.style.display = 'none';
      generateSeedPoints(parseInt(elPoints.value), parseInt(elRandom.value));
      render();
    });

    elModeWave.addEventListener('click', () => {
      mode = 'wave';
      elModeWave.classList.add('active');
      elModeBlob.classList.remove('active');
      elWaveGroup.style.display = 'flex';
      render();
    });

    [elPoints, elRandom, elSmooth, elLayers, elC1, elC2, elAngle, elAnimate].forEach(input => {
      input.addEventListener('input', () => {
        if (input === elPoints || input === elRandom) {
          generateSeedPoints(parseInt(elPoints.value), parseInt(elRandom.value));
        }
        render();
      });
    });

    elShuffle.addEventListener('click', () => {
      generateSeedPoints(parseInt(elPoints.value), parseInt(elRandom.value));
      render();
    });

    document.getElementById('btn-copy-svg').addEventListener('click', () => {
      navigator.clipboard.writeText(currentSvgCode);
      showToast('SVG Code copied!');
    });

    document.getElementById('btn-copy-raw').addEventListener('click', () => {
      navigator.clipboard.writeText(currentSvgCode);
      showToast('SVG Code copied!');
    });

    document.getElementById('btn-copy-css').addEventListener('click', () => {
      const encoded = encodeURIComponent(currentSvgCode).replace(/'/g, "%27").replace(/"/g, "%22");
      const css = `background-image: url("data:image/svg+xml,${encoded}");\nbackground-repeat: no-repeat;\nbackground-size: cover;`;
      navigator.clipboard.writeText(css);
      showToast('CSS Background copied!');
    });

    document.getElementById('btn-download').addEventListener('click', () => {
      const blob = new Blob([currentSvgCode], { type: 'image/svg+xml' });
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = mode === 'blob' ? 'organic-blob.svg' : 'section-wave.svg';
      a.click();
      URL.revokeObjectURL(url);
    });

    // Init
    generateSeedPoints(6, 40);
    render();
Copied to clipboard!