239 lines
6.4 KiB
JavaScript
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");
|
|
});
|
|
});
|
|
}); |