repos

isaacbythewood.com-nextjs

mirror archived upstream

Animation-heavy personal portfolio on Next.js: a custom cursor, a page-transition loader, and canvas backgrounds.

canvas-animationscss-modulesdark-themehandcodednextjspersonal-websiteportfolioreact

3.0 KB · 115 lines · JavaScript Raw History
  1import React, { useEffect, useRef } from "react";
  2import styles from "@styles/components/canvas.module.css";
  3import PropTypes from "prop-types";
  4
  5const Constellations = ({ options }) => {
  6  const isActive = options.isActive !== undefined ? options.isActive : true;
  7  const canvas = useRef(null);
  8
  9  useEffect(() => {
 10    const cvs = canvas.current;
 11
 12    cvs.width = cvs.offsetWidth;
 13    cvs.height = cvs.offsetHeight;
 14
 15    const resizeCanvas = () => {
 16      cvs.width = cvs.offsetWidth;
 17      cvs.height = cvs.offsetHeight;
 18    };
 19    window.addEventListener("resize", resizeCanvas);
 20
 21    return () => {
 22      window.removeEventListener("resize", resizeCanvas);
 23    };
 24  }, []);
 25
 26  useEffect(() => {
 27    const cvs = canvas.current;
 28    const ctx = cvs.getContext("2d");
 29
 30    let stars = [];
 31    let numStars = 0;
 32    const maxNumStars = options.numStars;
 33    while (numStars < maxNumStars) {
 34      const randomPoint = [
 35        cvs.width * Math.random(),
 36        cvs.height * Math.random(),
 37      ];
 38      stars.push({
 39        loc: randomPoint,
 40        dir: [Math.random() > 0.5 ? "+" : "-", Math.random() > 0.5 ? "+" : "-"],
 41      });
 42      numStars++;
 43    }
 44
 45    let starsAnimationFrame = null;
 46    const starDistance = 150;
 47    const drawStars = () => {
 48      ctx.clearRect(0, 0, cvs.width, cvs.height);
 49
 50      stars.map((star) => {
 51        ctx.beginPath();
 52        ctx.arc(...star.loc, 2, 0, 2 * Math.PI);
 53        ctx.fillStyle = "rgb(255, 255, 255)";
 54        ctx.fill();
 55        ctx.closePath();
 56        const closeStars = stars.filter((closeStar) => {
 57          return (
 58            Math.hypot(
 59              star.loc[0] - closeStar.loc[0],
 60              star.loc[1] - closeStar.loc[1]
 61            ) < starDistance
 62          );
 63        });
 64        closeStars.map((closeStar) => {
 65          ctx.beginPath();
 66          ctx.moveTo(...star.loc);
 67          ctx.lineTo(...closeStar.loc);
 68          ctx.strokeStyle = `rgba(255, 255, 255, ${
 69            (starDistance -
 70              Math.hypot(
 71                star.loc[0] - closeStar.loc[0],
 72                star.loc[1] - closeStar.loc[1]
 73              )) /
 74            starDistance
 75          })`;
 76          ctx.stroke();
 77          ctx.closePath();
 78        });
 79      });
 80
 81      stars = stars.map((star) => {
 82        if (star.loc[0] < 0) star.dir[0] = "+";
 83        else if (star.loc[0] > cvs.width) star.dir[0] = "-";
 84        if (star.loc[1] < 0) star.dir[1] = "+";
 85        else if (star.loc[1] > cvs.height) star.dir[1] = "-";
 86
 87        star.loc[0] += parseFloat(`${star.dir[0]}0.5`);
 88        star.loc[1] += parseFloat(`${star.dir[1]}0.5`);
 89
 90        return star;
 91      });
 92
 93      if (isActive) {
 94        starsAnimationFrame = window.requestAnimationFrame(drawStars);
 95      }
 96    };
 97
 98    if (isActive) {
 99      starsAnimationFrame = window.requestAnimationFrame(drawStars);
100    }
101
102    return () => {
103      window.cancelAnimationFrame(starsAnimationFrame);
104    };
105  }, [isActive]);
106
107  return <canvas ref={canvas} className={styles.canvas} />;
108};
109
110Constellations.propTypes = {
111  options: PropTypes.object,
112};
113
114export default Constellations;