Search code examples
javascriptcssreactjsfonts

Canvas in React not loading font during the render of component


That's my code:

import React, { useEffect, useRef } from "react";
import PropTypes from "prop-types";
import "../fonts/fonts.css";
import "../App.css";
import { motion } from "framer-motion";
import { useParams } from "react-router-dom";

const Canvas = ({ height, width, imageUrl, courseId }) => {
  const canvasRef = useRef(null);
  const { userId, userName } = useParams();

  function divideUserId(userId) {
    const cleanedUserId = userId.replace(/[^\w]/g, "").toUpperCase();

    const parts = [];
    for (let i = 0; i < 3; i++) {
      const startIndex = i * 3;
      const endIndex = startIndex + 3;
      const part = cleanedUserId.slice(startIndex, endIndex);
      parts.push(part);
    }

    return parts;
  }

  const draw = (context) => {
    const date = new Date().toLocaleDateString();

    const kursyPomocniczne = [
      {
        secretId: "mat_podst",
        name: "Kurs do matury podstawowej",
      },
      {
        secretId: "mat_rozszerz",
        name: "Kurs do matury rozszerzonej",
      },
      {
        secretId: "mat_stud",
        name: "Kurs matematyki studenckiej",
      },
    ];

    const courseName = kursyPomocniczne.find(
      (course) => course.secretId === courseId
    ).name;

    const userText = userName.split("+").join(" ");
    const userTextWidth = context.measureText(userText).width;
    const userTextHeight = 40; // Zakładamy, że wysokość tekstu to 40px

    const canvasCenterX = context.canvas.width / 2;
    const canvasCenterY = context.canvas.height / 2;

    const userTextX = canvasCenterX - userTextWidth / 2;
    const userTextY = canvasCenterY + (userTextHeight + 117 / 2);

    const isFemale =
      userText && userText.charAt(userText.length - 1).toUpperCase() === "A";

    if (!isFemale) {
      context.strokeStyle = "#192e50";
      context.lineWidth = 4;
      context.beginPath();
      context.moveTo(501, 495);
      context.lineTo(490, 495);
      context.stroke();
      context.beginPath();
      context.moveTo(401, 536);
      context.lineTo(390, 536);
      context.stroke();
      context.beginPath();
      context.moveTo(721, 536);
      context.lineTo(710, 536);
      context.stroke();
    } else {
      context.strokeStyle = "#192e50";
      context.lineWidth = 4;
      context.beginPath();
      context.moveTo(705, 536);
      context.lineTo(694, 536);
      context.stroke();
    }

    context.fillStyle = "#192e50";
    context.font = "22px 'Serif', serif";
    context.fillText(date, 330, 608);
    context.fillStyle = "#192e50";
    context.font = "56px 'Great Vibes', cursive";
    context.fillText(userText, userTextX, userTextY);

    const courseCanvas = document.createElement("canvas");
    courseCanvas.width = 830;
    courseCanvas.height = 706;
    const courseContext = courseCanvas.getContext("2d");

    const courseCenterX = courseContext.canvas.width / 2;

    const courseTextWidth = courseContext.measureText(courseName).width;
    const courseTextX = courseCenterX - courseTextWidth / 2;

    courseContext.fillStyle = "#192e50";
    courseContext.font = "18px 'Serif', serif";
    courseContext.fillText(courseName.toUpperCase(), courseTextX, 520);

    context.drawImage(courseCanvas, 0, 0);

    const tempCanvas = context.canvas.cloneNode();
    const singContext = tempCanvas.getContext("2d");
    singContext.fillStyle = "#192e50";
    singContext.font = "9px 'Ubuntu', serif";
    singContext.justifyContent = "center";

    const parts = divideUserId(userId);

    parts.forEach((part, index) => {
      const partY = canvasCenterY - (40 - 572 / 2) + 10 * index;
      singContext.globalAlpha = 0.3;
      singContext.fillText(part, 881, partY);
    });

    context.drawImage(tempCanvas, 0, 0);

    singContext.clearRect(0, 0, tempCanvas.width, tempCanvas.height);
  };

  useEffect(() => {
    const canvas = canvasRef.current;
    const context = canvas.getContext("2d");
    const image = new Image();

    image.onload = () => {
      context.drawImage(image, 0, 0, width, height);
      draw(context);
    };
    image.src = imageUrl;
  }, [draw, height, width, imageUrl]);

  return (
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      transition={{ duration: 2 }}
    >
      <canvas
        ref={canvasRef}
        height={height}
        width={width}
        className="rounded-xl mx-auto xl:w-4/5 xl:h-3/5 lg:w-3/5 lg:h-3/5  md:w-3/5 md:h-3/5 sm:w-3/5 sm:h-3/5 xs:w-3/5 xs:h-3/5"
      />
    </motion.div>
  );
};

Canvas.propTypes = {
  height: PropTypes.number.isRequired,
  width: PropTypes.number.isRequired,
  imageUrl: PropTypes.string.isRequired,
};

export default Canvas;

Well, I've tried almost every option with font face constructor, adding font face in App.css, creating file from where I'm importing this fonts, downloading fonts as a woff, as ttf and importing from folder, importing from google fonts link and none of them work. The font is not being loaded as a component mounts, it actually has to load before the component is rendered so that for sure the font will be included. Now, I have to refresh the page to see this font. For default the font is being rendered as Comic Sans since it's default cursive font. What should I do?


Solution

  • Well, I managed to solve the issue through workaround using another div which force the font to load before the canvas is being rendered:

    <motion.div
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          transition={{ duration: 2 }}
        >
          <div
            style={{
              fontFamily: "Great Vibes",
            }}
            className="absolute -left-10"
          >
            .
          </div>
          <canvas
            ref={canvasRef}
            height={height}
            width={width}
            className="rounded-xl mx-auto w-10/12"
          />
        </motion.div>