likes
comments
collection
share

网格画法:原生 Canvas、React-konva 画网格,可拖动、可放大缩小、并带有坐标系 0 0 位置辅助线

作者站长头像
站长
· 阅读数 43

原生 Canvas、React-konva 画网格

网格:一种可以在 canvas 面板上绘制图形的辅助线集合。

网格画法:原生 Canvas、React-konva 画网格,可拖动、可放大缩小、并带有坐标系 0 0 位置辅助线

我们设定 canvas 初始化左上角顶点为 0,0 点,向右👉和向下👇是 X Y 轴正方向

一、原生 Canvas 画网格

1、使用 Canvas 的方法

原生画网格,只需要使用 ctxmoveTolineTostrokeStyle

1. moveTo

是 Canvas 2D API 将一个新的子路径的起始点移动到 (x,y) 坐标的方法。

ctx.moveTo(x, y);

2. lineTo

是 Canvas 2D API 使用直线连接子路径的终点到 x,y 坐标的方法。

ctx.lineTo(x, y);

3. strokeStyle

是 Canvas 2D API 描述画笔(绘制图形)颜色或者样式的属性。默认值是 #000

ctx.strokeStyle = '#ff0000'

2、代码

import React, { useEffect, useState } from 'react';
import { Button } from 'antd';

interface IPageSlicePosType {
    x: number;
    y: number;
}

export const CanvasGrid = () => {

    // 当前 canvas 的 0 0 坐标,我们设置 canvas 左上角顶点为 0 0,向右👉和向下👇是 X Y 轴正方向,0,0 为 pageSlicePos 初始值
    const [pageSlicePos, setPageSlicePos] = useState<IPageSlicePosType>({
        x: 0,
        y: 0,
    });
    const [ctxVal, setCtxVal] = useState<any>(null); // canvas 的 ctx
    const [scale, setScale] = useState<number>(1); // 缩放比例
    const [solidColor] = useState<string>('#CCCCCC70'); // 实线颜色
    const [dashedColor] = useState<string>('#CCCCCC25'); // 虚线颜色
    const [zeroColor] = useState<string>('#358bf3'); // 0 点坐标系颜色

    // 监听 pageSlicePos 数据,有变动则进行 canvas 的绘制
    useEffect(() => {
        if (ctxVal) {
            // 重新绘制之前清空 canvas
            ctxVal.clearRect(0, 0, ctxVal.canvas.width, ctxVal.canvas.height);
        }
        drawLineGrid();
    }, [pageSlicePos]);

    /**
     * 绘制网格
     * @param scaleVal 缩放倍数
     */
    const drawLineGrid = (scaleVal = scale) => {
        /*获取元素*/
        var myCanvas: any = document.querySelector('#myCanvas');
        /*获取绘图工具*/
        var ctx = ctxVal || myCanvas.getContext('2d');
        setCtxVal(ctx);
        // 设置网格大小
        var girdSize = 5 * scaleVal;

        // 获取Canvas的width、height
        var CanvasWidth = ctx.canvas.width;
        var CanvasHeight = ctx.canvas.height;

        // 在 pageSlicePos 的 x,y 点位画一个 10 * 10 的红色标记用来表示当前页面的 0 0 坐标
        ctx.fillRect(pageSlicePos.x, pageSlicePos.y, 10, 10); // 效果图红色小方块
        ctx.fillStyle = 'red';

        const canvasXHeight = CanvasHeight - pageSlicePos.y;
        const canvasYWidth = CanvasWidth - pageSlicePos.x;
        // 从 pageSlicePos.y 处开始往 Y 轴正方向画 X 轴网格
        const xPageSliceTotal = Math.ceil(canvasXHeight / girdSize);
        for (let i = 0; i < xPageSliceTotal; i++) {
            ctx.beginPath(); // 开启路径,设置不同的样式
            ctx.moveTo(0, pageSlicePos.y + girdSize * i);
            ctx.lineTo(CanvasWidth, pageSlicePos.y + girdSize * i);
            ctx.strokeStyle = i === 0 ? zeroColor : (i % 5 === 0 ? solidColor : dashedColor); // 如果为 0 则用蓝色标记,取余 5 为实线,其余为比较淡的线
            ctx.stroke();
        }

        // 从 pageSlicePos.y 处开始往 Y 轴负方向画 X 轴网格
        const xRemaining = pageSlicePos.y;
        const xRemainingTotal = Math.ceil(xRemaining / girdSize);
        for (let i = 0; i < xRemainingTotal; i++) {
            if (i === 0) continue;
            ctx.beginPath(); // 开启路径,设置不同的样式
            ctx.moveTo(0, pageSlicePos.y - girdSize * i); // -0.5是为了解决像素模糊问题
            ctx.lineTo(CanvasWidth, pageSlicePos.y - girdSize * i);
            ctx.strokeStyle = i === 0 ? zeroColor : (i % 5 === 0 ? solidColor : dashedColor);// 如果为 0 则用蓝色标记,取余 5 为实线,其余为比较淡的线
            ctx.stroke();
        }

        // 从 pageSlicePos.x 处开始往 X 轴正方向画 Y 轴网格
        const yPageSliceTotal = Math.ceil(canvasYWidth / girdSize); // 计算需要绘画y轴的条数
        for (let j = 0; j < yPageSliceTotal; j++) {
            ctx.beginPath(); // 开启路径,设置不同的样式
            ctx.moveTo(pageSlicePos.x + girdSize * j, 0);
            ctx.lineTo(pageSlicePos.x + girdSize * j, CanvasHeight);
            ctx.strokeStyle = j === 0 ? zeroColor : (j % 5 === 0 ? solidColor : dashedColor);// 如果为 0 则用蓝色标记,取余 5 为实线,其余为比较淡的线
            ctx.stroke();
        }

        // 从 pageSlicePos.x 处开始往 X 轴负方向画 Y 轴网格
        const yRemaining = pageSlicePos.x;
        const yRemainingTotal = Math.ceil(yRemaining / girdSize);
        for (let j = 0; j < yRemainingTotal; j++) {
            if (j === 0) continue;
            ctx.beginPath(); // 开启路径,设置不同的样式
            ctx.moveTo(pageSlicePos.x - girdSize * j, 0);
            ctx.lineTo(pageSlicePos.x - girdSize * j, CanvasHeight);
            ctx.strokeStyle = j === 0 ? zeroColor : (j % 5 === 0 ? solidColor : dashedColor);// 如果为 0 则用蓝色标记,取余 5 为实线,其余为比较淡的线
            ctx.stroke();
        }
    };
    
	/**
     * 点击缩放,设置缩放倍数
     */
    const clickScale = () => {
        const scaleVal = scale + 1 > 6 ? 1 : scale + 1;
        setScale(scaleVal);
        ctxVal.clearRect(0, 0, ctxVal.canvas.width, ctxVal.canvas.height);
        drawLineGrid(scaleVal);
    }

    /**
     * 拖动 canvas 动态渲染,拖动时,动态设置 pageSlicePos 的值
     * @param e Event
     */
    const mouseDown = (e: any) => {
        const downX = e.clientX;
        const downY = e.clientY;
        const { x, y } = pageSlicePos;
        var myCanvas: any = document.querySelector('#myCanvas');
        myCanvas.onmousemove = (ev: any) => {
            const moveX = ev.clientX;
            const moveY = ev.clientY;
            setPageSlicePos({
                x: x + (moveX - downX),
                y: y + (moveY - downY),
            });
            myCanvas.onmouseup = (en: any) => {
                myCanvas.onmousemove = null;
                myCanvas.onmouseup = null;
            };
        }
        myCanvas.onmouseup = (en: any) => {
            myCanvas.onmousemove = null;
            myCanvas.onmouseup = null;
        };
    }
    return (
        <div className='canvas'>
            <div>
                <Button onClick={clickScale}>缩放{scale}</Button>
            </div>
            <div style={{ width: '600px', margin: '0 auto'}}>
                <canvas onMouseDown={mouseDown} id="myCanvas" width="600" height="400"></canvas>
            </div>
        </div>
    )
}

