【问题标题】:Rendering / Returning HTML5 Canvas in ReactJS在 ReactJS 中渲染/返回 HTML5 Canvas
【发布时间】:2015-07-29 13:15:47
【问题描述】:

我正在尝试使用 React 渲染画布。画布内的图形也将由 Reacts render 方法处理。

当前代码确实使用我想要的图形渲染了一个画布。但我正在寻找一种方法来从render 方法中返回带有我的图形的画布。我想要这个的原因是因为我希望它以“反应方式”工作。

理想情况下,我希望我的代码是这样的:

return(
  <div>
    <canvas id='my_canvas_with_graphics_rendered_in_react'></canvas>
  </div>
);

首先,我想知道这是否可能。或者,如果我应该寻找替代方案。

也许我错过了 React 的要点,如果是这样,请告诉我。我确实希望有一个解决方案,因为从我读过的内容来看,React 将自己吹捧为MVC 中的V。这就是为什么,如果可能的话,这将是我正在尝试做的事情的完美解决方案。我不必担心画布内的渲染。我只需将数据提供给组件,React 就会重新渲染更改。

我已经在return 语句中标记了我认为正确的代码应该放在哪里。

HTML 代码:

<div id='wrapper'>
    <canvas id='canvas'></canvas>
</div>

jsx 代码:

var MyCanvas = React.createClass({
    render:function(){
        var line = this.props.lines;
        var ctx = this.props.canvas.getContext("2d");

        ctx.strokeStyle = 'rgba(0,0,0,0.5)';
        ctx.beginPath();
        ctx.moveTo(line[0].x0, line[0].y0);
        // .... more code .....
        ctx.stroke();

        return(
            <div>
                /* *********************************
                ??? RETURN MY RENDERED CANVAS
                    WITH GRAPHIC LINES. ????
             This is where I want to return the canvas 
                *********************************  */
            </div>
        );

    }
});


var wrapper = document.getElementById('wrapper');
var canvas = document.getElementById('canvas');
var line1 = { x0: 0, y0: 10, x1: 20, y1: 30 };
var line2 = { x0: 40, y0: 50, x1: 60, y1: 70 };

var myCanvas = <MyCanvas canvas={ canvas } lines={ [line1, line2] } />;
React.render(myCanvas, wrapper);

希望我说清楚了。

