From 4cf69f207097bc66f8154b6b3f098db4d545b703 Mon Sep 17 00:00:00 2001 From: Amy Date: Thu, 26 Aug 2021 00:37:14 -0400 Subject: [PATCH] Add random shape backgrounds --- components/ShapesBackground.tsx | 212 ++++++++++++++++++++++++++++++-- pages/_app.tsx | 3 +- 2 files changed, 203 insertions(+), 12 deletions(-) diff --git a/components/ShapesBackground.tsx b/components/ShapesBackground.tsx index 4dd8766c..45a33963 100644 --- a/components/ShapesBackground.tsx +++ b/components/ShapesBackground.tsx @@ -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({}); const { width, height } = useWindowDimension(); + const shapesContainerRef = useRef(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 ( -
+
{Object.entries(config).map(([type, instances]) => instances.map((attributes, idx) => ( + ); +} + 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 ( - + (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: [ diff --git a/pages/_app.tsx b/pages/_app.tsx index cc95ec8f..e705b5ec 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -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 */}