skip to main content

Jixun's Blog 填坑还是开坑,这是个好问题。

Draw with canvas in React JS

Preface #

Initially I did not plan to use canvas for this task, but to use a div with block of span with solid colours.

However, for some bazaar reasons, that would cause the content to offset 1 pixel towards right and bottom.

Since React works directly with DOM elements, I would need to get the canvas context to draw.

The code #

Firstly, is the base class CanvasComponent, where all drawing component will derive from here.

Where except the data property, other props will be passed straightaway to the underlaying canvas element.

// CanvasComponent.tsx
import React, {CanvasHTMLAttributes, Component} from 'react';

export interface ICanvasComponentProps<T> extends CanvasHTMLAttributes<HTMLCanvasElement> {
  data: T;

  // must be present
  width: number;
  height: number;

abstract class CanvasComponent<P extends ICanvasComponentProps<T>, S, T> extends Component<P, S> {
  protected canvas?: HTMLCanvasElement;
  protected ctx?: CanvasRenderingContext2D;
  protected drawId: number = 0;

  private setupCanvas = (canvas: HTMLCanvasElement) => {
    this.canvas = canvas;
    if (canvas) {
      this.ctx = canvas.getContext('2d')!;

  render() {
    this.drawId = requestAnimationFrame(this.dispatchDraw);

    const { data, ...props } = this.props;

    return (
      <canvas {...props} ref={this.setupCanvas} />

  abstract draw(ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement): void;
  private dispatchDraw = () => {
    if (this.ctx && this.canvas) {
      this.draw(this.ctx, this.canvas);

export default CanvasComponent;

Then the actual component to implement the draw method, for example the StorageBar component to illustrate the storage usage:

import CanvasComponent, {ICanvasComponentProps} from "./CanvasComponent";

interface ICanvasData {
  used: number;

interface IPropsCanvas extends ICanvasComponentProps<ICanvasData> {


interface IState {


class StorageBar extends CanvasComponent<IPropsCanvas, IState, ICanvasData> {
  draw(ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement): void {
    const { data, width, height } = this.props;
    const {
    } = data;

    const w = width - 2;
    const h = height - 2;

    ctx.strokeStyle = '1px solid #000';
    ctx.strokeRect(0, 0, width, height);

    ctx.fillStyle = '#fff';
    ctx.fillRect(1, 1, w, h);

    const usedBar = w * used;
    ctx.fillStyle = '#4d9510';
    ctx.fillRect(1, 1, usedBar, h);

export default StorageBar;

Finally, use it elsewhere like so:

    used: used / total

How it looks #

Actual Screenshot

You can also see it in action at

Creative Commons Licence This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.