React 繪制圖形

最近項(xiàng)目中有一個(gè)需求, 在提供的圖片上畫出圖形區(qū)域,并且圖形可以編輯改變。

問題點(diǎn):
1、圖形為多邊形,畫圖需要canvas來實(shí)現(xiàn);
2、圖片存在縮放的問題,需要讓canvas match渲染出的圖片;

React Canvas

先放結(jié)論:react canvas 繪制2d 畫布有bug,繪制指定兩點(diǎn)坐標(biāo)的圖形錯(cuò)誤。
后來發(fā)現(xiàn),原來從18年開始,react 已不再維護(hù)canvas 相關(guān)api。

  • 原生Canvas繪圖
<canvas id="canvas"></canvas>
....
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");

ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(100, 100);
ctx.stroke();

效果如下


原生canvas
  • React Canvas 繪圖
import React from "react";

export default class CanvasTest extends React.Component {
  constructor(props) {
    super(props);
    this.ref = React.createRef();
  }

  componentDidMount() {
    const { current } = this.ref;
    if (!current) return;
    const ctx = current.getContext("2d");
    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.lineTo(100, 100);
    ctx.stroke();
  }

  render() {
    return (
      <canvas
        ref={this.ref}
        style={{ width: "300px", height: "300px", border: "1px solid #000" }}
      />
    );
  }
}

效果如下


react canvas

兩個(gè)點(diǎn)分別為(0,0), (100,100)。繪制出的線的斜率不一致。

  • 解決辦法:React Konva
    React Konva 是Konva的基于React 的實(shí)現(xiàn)。所有相關(guān)的API 可以查閱Konva中的文檔。
Canvas match 圖片

將canvas 以絕對(duì)定位的方式覆蓋在圖片上。以圖片縮放比例的方式來動(dòng)態(tài)計(jì)算canvas 大小。

// 需要在圖片渲染后再調(diào)用calcViewPosition函數(shù),所以需要根據(jù)實(shí)際的效果添加setTimeout; 
    calcViewPosition = () => {
        if (!this.props.imgSize) {
            return;
        }
        const parent = this.containerRef.current;
        if (!parent) {
            return;
        }
        const parentRect = parent.getBoundingClientRect();
        const parentWidth = parentRect.width;
        const parentHeight = parentRect.height;
        const imageWidth = this.props.imgSize[0];
        const imageHeight = this.props.imgSize[1];

        const ratioX = imageWidth / parentWidth;
        const ratioY = imageHeight / parentHeight;
        let ratio = 0;

        let offset = { left: 0, top: 0 };

        if (ratioX < ratioY) {
            ratio = this.props.objectFitContain ? ratioY : ratioX;
        } else {
            ratio = this.props.objectFitContain ? ratioX : ratioY;
        }
        offset.left = (parentWidth - imageWidth / ratio) / 2;
        offset.top = (parentHeight - imageHeight / ratio) / 2;

        this.setState({
            offset,
            ratio
        });
    }
...
            <div className={styles.container} ref={this.containerRef}>
                {imgSource && (
                    <img src={imgSource} style={{ objectFit: imgObjectFit }} className={styles.img} draggable={false} />
                )}
                <Stage
                    width={size.width / scale}
                    height={size.height / scale}
                    className={stageRectClass}
                    style={style}
                    onMouseUp={this.mouseUpEventHandler}
                    onMouseMove={this.mouseMoveEventHandler}
                >
                    <Layer>
                        <Line points={pointers} stroke={lineColor} closed={true} strokeWidth={rectLineWidth} />
                        <Line
                            points={linePointers}
                            stroke={distanceLineColor}
                            closed={true}
                            strokeWidth={distanceLineWidth}
                        />
                    </Layer>
                </Stage>
            </div>
// Less 
.container {
    position: relative;
    width: 100%;
    height: 100%;
    overflow: hidden;
}

.stage {
    position: absolute;
    z-index: 10;
    top: 0;
    left: 0;
}

.img {
    width: 100%;
    height: 100%;
}

.stageLine {
    .stage();
    z-index: 20;
}

效果如下


demo
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容