【问题讨论】:

    标签: javascript reactjs


    【解决方案1】:

    您唯一的错误是您不熟悉 react refs。 试试这个:

    class ConnectionChart extends React.Component {
    
        componentDidMount() {
            let canvas = ReactDOM.findDOMNode(this.refs.myCanvas);
            let ctx = canvas.getContext('2d');
    
            ctx.fillStyle = 'rgb(200,0,0)';
            ctx.fillRect(10, 10, 55, 50);
        }
    
        render() {
            return (
                <div>
                    <canvas ref="myCanvas" />
                </div>
            );
        }
    }
    

    你可能已经摆脱了 ES6 风格,但你明白了。 当然你也可以用其他方法画画^^

    【讨论】:

    • 来自 React docs 的注释:“虽然字符串引用没有被弃用,但它们被认为是遗留的,并且很可能在未来的某个时候被弃用。Callback refs 是首选。 "
    • 很好的答案,但它们现在被认为是遗留问题,请参阅 -> facebook.github.io/react/docs/…
    【解决方案2】:

    您可以这样做:

    var MyCanvas = React.createClass({
      componentDidMount: function () {
        React.getDOMNode(this).appendChild(this.props.canvas);
      },
      render: function() {
        return <div />;
      }
    });
    

    你只需渲染一个空的 div 并等待 React 挂载它。挂载后,您将画布附加到 React 创建的实际 DOM 节点。

    原因是render 方法只返回虚拟 DOM 节点,然后 React 将其转换为真实的 DOM 节点。而且由于您有一个真实的 DOM 节点,因此您无法将其转换为虚拟节点。另一个原因是你应该只返回来自 React 控制的render 的节点。而且由于您是从 Reacts 控制之外管理画布,因此您应该使用真正的 DOM api 来管理它。

    【讨论】:

    • 第一个问题:我在componentDidMount 或render 中调用我的图形绘制例程是否重要?
    • 第二个问题:从 React 内部管理画布(它的创建和引用)是否会更像 Reacts 的做事方式?我的想法是让这个组件成为更大的 React 组件集的一部分。
    • 第一个问题:是的,这很重要。 render 在组件实例的生命周期内可以被调用任意次数,而componentDidMount 只会被调用一次。一定要实现componentWillUnmount 并在那里进行清理。
    • 第二个问题:取决于你想用画布做什么。问题是画布 API 是命令式的而不是声明式的,所以你需要这样的东西来做到这一点:github.com/Flipboard/react-canvas
    • 请注意,不推荐使用 React.getDOMNode():facebook.github.io/react/docs/component-api.html
    【解决方案3】:

    关键是重写正确的 React 生命周期方法来进行绘图。 render 方法创建画布 DOM 元素,你可以添加一个 refs 回调来设置对它的引用,但你必须等到组件安装后才能在画布上绘制。

    这是一个使用 React 文档推荐的回调 ref 的示例。它旨在根据道具渲染静态(非动画)图形组件,并在道具更改时更新。它还订阅窗口调整大小事件,以便可以通过 CSS 动态调整大小(例如width: 100%)。 This example is based on the code from this Gist.

    export default class CanvasComponent extends React.Component {
    
        constructor(props) {
            super(props);
    
            this._resizeHandler = () => {
                /* Allows CSS to determine size of canvas */
                this.canvas.width = this.canvas.clientWidth;
                this.canvas.height = this.canvas.clientHeight;
    
                this.clearAndDraw();
            }
        }
    
        componentDidMount() {
            window.addEventListener('resize', this._resizeHandler);
    
            /* Allows CSS to determine size of canvas */
            this.canvas.width = this.canvas.clientWidth;
            this.canvas.height = this.canvas.clientHeight;
    
            this.clearAndDraw();
        }
    
        componentWillUnmount() {
            window.removeEventListener('resize', this._resizeHandler);
        }
    
        componentDidUpdate(prevProps, prevState) {
            if (this.props.secondRect !== prevProps.secondRect) {
                this.clearAndDraw();
            }
        }
    
        clearAndDraw() {
            const ctx = this.canvas.getContext('2d');
            if (ctx) {
                ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
                this.draw(ctx);
            }
        }
    
        draw(ctx) {
            ctx.fillStyle = 'rgb(200, 0, 0)';
            ctx.fillRect(10, 10, 50, 50);
    
            if (this.props.secondRect) {
                ctx.fillStyle = 'rgba(0, 0, 200, 0.5)';
                ctx.fillRect(30, 30, 50, 50);
            }
        }
    
        render() {
            return (
                <canvas ref={canvas => this.canvas = canvas} />
            );
        }
    }
    

    您可以从父组件中设置secondRect 属性,或者从状态中设置它以在属性更新时查看组件更新。您可以扩展此想法以构建任何类型的数据驱动画布渲染,例如图表或图形。

    【讨论】:

    • 我编辑了这个例子,以展示在重绘画布之前检查componentDidUpdate 中相关道具或状态是否发生变化的最佳实践。如果你不检查 props 或 state 是否真的改变了,你最终可能会比必要的更频繁地重绘画布。
    • 这不再起作用了。请更新您的答案以使用 createRef 和 this.canvas.current
    【解决方案4】:

    我想我会使用 React.createRef() 更新此答案,因为 this.refs 现在是旧版。 Refs and the DOM

    import React from "react";
    import * as PropTypes from "prop-types";
    
    class Canvas extends React.Component {
        constructor() {
            super()
            this.canvas = React.createRef()
        }
        componentDidMount() {
            const ctx = this.canvas.current.getContext('2d')
            this.props.draw(ctx);
        }
        render() {
            return <canvas ref={this.canvas} />;        
        }
    }
    Canvas.propTypes = {
      draw: PropTypes.func.isRequired
    };
    export default Canvas
    

    您可以通过提供绘图上下文来使用此Canvas。

    <Canvas  
      draw={ctx => {
        ctx.beginPath();
        ctx.arc(95, 50, 40, 0, 2 * Math.PI);
        ctx.closePath();
        ctx.stroke();
        }
      }
    />
    

    【讨论】:

      【解决方案5】:

      我移植了 @matthew answer 以使用 React Hooks。如此频繁地响应变化:)

      import React, { useEffect } from 'react';
      import PropTypes from 'prop-types';
      
      const Canvas = ({ draw }) => {
        const canvas = React.createRef();
      
        useEffect(() => {
          const ctx = canvas.current.getContext('2d');
          draw(ctx);
        }, [draw, canvas]);
      
        return <canvas ref={canvas} />;
      };
      
      Canvas.propTypes = {
        draw: PropTypes.func.isRequired,
      };
      
      export default Canvas;
      

      您可以通过提供绘图上下文来使用此 Canvas。

      <Canvas  
        draw={ctx => {
          ctx.beginPath();
          ctx.arc(95, 50, 40, 0, 2 * Math.PI);
          ctx.closePath();
          ctx.stroke();
          }
        }
      />
      

      【讨论】:

        猜你喜欢
        • 2012-03-23
        • 2012-04-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-09-22
        • 2014-03-13
        • 2012-08-01
        • 2017-06-06
        相关资源
        最近更新 更多