Add random shape backgrounds

This commit is contained in:
Amy 2021-08-26 00:37:14 -04:00
parent 59c848acad
commit 4cf69f2070
2 changed files with 203 additions and 12 deletions

View File

@ -1,5 +1,5 @@
import { useWindowDimension } from "hooks/useWindowDimension";
import React, { CSSProperties, useEffect, useState } from "react";
import React, { CSSProperties, useEffect, useRef, useState } from "react";
import { Image } from "./Image";
@ -12,13 +12,21 @@ interface Props {
export function ShapesBackground({ getConfig }: Props) {
const [config, setConfig] = useState<ShapesConfig>({});
const { width, height } = useWindowDimension();
const shapesContainerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
setConfig(getConfig?.() ?? {});
const width = shapesContainerRef.current?.clientWidth;
const height = shapesContainerRef.current?.clientHeight;
if (width == null || height == null) {
return;
}
setConfig(getConfig?.(width, height) ?? {});
}, [getConfig, width, height]);
return (
<div className={styles.shapesContainer}>
<div className={styles.shapesContainer} ref={shapesContainerRef}>
{Object.entries(config).map(([type, instances]) =>
instances.map((attributes, idx) => (
<Shape
@ -32,6 +40,16 @@ export function ShapesBackground({ getConfig }: Props) {
);
}
function Shape(props: { type: ShapeType; style: CSSProperties }) {
return (
<Image
src={`/images/shapes/${props.type}.svg`}
className={styles.shape}
style={props.style}
/>
);
}
export type ShapeType =
| "asterisk"
| "circle"
@ -43,24 +61,196 @@ export type ShapeType =
| "triangle"
| "triangleBig"
| "waves"
| "waveBig";
| "wavesBig";
export type ShapesConfig = {
[key in ShapeType]?: CSSProperties[];
};
export type GetShapesConfig = () => ShapesConfig;
export type GetShapesConfig = (width: number, height: number) => ShapesConfig;
function Shape(props: { type: ShapeType; style: CSSProperties }) {
type ShapeSize = {
[key in ShapeType]: {
size: "big" | "small";
width: number;
height: number;
// 0 <= minAngle, maxAngle <= 180
minAngle?: number;
maxAngle?: number;
};
};
const shapeTypes: ShapeType[] = [
"asterisk",
"circle",
"cross",
"dots",
"hash",
"plus",
"ring",
"triangle",
"triangleBig",
"waves",
"wavesBig",
];
const shapesSizes: ShapeSize = {
asterisk: {
size: "big",
width: 168,
height: 168,
},
circle: {
size: "big",
width: 132,
height: 132,
},
cross: {
size: "big",
width: 150,
height: 150,
},
dots: {
size: "big",
width: 232,
height: 250,
},
hash: {
size: "small",
width: 60,
height: 60,
},
plus: {
size: "small",
width: 48,
height: 48,
},
ring: {
size: "small",
width: 70,
height: 70,
},
triangle: {
size: "small",
width: 68,
height: 68,
minAngle: 15,
maxAngle: 26,
},
triangleBig: {
size: "big",
width: 138,
height: 138,
minAngle: 15,
maxAngle: 26,
},
waves: {
size: "small",
width: 102,
height: 50,
},
wavesBig: {
size: "big",
width: 252,
height: 132,
},
};
const shapesBySize = {
big: Object.entries(shapesSizes)
.map((shape) => shape[0])
.filter(
(shape) => shapesSizes[shape as ShapeType]["size"] == "big"
) as ShapeType[],
small: Object.entries(shapesSizes)
.map((shape) => shape[0])
.filter(
(shape) => shapesSizes[shape as ShapeType]["size"] == "small"
) as ShapeType[],
};
// Used to generate random shapes in the margins
export const defaultGetShapesConfig = ((pageWidth, pageHeight) => {
if (window.innerWidth <= 768) {
return mobileShapesConfig;
}
const defaultConfig: ShapesConfig = {};
shapeTypes.forEach((shape) => (defaultConfig[shape] = []));
const gap = 20;
const boxWidth = Math.max(300, (pageWidth - 800 - 2 * gap) / 2);
const boxHeight = 400;
for (let y = 0; y + boxHeight <= pageHeight; y += gap + boxHeight) {
for (let x = 0; x <= 1; ++x) {
if (Math.random() < 0.3) {
continue;
}
const size = y == 0 || y + 2 * boxHeight > pageHeight ? "big" : "small";
const shape: ShapeType = getRandomShape(size);
const verticalOffset = getVerticalOffset(boxHeight, shape);
const horizontalOffset = getHorizontalOffset(boxWidth, shape);
const shapeWidth = shapesSizes[shape]["width"];
const shapeHeight = shapesSizes[shape]["height"];
const angle = getRandomAngle(shape);
const colour = getRandomColour();
const opacity = getRandomOpacity(colour);
defaultConfig[shape]?.push({
top: `${((y + verticalOffset) / 16).toFixed(5)}rem`,
left: x == 0 ? `${(horizontalOffset / 16).toFixed(5)}rem` : "unset",
right: x == 1 ? `${(horizontalOffset / 16).toFixed(5)}rem` : "unset",
width: `${(shapeWidth / 16).toFixed(5)}rem`,
height: `${(shapeHeight / 16).toFixed(5)}rem`,
transform: `rotate(${angle}deg)`,
filter: `var(--${colour})`,
opacity: `${opacity}%`,
});
}
}
return defaultConfig;
}) as GetShapesConfig;
function getRandomShape(size: "big" | "small"): ShapeType {
const idx = Math.floor(Math.random() * shapesBySize[size].length);
return shapesBySize[size][idx];
}
function getVerticalOffset(boxHeight: number, shape: ShapeType): number {
const padding = shapesSizes[shape]["height"];
return Math.floor(Math.random() * (boxHeight - padding));
}
function getHorizontalOffset(boxWidth: number, shape: ShapeType): number {
const padding = shapesSizes[shape]["width"];
return Math.floor(Math.random() * (boxWidth - padding));
}
function getRandomAngle(shape: ShapeType): number {
const minAngle = shapesSizes[shape]["minAngle"] ?? 0;
const maxAngle = shapesSizes[shape]["maxAngle"] ?? 0;
const direction = Math.random() < 0.5 ? 1 : -1;
return (
<Image
src={`/images/shapes/${props.type}.svg`}
className={styles.shape}
style={props.style}
/>
(minAngle + Math.floor(Math.random() * (maxAngle - minAngle + 1))) *
direction
);
}
function getRandomColour(): "blue" | "teal" {
return Math.random() < 0.7 ? "blue" : "teal";
}
function getRandomOpacity(colour: "blue" | "teal"): number {
if (colour == "blue") {
return Math.random() < 0.8 ? 20 : 25;
} else {
return Math.random() < 0.8 ? 25 : 30;
}
}
// Used for most mobile pages
export const mobileShapesConfig = {
dots: [

View File

@ -12,6 +12,7 @@ import { Link } from "@/components/Link";
import { Navbar } from "@/components/Navbar";
import { Pre } from "@/components/Pre";
import {
defaultGetShapesConfig,
GetShapesConfig,
ShapesBackground,
} from "@/components/ShapesBackground";
@ -41,7 +42,7 @@ export default function App({ Component, pageProps }: AppProps): JSX.Element {
{/* Wrapping content with a div to allow for a display: block parent */}
<div className={styles.contentContainer}>
<ShapesBackground
getConfig={Component.getShapesConfig ?? undefined}
getConfig={Component.getShapesConfig ?? defaultGetShapesConfig}
/>
<Layout>
<Component {...pageProps} />