3、效果展示

网格画法:原生 Canvas、React-konva 画网格,可拖动、可放大缩小、并带有坐标系 0 0 位置辅助线

二、React-konva

1、konva 介绍

Konva 中文网 React-konva

2、react-konva 示例

import { Stage, Layer, Rect, Circle } from 'react-konva';

export const App = () => {
  return (
    // Stage - is a div wrapper
    // Layer - is an actual 2d canvas element, so you can have several layers inside the stage
    // Rect and Circle are not DOM elements. They are 2d shapes on canvas
    <Stage width={window.innerWidth} height={window.innerHeight}>
      <Layer>
        <Rect width={50} height={50} fill="red" />
        <Circle x={200} y={200} stroke="black" radius={50} />
      </Layer>
    </Stage>
  );
}

3、代码

1. konva-canvas.tsx

import React, { useState } from "react";
import { Button } from 'antd';
import { Stage } from "react-konva";
import { LineGrid } from '../components/line-grid';

export const KonvasCanvas = () => {
    const [scale, setScale] = useState<number>(1); // 缩放比例
    const [stagePos, setStagePos] = React.useState({ x: 0, y: 0 }); // 页面 0 0 坐标位置
    const [CanvasWidth] = useState<number>(600); // canvas 宽度
    const [CanvasHeight] = useState<number>(400); // canvas 高度
    
    /**
     * 点击缩放
     */
    const clickScale = () => {
        const scaleVal = scale + 1 > 6 ? 1 : scale + 1;
        setScale(scaleVal);
    }
    return (
        <div className='konva' style={{ width: '600px', margin: '20px auto', }}>
            <Button onClick={clickScale}>缩放{scale}</Button>
            <div id="konva-canvas">
                <Stage
                    x={stagePos.x}
                    y={stagePos.y}
                    width={CanvasWidth}
                    height={CanvasHeight}
                    strokeWidth={1}
                    draggable
                    onDragMove={e => {
                    	// 拖动事件,设置 stagePos 值
                        const { x, y } = e.currentTarget.position();
                        setStagePos({
                            x: Math.round(x),
                            y: Math.round(y),
                        });
                    }}
                >
                    <LineGrid scale={scale} CanvasWidth={CanvasWidth} CanvasHeight={CanvasHeight} stagePos={stagePos} />
                </Stage>
            </div>
        </div>
    )
};

