Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save druzn3k/7dda6f983470992208ae29cf6dc5a269 to your computer and use it in GitHub Desktop.
Save druzn3k/7dda6f983470992208ae29cf6dc5a269 to your computer and use it in GitHub Desktop.
Generative SVG Noise Pattern Maker 🎨

Generative SVG Noise Pattern Maker 🎨

I'm a huge fan of these kinds of noisy grid patterns, and I wanted to add some to my in-progress site. Rather than tinkering away in Figma, I thought I would make a little generator. I hope some other folks find it useful!

A Pen by George Francis on CodePen.

License.

<div class="generator">
<svg class="generator__canvas" viewBox="0 0 200 200">
<rect x="0" y="0" width="100%" height="100%" fill="#ffffff"></rect>
</svg>
<div class="[ stack ] [ generator__controls ]">
<!-- Pattern -->
<div class="generator__control generator__control--switch">
<p class="generator__control-label">Pattern</p>
<button class="switch-btn" aria-pressed="false" role="switch" aria-labelledby="patternLabel" id="modeSwitch">
<span class="switch-btn__marker"></span>
<span class="[ center-xy ] switch-btn__label">Lines</span>
<span class="[ center-xy ] switch-btn__label">Dots </span>
</button>
</div>
<!-- Grid size -->
<div class="generator__control generator__control--range">
<label class="generator__control-label" for="cellSizeSlider">Cell Size</label>
<input class="range-input" type="range" name="cellSize" id="cellSizeSlider" aria-labelledby="cellSizeLabel" min="0" max="3" step="1" value="0">
</div>
<!-- Variance -->
<div class="generator__control generator__control--range">
<label class="generator__control-label" for="variance">Variance</label>
<input class="range-input" type="range" name="variance" id="variance" min="0" max="1" value="0.5" step="0.1">
</div>
<!-- Color -->
<div class="generator__control generator__control--color">
<label class="generator__control-label" for="color">Color</label>
<input type="color" name="color" id="color" value="#1a1a1a">
</div>
<div class="generator__buttons">
<button class="generate">Regenerate</button>
<button class="clipboard">Copy SVG</button>
</div>
</div>
</div>
<div class="wave">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320" preserveAspectRatio="xMidYMid slice">
<path fill="#0099ff" fill-opacity="1" d="M0,192L48,197.3C96,203,192,213,288,229.3C384,245,480,267,576,250.7C672,235,768,181,864,181.3C960,181,1056,235,1152,234.7C1248,235,1344,181,1392,154.7L1440,128L1440,320L1392,320C1344,320,1248,320,1152,320C1056,320,960,320,864,320C768,320,672,320,576,320C480,320,384,320,288,320C192,320,96,320,48,320L0,320Z"></path>
</svg>
<div></div>
</div>
import { SVG } from "https://cdn.skypack.dev/@svgdotjs/svg.js";
import SimplexNoise from "https://cdn.skypack.dev/simplex-noise@2.4.0";
import { map } from "https://cdn.skypack.dev/@georgedoescode/generative-utils@1.0.0";
import debounce from "https://cdn.skypack.dev/debounce@1.2.1";
import copy from "https://cdn.skypack.dev/copy-to-clipboard@3.3.1";
const simplex = new SimplexNoise();
const width = 200;
const height = 200;
let res = 10;
let cols = width / res;
let rows = height / res;
const svg = SVG(".generator__canvas");
let mode = "LINES";
let color = "#1a1a1a";
let noiseInc = 0.05;
function generate() {
svg.clear();
let xOff = Math.random() * 1000;
for (let i = 0; i < cols; i++) {
let yOff = 0;
xOff += noiseInc;
for (let j = 0; j < rows; j++) {
const noise = simplex.noise2D(xOff, yOff);
const scale = map(noise, -1, 1, 0.125, 0.75);
const rotate = map(noise, -1, 1, 0, 360);
if (mode === "LINES") {
svg
.line(i * res, j * res, i * res + res, j * res + res)
.scale(0.25)
.stroke({
color: color,
width: 4,
linecap: "round"
})
.rotate(rotate);
} else {
svg
.circle(res)
.x(i * res)
.y(j * res)
.fill(color)
.scale(scale);
}
yOff += noiseInc;
}
}
}
generate();
document.querySelector(".generate").addEventListener("click", () => {
generate();
});
document.querySelector("#cellSizeSlider").addEventListener("input", (e) => {
const val = parseInt(e.target.value);
const options = [10, 20, 25, 40];
res = options[val];
cols = width / res;
rows = height / res;
console.log(res);
generate();
});
document.querySelector("#variance").addEventListener(
"input",
debounce((e) => {
const val = parseFloat(e.target.value);
noiseInc = map(val, 0, 1, 0.025, 0.075);
generate();
}, 10)
);
document.querySelector("#modeSwitch").addEventListener("click", (e) => {
if (e.target.getAttribute("aria-pressed") === "false") {
mode = "DOTS";
} else {
mode = "LINES";
}
generate();
});
document.querySelector("#color").addEventListener(
"input",
debounce((e) => {
color = e.target.value;
svg.node.querySelectorAll("*").forEach((el) => {
console.log(el);
if (mode === "DOTS") {
el.setAttribute("fill", color);
} else {
el.setAttribute("stroke", color);
}
});
}, 10)
);
document.querySelectorAll(".switch-btn").forEach((el) => {
el.addEventListener("click", (e) => {
const pressed = e.target.getAttribute("aria-pressed") === "true";
e.target.setAttribute("aria-pressed", String(!pressed));
});
});
document.querySelector(".clipboard").addEventListener("click", (e) => {
copy(svg.node.outerHTML);
e.target.innerHTML = "Copied to Clipboard!";
setTimeout(() => {
e.target.innerHTML = "Copy SVG";
}, 1500);
});
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--gray-dark: hsl(0, 0%, 8%);
--gray-med: hsl(0, 0%, 88%);
--gray-light: hsl(0, 0%, 97%);
}
html {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
min-height: 100vh;
display: grid;
place-items: center;
padding: 1rem;
font-family: "Poppins", sans-serif;
color: var(--gray-dark);
line-height: 1;
}
.stack {
--space: 1.5rem;
> * + * {
margin-top: var(--space);
}
}
.generator {
position: relative;
z-index: 1;
width: 100%;
max-width: 28rem;
background: #fff;
padding: 1rem;
border-radius: 1.5rem;
box-shadow: 0 2.8px 2.2px rgba(0, 0, 0, 0.006),
0 6.7px 5.3px rgba(0, 0, 0, 0.008), 0 12.5px 10px rgba(0, 0, 0, 0.01),
0 22.3px 17.9px rgba(0, 0, 0, 0.012), 0 41.8px 33.4px rgba(0, 0, 0, 0.014),
0 100px 80px rgba(0, 0, 0, 0.02);
&__canvas {
margin-bottom: 2rem;
}
&__control {
display: grid;
grid-template-columns: 112px 1fr;
align-items: center;
height: 3rem;
&-label {
font-weight: 600;
}
&--switch {
.switch-btn {
--height: 3rem;
position: relative;
display: grid;
grid-template-columns: 1fr 1fr;
align-items: center;
width: 100%;
height: var(--height);
margin: 0 auto;
border-radius: calc(var(--height) / 2);
border: 0;
background: var(--gray-light);
> * {
pointer-events: none;
}
&__label {
position: relative;
height: 100%;
}
&__marker {
position: absolute;
width: calc(50% - 0.25rem);
height: calc(100% - 0.5rem);
background: hsl(0, 0%, 100%);
left: 0.25rem;
border-radius: calc(var(--height) / 2);
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.05);
transition: transform 200ms ease-in-out;
}
&[aria-pressed="true"] {
.switch-btn__marker {
transform: translateX(100%);
}
}
}
}
&--range {
.range-input {
width: 100%;
cursor: pointer;
--thumb-size: 1.5rem;
--track-height: 0.25rem;
outline-offset: 0.25rem;
&:focus {
outline: 2px solid #f0417c;
}
&::-webkit-slider-thumb {
-webkit-appearance: none;
border: 1px solid var(--gray-med);
height: var(--thumb-size);
width: var(--thumb-size);
border-radius: 50%;
background: #ffffff;
margin-top: -10px;
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.05);
}
&::-moz-range-thumb {
border: 1px solid var(--gray-med);
height: var(--thumb-size);
width: var(--thumb-size);
border-radius: 50%;
background: #ffffff;
margin-top: -10px;
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.05);
}
&::-webkit-slider-runnable-track {
width: 100%;
height: var(--track-height);
background: var(--gray-light);
box-shadow: inset 0px 1px 3px 0px rgba(0, 0, 0, 0.05);
border-radius: 0.25rem;
margin: 0.5rem;
}
&::-moz-range-track {
width: 100%;
height: var(--track-height);
background: var(--gray-light);
box-shadow: inset 0px 1px 3px 0px rgba(0, 0, 0, 0.5);
border-radius: 0.25rem;
}
}
}
&--color {
input {
width: 100%;
border: none;
padding: 0.5rem;
height: 3rem;
cursor: pointer;
overflow: hidden;
background: var(--gray-light);
border-radius: 0.5rem;
}
}
}
&__buttons {
display: grid;
grid-template-columns: 1fr;
grid-gap: 1rem;
margin-top: 2.2rem;
button {
height: 3rem;
border: 0;
border-radius: 1.5rem;
font-weight: 600;
transition: transform 150ms ease-in-out;
transform: translate3d(0px, 0px, 0px);
&:hover {
transform: scale(1.075);
}
&:first-of-type {
background: #f0417c;
color: #fff;
}
&:nth-of-type(2) {
background: #fff;
border: 2px solid var(--gray-dark);
}
}
}
@media only screen and (min-width: 32rem) {
padding: 1.5rem;
}
@media only screen and (min-width: 62rem) {
position: relative;
display: grid;
grid-template-columns: 440px 1fr;
max-width: 62rem;
align-items: center;
padding-right: 0;
&__canvas {
grid-column: 1;
margin-bottom: 0;
max-width: 100%;
}
&__controls {
grid-column: 2;
padding: 0 3.5rem;
}
&__buttons {
grid-column: 2;
grid-template-columns: 1fr 1fr;
}
}
}
button {
font-family: inherit;
cursor: pointer;
font-size: 0.875rem;
}
label {
display: block;
}
.center-xy {
display: grid;
place-items: center;
}
input[type="range"] {
-webkit-appearance: none;
width: 100%;
background: transparent;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
}
input[type="range"]:focus {
outline: none;
}
input[type="range"]::-ms-track {
width: 100%;
cursor: pointer;
background: transparent;
border-color: transparent;
color: transparent;
}
button,
input {
outline-offset: 0.25rem;
&:focus {
outline: 2px solid #f0417c;
}
}
.wave {
position: fixed;
bottom: 0;
left: 0;
width: 100vw;
height: 75vh;
--fill: var(--gray-light);
svg {
height: 320px;
width: 100%;
transform: translateY(16px);
path {
fill: var(--fill);
}
}
div {
height: 100%;
width: 100%;
background: var(--fill);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment