【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】ChatApp with Realtime updat…

  2. 【NextJS】Access AppSync API with Dyn…

  3. 【NextJS】Checkbox・Radio・Select

  4. 【NextJS】Cognito with Amplify(Gen2)+…

  5. 【NextJS】Hooks-useState(update separ…

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

最近の記事

制作実績一覧

  1. Checkeys