【NextJS】Canvasを使い、図形を描画

1. 概要

前回はError Handlingの使い方についてでした。今回はCanvasの使い方についてです。

対象としては開発を1年程やってて自分で最初から開発してみたい方になります。そのため細かい用語などの説明はしません。

2. nodeのインストール

こちらを参考

3. プロジェクトを作成

こちらを参考

4. 必要なライブラリをインストール

こちらを参考

5. ソースコード

※前回より差分のみを記載

5-1-1. src/app/components/component05/canvas.tsx

"use client";

import { useCallback, useEffect, useRef } from "react";

type CanvasProps = {
  canvasWidth: number;
  canvasHeight: number;
  axisX: number;
  axisY: number;
  rectWidth: number;
  rectHeight: number;
  data: number;
};

const Canvas = (props: CanvasProps) => {
  const {
    canvasWidth,
    canvasHeight,
    axisX,
    axisY,
    rectWidth,
    rectHeight,
    data,
  } = props;
  const canvasRef = useRef<HTMLCanvasElement>(null);

  const startAxis = 30;
  const endAxis = 270;
  const distance = 40;

  const getCtx = (): CanvasRenderingContext2D => {
    const canvas: any = canvasRef.current;
    if (!canvas || !canvas.getContext) throw new Error("Something may happen.");
    return canvas.getContext("2d");
  };

  const drawOutline = useCallback(
    (ctx: CanvasRenderingContext2D, fgColor: string) => {
      const rect = new Path2D();
      rect.rect(0, 0, canvasWidth, canvasHeight);
      ctx.beginPath();
      ctx.clearRect(0, 0, canvasWidth, canvasHeight);
      ctx.fillStyle = fgColor;
      ctx.stroke(rect);
    },
    [canvasWidth, canvasHeight]
  );

  const drawRect = useCallback(
    (ctx: CanvasRenderingContext2D, fgColor: string) => {
      const rect = new Path2D();
      rect.rect(axisX, axisY, rectWidth, rectHeight);
      ctx.beginPath();
      ctx.fillStyle = fgColor;
      ctx.fill(rect);
    },
    [axisX, axisY, rectWidth, rectHeight]
  );

  const drawLine = useCallback(
    (ctx: CanvasRenderingContext2D, fgColor: string) => {
      ctx.beginPath();
      ctx.strokeStyle = fgColor;
      ctx.lineWidth = 2;
      for (let i = 0; i < 7; i++) {
        const y = startAxis + i * distance;
        const x = startAxis + i * distance;
        ctx.moveTo(startAxis, y);
        ctx.lineTo(endAxis, y);
        ctx.moveTo(x, startAxis);
        ctx.lineTo(x, endAxis);
      }
      ctx.stroke();
    },
    []
  );

  const drawArc = useCallback(
    (ctx: CanvasRenderingContext2D, fgColor: string) => {
      const radius = 5;
      const startAngle = 0;
      const endAngle = Math.PI * 2;
      ctx.beginPath();
      ctx.fillStyle = fgColor;
      for (let i = 0; i < data; i++) {
        const pointAxis = startAxis + distance * i;
        ctx.moveTo(pointAxis, pointAxis);
        ctx.arc(pointAxis, pointAxis, radius, startAngle, endAngle, true);
      }
      ctx.fill();
    },
    [data]
  );

  const drawAll = useCallback(() => {
    const ctx: CanvasRenderingContext2D = getCtx();
    drawRect(ctx, "green");
    drawLine(ctx, "white");
    drawArc(ctx, "red");
  }, [drawRect, drawLine, drawArc]);

  useEffect(() => drawAll(), [drawAll]);

  return <canvas ref={canvasRef} width={canvasWidth} height={canvasHeight} />;
};

export default Canvas;

5-1-2. src/app/components/component05/page.module.scss

.component {
  color: blue;
  & ul {
    margin-left: 20px;
    & li {
      list-style: disc;
    }
  }

  canvas {
    border: 1px solid black;
  }
}

5-1-3. src/app/components/component05/page.tsx

"use client";

import React, { useState } from "react";

import { Button } from "@mui/material";

import GoBack from "@/lib/components/go-back";
import scss from "./page.module.scss";
import Canvas from "./canvas";

const Component05 = () => {
  const [point, setPoint] = useState(0);
  const drawPoint = () => {
    point == 7 ? setPoint(0) : setPoint(point + 1);
  };

  return (
    <div className={scss.component}>
      <GoBack />
      <br />
      <br />
      <ul>
        <li>Drawing</li>
      </ul>
      <Canvas
        canvasWidth={300}
        canvasHeight={300}
        axisX={0}
        axisY={0}
        rectWidth={300}
        rectHeight={300}
        data={point}
      />
      <br />
      <Button
        variant="contained"
        size="medium"
        color="primary"
        onClick={() => drawPoint()}
      >
        Click
      </Button>
    </div>
  );
};

export default Component05;

5-1-4. src/app/component/page.tsx

"use client";

import React from "react";
import { Link } from "@mui/material";

import scss from "./page.module.scss";

const Components = () => {
  return (
    <div className={scss.components}>
      <ul>
        <li>
          <Link href="/components/component01" underline="hover">
            Component01
          </Link>
        </li>
        <li>
          <Link href="/components/component02" underline="hover">
            Component02
          </Link>
        </li>
        <li>
          <Link href="/components/component03" underline="hover">
            Component03
          </Link>
        </li>
        <li>
          <Link href="/components/component04" underline="hover">
            Component04
          </Link>
        </li>
        <li>
          <Link href="/components/component05" underline="hover">
            Component05
          </Link>
        </li>
      </ul>
    </div>
  );
};

export default Components;

6. サーバーを起動

npm run dev

