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();