codepens/generative-customisable-svg.../dist/script.js

239 lines
6.4 KiB
JavaScript

import { SVG } from "https://cdn.skypack.dev/@svgdotjs/svg.js";
import html2canvas from "https://cdn.skypack.dev/html2canvas@1.0.0-rc.7";
import ResizeObserver from "https://cdn.skypack.dev/resize-observer-polyfill@1.5.1";
import FileSaver from "https://cdn.skypack.dev/file-saver@2.0.5";
console.clear();
const socialImageSVG = document.querySelector(".social-image");
const socialImageTitle = document.querySelector(".social-image__title");
const socialImageMeta = document.querySelector(".social-image__meta");
const saveBtn = document.querySelector(".controls__btn--save");
const alignmentBtn = document.querySelector(".controls__btn--alignment");
const colorBtn = document.querySelector(".controls__btn--colors");
const shapesBtn = document.querySelector(".controls__btn--shapes");
let baseColor;
let baseColorWhite;
let baseColorBlack;
let complimentaryColor1;
let complimentaryColor2;
let shapeColors;
const alignmentOpts = ["flex-start", "flex-end", "center"];
const shapes = SVG(socialImageSVG).group();
setColors();
generate();
const resizeObserver = new ResizeObserver(() => {
generate();
});
resizeObserver.observe(socialImageTitle);
resizeObserver.observe(socialImageMeta);
function generate() {
shapes.clear();
const htmlRects = [
relativeBounds(socialImageSVG, socialImageTitle),
relativeBounds(socialImageSVG, socialImageMeta)
];
const rects = generateRandomRects(htmlRects);
for (const rect of rects.slice(2, rects.length)) {
drawRandomShape(rect);
}
}
function setColors() {
const baseHue = random(0, 360);
const saturation = random(60, 90);
baseColor = `hsl(${baseHue}, ${saturation}%, 60%)`;
baseColorWhite = `hsl(${baseHue}, ${saturation}%, 97%)`;
baseColorBlack = `hsl(${baseHue}, 95%, 3%)`;
complimentaryColor1 = `hsl(${baseHue + 90}, ${saturation}%, 60%)`;
complimentaryColor2 = `hsl(${baseHue + 180}, ${saturation}%, 60%)`;
shapeColors = [complimentaryColor1, complimentaryColor2, baseColor];
socialImageSVG.style.background = baseColorWhite;
socialImageSVG.style.color = baseColorBlack;
}
function drawRandomShape({ x, y, width, height }) {
const shapeChoices = ["rect", "ellipse", "triangle"];
let shape;
switch (shapeChoices[~~random(0, shapeChoices.length)]) {
case "ellipse":
shape = shapes.ellipse(width, height).x(x).y(y);
break;
case "triangle":
shape = shapes
.polygon(`0 ${height}, ${width / 2} 0, ${width} ${height}`)
.x(x)
.y(y);
break;
default:
shape = shapes.rect(width, height).x(x).y(y);
}
const color = randomColor();
if (random(0, 1) > 0.25) {
shape.fill(color);
} else {
shape
.stroke({
color,
width: 16
})
.fill("transparent");
}
shape.node.classList.add("shape");
shape.rotate(random(0, 90)).scale(0.825);
shape.opacity(random(0.5, 1));
}
function randomColor() {
return shapeColors[~~random(0, shapeColors.length)];
}
function randomAlignment() {
return alignmentOpts[~~random(0, alignmentOpts.length)];
}
function generateRandomRects(existing) {
const rects = [...existing];
const tries = 250;
const maxShapes = 6;
for (let i = 0; i < tries; i++) {
if (rects.length === maxShapes + existing.length) break;
const size = random(100, 600);
const rect = {
x: random(-size, 1200),
y: random(-size, 630),
width: size,
height: size
};
if (!rects.some((r) => detectRectCollision(r, rect))) {
rects.push(rect);
}
}
return rects;
}
function random(min, max) {
return Math.random() * (max - min) + min;
}
function detectRectCollision(rect1, rect2, padding = 32) {
return (
rect1.x < rect2.x + rect2.width + padding &&
rect1.x + rect1.width + padding > rect2.x &&
rect1.y < rect2.y + rect2.height + padding &&
rect1.y + rect1.height + padding > rect2.y
);
}
function relativeBounds(svg, HTMLElement) {
const { x, y, width, height } = HTMLElement.getBoundingClientRect();
const startPoint = svg.createSVGPoint();
startPoint.x = x;
startPoint.y = y;
const endPoint = svg.createSVGPoint();
endPoint.x = x + width;
endPoint.y = y + height;
const startPointTransformed = startPoint.matrixTransform(
svg.getScreenCTM().inverse()
);
const endPointTransformed = endPoint.matrixTransform(
svg.getScreenCTM().inverse()
);
return {
x: startPointTransformed.x,
y: startPointTransformed.y,
width: endPointTransformed.x - startPointTransformed.x,
height: endPointTransformed.y - startPointTransformed.y
};
}
// regenerate our shapes and shape positions
shapesBtn.addEventListener("click", () => {
generate();
});
// set new random color values and update the existing shapes with these colors
colorBtn.addEventListener("click", () => {
setColors();
// find all the shapes in our svg and update their fill / stroke
socialImageSVG.querySelectorAll(".shape").forEach((node) => {
if (node.getAttribute("stroke")) {
node.setAttribute("stroke", randomColor());
} else {
node.setAttribute("fill", randomColor());
}
});
});
// choose random new alignment options and update the CSS custom properties, regenerate the shapes
alignmentBtn.addEventListener("click", () => {
socialImageSVG.style.setProperty(
"--align-text-x",
alignmentOpts[~~random(0, alignmentOpts.length)]
);
socialImageSVG.style.setProperty(
"--align-text-y",
alignmentOpts[~~random(0, alignmentOpts.length)]
);
generate();
});
// save our social image as a .png file
saveBtn.addEventListener("click", () => {
const bounds = socialImageSVG.getBoundingClientRect();
// on save, update the dimensions of our social image so that it exports as expected
socialImageSVG.style.width = "1200px";
socialImageSVG.style.height = "630px";
socialImageSVG.setAttribute("width", 1200);
socialImageSVG.setAttribute("height", 630);
// this fixes an odd visual "cut off" bug when exporting
window.scrollTo(0, 0);
html2canvas(document.querySelector(".wrapper__inner"), {
width: 1200,
height: 630,
scale: 2 // export our image at 2x resolution so it is nice and crisp on retina devices
}).then((canvas) => {
canvas.toBlob(function (blob) {
// restore the social image styles
socialImageSVG.style.width = "100%";
socialImageSVG.style.height = "auto";
socialImageSVG.setAttribute("width", "");
socialImageSVG.setAttribute("height", "");
FileSaver.saveAs(blob, "generative-social-image.png");
});
});
});