7. ブラウザで確認

  • http://localhost:3000

8. ディレクトリの構造

.
├── README.md
├── next-env.d.ts
├── next.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
│   ├── js
│   │   └── script.js
│   ├── next.svg
│   └── vercel.svg
├── src
│   ├── app
│   │   ├── components
│   │   │   ├── component01
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── component02
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── component03
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── component04
│   │   │   │   ├── checkbox-demo.tsx
│   │   │   │   ├── page.module.scss
│   │   │   │   ├── page.tsx
│   │   │   │   ├── radio-demo.tsx
│   │   │   │   └── select-demo.tsx
│   │   │   ├── component05
│   │   │   │   ├── canvas.tsx
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── page.module.scss
│   │   │   └── page.tsx
│   │   ├── events
│   │   │   ├── event01
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── page.module.scss
│   │   │   └── page.tsx
│   │   ├── favicon.ico
│   │   ├── globals.css
│   │   ├── globals.scss
│   │   ├── hooks
│   │   │   ├── hook01
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── hook02
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── hook03
│   │   │   │   ├── child.tsx
│   │   │   │   ├── counter-provider.tsx
│   │   │   │   ├── grandchild.tsx
│   │   │   │   ├── myself.tsx
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── hook04
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── hook05
│   │   │   │   ├── child.tsx
│   │   │   │   ├── counter-provider.tsx
│   │   │   │   ├── grandchild.tsx
│   │   │   │   ├── myself.tsx
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── hook06
│   │   │   │   ├── child.tsx
│   │   │   │   ├── page.module.scss
│   │   │   │   ├── page.tsx
│   │   │   │   ├── play-provider.tsx
│   │   │   │   ├── text-box.tsx
│   │   │   │   └── video-player.tsx
│   │   │   ├── page.module.scss
│   │   │   └── page.tsx
│   │   ├── layout.module.scss
│   │   ├── layout.tsx
│   │   ├── nextjs
│   │   │   ├── nextjs01
│   │   │   │   ├── child
│   │   │   │   │   ├── client-page.tsx
│   │   │   │   │   ├── metadata.ts
│   │   │   │   │   └── page.tsx
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── nextjs02
│   │   │   │   ├── [...slug]
│   │   │   │   │   └── page.tsx
│   │   │   │   ├── page.module.scss
│   │   │   │   ├── page.tsx
│   │   │   │   └── shop
│   │   │   │       └── [id]
│   │   │   │           └── page.tsx
│   │   │   ├── nextjs03
│   │   │   │   ├── fetch-image.tsx
│   │   │   │   ├── get-image.tsx
│   │   │   │   ├── loading.tsx
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── nextjs04
│   │   │   │   ├── actions.ts
│   │   │   │   ├── list.tsx
│   │   │   │   ├── page.module.scss
│   │   │   │   ├── page.tsx
│   │   │   │   └── register-form.tsx
│   │   │   ├── nextjs05
│   │   │   │   ├── locale-switcher.tsx
│   │   │   │   ├── metadata.ts
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── nextjs06
│   │   │   │   ├── error.tsx
│   │   │   │   ├── fetch-check.tsx
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── page.module.scss
│   │   │   └── page.tsx
│   │   ├── page.module.scss
│   │   ├── page.tsx
│   │   └── redux
│   │       ├── page.module.scss
│   │       ├── page.tsx
│   │       ├── redux01
│   │       │   ├── child.tsx
│   │       │   ├── counter-slice.ts
│   │       │   ├── grandchild.tsx
│   │       │   ├── hooks.ts
│   │       │   ├── myself.tsx
│   │       │   ├── page.module.scss
│   │       │   ├── page.tsx
│   │       │   └── store.ts
│   │       └── redux02
│   │           ├── child.tsx
│   │           ├── hooks.ts
│   │           ├── image-box.tsx
│   │           ├── image-slice.ts
│   │           ├── page.module.scss
│   │           ├── page.tsx
│   │           ├── store.ts
│   │           ├── text-box.tsx
│   │           └── text-slice.ts
│   ├── lib
│   │   ├── common
│   │   │   ├── db
│   │   │   │   ├── pool-config.ts
│   │   │   │   └── pool.ts
│   │   │   ├── definitions.ts
│   │   │   ├── i18n
│   │   │   │   ├── dictionaries
│   │   │   │   │   ├── cn.json
│   │   │   │   │   ├── en.json
│   │   │   │   │   ├── ja.json
│   │   │   │   │   └── ko.json
│   │   │   │   ├── dictionaries.ts
│   │   │   │   └── i18n-config.ts
│   │   │   └── sidebar-links.tsx
│   │   ├── components
│   │   │   ├── alert-snackbar.tsx
│   │   │   ├── go-back.tsx
│   │   │   └── spacer.tsx
│   │   ├── footer.tsx
│   │   ├── header.tsx
│   │   ├── sidebar.tsx
│   │   └── utils
│   │       └── util.ts
│   └── scss
│       └── common
│           ├── _index.scss
│           ├── _mixin.scss
│           ├── _mq.scss
│           └── _variables.scss
├── tailwind.config.ts
└── tsconfig.json

42 directories, 133 files

9. 備考

今回はCanvasの使い方についてでした。

10. 参考

投稿者プロフィール

Sondon
開発好きなシステムエンジニアです。
卓球にハマってます。

関連記事

  1. 【NextJS】SassとTypescriptでレイアウトを構成

  2. 【NextJS】FullCalendar

  3. 【NextJS】Hooks-useReducer

  4. 【NextJS】Hooks-useContext・useReducer…

  5. 【NextJS】Error Handling

  6. 【NextJS】NextJS・TypeScript・Apollo Cl…

最近の記事

  1. VBA

制作実績一覧

  1. Checkeys