2. LineGrid.tsx

import React, { useState } from 'react';
import { Layer, Rect, Line } from "react-konva";

export const LineGrid = (props: any) => {
    const { scale, stagePos, CanvasWidth, CanvasHeight } = props;
    // 需要插入的网格 components
    const gridComponents = [];
    // 网格分成多少份 宽度 * 倍数 / 需要多少网格数
    const girdSize = 100 * scale / 20;
    // canvas X、Y 轴距离 stagePos x y 的距离
    const canvasXHeight = CanvasHeight - stagePos.y;
    const canvasYWidth = CanvasWidth - stagePos.x;
    const [solidColor] = useState<string>('#CCCCCC70'); // 实线颜色
    const [dashedColor] = useState<string>('#CCCCCC25'); // 虚线颜色
    const [zeroColor] = useState<string>('#358bf3'); // 0 点颜色
    const [strokeWidth] = useState<number>(1); // strokeWidth
    const [shadowOpacity] = useState<number>(0); // shadowOpacity
    const [shadowEnabled] = useState<boolean>(false); // shadowEnabled

    // 从 pageSlicePos.y 处开始往 Y 轴正方向画 X 轴网格
    const xPageSliceTotal = Math.ceil(canvasXHeight / girdSize);
    for (let i = 0; i < xPageSliceTotal; i++) {
        gridComponents.push(<Line
            x={0 - stagePos.x}
            y={girdSize * i}
            strokeWidth={strokeWidth}
            shadowOpacity={shadowOpacity}
            shadowEnabled={shadowEnabled}
            points={[0, 0, CanvasWidth, 0]}
            stroke={i === 0 ? zeroColor : i % 5 === 0 ? solidColor : dashedColor}
        />)
    }

    // 从 pageSlicePos.y 处开始往 Y 轴负方向画 X 轴网格
    const xRemaining = stagePos.y;
    const xRemainingTotal = Math.ceil(xRemaining / girdSize);
    for (let i = 0; i < xRemainingTotal; i++) {
        if (i === 0) continue;
        gridComponents.push(<Line
            x={0 - stagePos.x}
            y={-girdSize * i}
            strokeWidth={strokeWidth}
            shadowOpacity={shadowOpacity}
            shadowEnabled={shadowEnabled}
            points={[0, 0, CanvasWidth, 0]}
            stroke={i === 0 ? zeroColor : i % 5 === 0 ? solidColor : dashedColor}
        />)
    }

    // 从 pageSlicePos.x 处开始往 X 轴正方向画 Y 轴网格
    const yPageSliceTotal = Math.ceil(canvasYWidth / girdSize); // 计算需要绘画y轴的条数
    for (let j = 0; j < yPageSliceTotal; j++) {
        gridComponents.push(<Line
            x={girdSize * j}
            y={0 - stagePos.y}
            strokeWidth={strokeWidth}
            shadowOpacity={shadowOpacity}
            shadowEnabled={shadowEnabled}
            points={[0, 0, 0, CanvasHeight]}
            stroke={j === 0 ? zeroColor : j % 5 === 0 ? solidColor : dashedColor}
        />)
    }

    // 从 pageSlicePos.x 处开始往 X 轴负方向画 Y 轴网格
    const yRemaining = stagePos.x;
    const yRemainingTotal = Math.ceil(yRemaining / girdSize);
    for (let j = 0; j < yRemainingTotal; j++) {
        if (j === 0) continue;
        gridComponents.push(<Line
            x={-girdSize * j}
            y={0 - stagePos.y}
            strokeWidth={strokeWidth}
            shadowOpacity={shadowOpacity}
            shadowEnabled={shadowEnabled}
            points={[0, 0, 0, CanvasHeight]}
            stroke={j === 0 ? zeroColor : j % 5 === 0 ? solidColor : dashedColor}
        />)
    }

    // 0 0 位置画一个 10 10 的红色 rect 矩形
    gridComponents.push(<Rect
        x={0}
        y={0}
        width={10}
        height={10}
        fill="red"
    />);

    return (
        <Layer>
            {gridComponents}
        </Layer>
    );
};

4、效果图

网格画法:原生 Canvas、React-konva 画网格,可拖动、可放大缩小、并带有坐标系 0 0 位置辅助线

三、代码

github 链接

码云链接

转载自:https://juejin.cn/post/7133514836667236365
评论
请登录