3
\$\begingroup\$

I'm trying to work HTML5 canvas video into a React application while making the code as clean as possible. What kind of improvements should be made to improve the performance utilizing the React to its fullest potential?

import React, { Component } from 'react';
import './App.css';
import video from './video.mp4';
import watermark from './watermark.png';

class App extends Component {
constructor() {
    super();
    this.state = {};
}

componentDidMount() {

const canvas = document.createElement('canvas');
canvas.width = 1280;
canvas.height = 720;
const context = canvas.getContext('2d');
context.drawImage(document.querySelector('video'), 0, 0, 720, 1280);
                    context.drawImage(
                        document.querySelector('.watermark'),
                        parseInt(document.querySelector('select').value),
                        parseInt(document.querySelector('select').value)
                    );
                    if (document.querySelector('input[name=live]').checked) {
                        this.setState({ image: canvas.toDataURL() });
                    }               

}


render() {
    const range = [];
    for (let i = 0; i < 1280; i++) {
        range.push(i);
    }

    return (
        <div className="app"> 

            <video  src={video} controls />

            <div>
            <div className="watermarkButton" style={{}}>

                <span className="watermarkButtonX">Watermark X
                <select className="positionX">
                    {range.map(i => (
                        <option key={i}>{i}</option>
                    ))} </select></span>

                <span className="watermarkButtonY">Watermark Y
                <select className="positionY">
                    {range.map(i => (
                        <option key={i}>{i}</option>
                    ))} </select></span>



                <span>Live</span>
                <input type="checkbox" name="live" />
                </div>


                <button className = "watermarkButton watermarkSubmit"
                    onClick={() => {
                        const canvas = document.createElement('canvas');
                        canvas.width = 1280;
                        canvas.height = 720;
                        const context = canvas.getContext('2d');
                        context.drawImage(document.querySelector('video'), 0, 0, 1280, 720);
                        context.drawImage(
                            document.querySelector('.watermark'),
                            parseInt(document.querySelector('.positionX').value),
                            parseInt(document.querySelector('.positionY').value)
                        );
                        this.setState({ image: canvas.toDataURL() });
                    }}> Watermark! </button>


                <img alt="watermarks" className="watermark" src={watermark} style={{ visibility: 'visible' }}  />
                <img alt="watermarks"   className="imageDisplay" height="405px" width="560px" src={this.state.image} />
            </div>
        </div>
    );
                }

            }

export default App;
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

For loop inside Render()

The loop gets called each time the component re-renders, it's a 1280 pushes to an array and that can be a performance issue, you can set it in componentDidMount or even outside the class since it's not dependent on its data,

Consider Array.from to create and fill the array without creating an intermediate one.

const range = Array.from({length: 1280}, (_, i) => i);

And since you're filling it with ints and the select value is an int you don't really need parseInt.

Using document.querySelector to read values

Basically, you can't easily rely on the input field because the state needs to come from the React app's state, not from the browser's idea of what the value should be.

See more explanation in this blog post.

You should instead use the state to store the values of the inputs. ( see code snippet below )

Accessing the DOM using document.querySelector

In React, we do not directly manipulate the actual DOM. Instead, we must manipulate the virtual representation and let React take care of changing the browser's DOM.

Check this article for in depth explanation and the different ways to access the DOM.

Using refs, instead of doing document.querySelector('video'), you can add a ref like : <video src={video} controls ref={video => this.video = video}/> then use this.video to do what you would do with a regular html element.

Using html5 canvas with react

import React, { Component } from 'react';
import './App.css';
import video from './video.mp4';
import watermark from './watermark.png';

const range = Array.from({ length: 1280 }, (_, i) => i);

class App extends Component {
  constructor() {
    super();
    this.state = {
      select: 0,
      live: false,
      positionX: 0,
      positionY: 0,
      checked: false
    };
  }

  componentDidMount() {
    const context = this.canvas1.getContext('2d');
    context.drawImage(this.video, 0, 0, 720, 1280);
    context.drawImage(this.watermark, this.state.select, this.state.select);

    if (this.state.live) {
      this.setState({ image: canvas.toDataURL() });
    }
  }

  render() {
    return (
      <div className="app">
        <video src={video} controls ref={video => this.video = video} />
        <div>
          <div className="watermarkButton" style={{}}>

            <span className="watermarkButtonX">Watermark X
              <select
                className="positionX"
                onChange={value => this.setState({ select: value, positionX: value })}
                value={this.state.positionX}>
                {range.map(i => (
                  <option key={i} value={i}>{i}</option>
                ))}
              </select>
            </span>

            <span className="watermarkButtonY">Watermark Y
              <select
                className="positionY"
                onChange={value => this.setState({ select: value, positionY: value })}
                value={this.state.positionY}>
                >
                {range.map(i => (
                  <option key={i} value={i}>{i}</option>
                ))}
              </select>
            </span>

            <span>Live</span>
            <input
              type="checkbox"
              name="live"
              checked={this.state.live}
              onChange={e => this.setState({ live: e.target.value })}
            />
          </div>


          <button className="watermarkButton watermarkSubmit"
            onClick={() => {
              const context = this.canvas2.getContext('2d');
              context.drawImage(this.video, 0, 0, 1280, 720);
              context.drawImage(this.watermark, this.state.positionX, this.state.positionY);
              this.setState({ image: canvas.toDataURL() });
            }}> Watermark! </button>

          <canvas ref={canvas => this.canvas1 = canvas} width={1280} height={720} />
          <canvas ref={canvas => this.canvas2 = canvas} width={1280} height={720} />

          <img
            alt="watermarks"
            className="watermark"
            src={watermark}
            style={{ visibility: 'visible' }}
            ref={watermark => this.watermark = watermark}
          />
          <img
            alt="watermarks"
            className="imageDisplay"
            height="405px"
            width="560px"
            src={this.state.image}
          />
        </div>
      </div>
    );
  }

}

export default App;
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.