Búsqueda de sitios web

typescript - HTML Canvas: El elemento dibujado sigue haciendo zoom o desplazándose


No estoy recibiendo ningún error, pero cuando presiono el ratón y muevo el ratón, el objeto en el lienzo se mueve en la dirección correcta pero no se detiene, simplemente se desliza fuera del lienzo, lo mismo sucede cuando hago zoom con la rueda del ratón, hace zoom pero de nuevo no se detiene.

Espero que el elemento en el lienzo deje de hacer zoom cuando deje de mover la rueda del ratón, también espero que el elemento deje de desplazarse cuando suelte el ratón después de arrastrarlo.

CodeSanbox: https://codesandbox.io/s/react-typescript-sdx09

El componente de React que carga el lienzo es

import React, { useEffect, useRef } from 'react';
import { Grid } from './grid';

const App: React.FC = () => {
  const canvasRef = useRef<HTMLCanvasElement>(null);


  const setGrid = () => {
    const grid = new Grid({w: 800, h: 800});
    if (canvasRef.current) {
      const canvas = canvasRef.current;
      const ctx = canvas.getContext('2d');
      if (ctx) {
        let now = 0;
        const tickLoop = (epoch: number) => {
          const tick = epoch - now;
          now = epoch;
          ctx.clearRect(0, 0, 800, 800);
          grid.update(tick);
          grid.draw(ctx);
          requestAnimationFrame(tickLoop);
        };
        requestAnimationFrame(tickLoop);
      }
    }
  };

  useEffect(() => {
    setGrid();
  });

  return (
    <React.Fragment>
      <canvas
        ref={canvasRef}
        width={800}
        height={800}
      ></canvas>

    </React.Fragment>
  );
};

export { App };

El componente importado llamado grid...

import { InputHandler } from './inputHandler';

export interface WHInterface {
  w: number;
  h: number;
}

export interface ControlInterface {
  view: { x: number; y: number; zoom: number };
  viewPos: { prevX: number | null; prevY: number | null; isDragging: boolean };
}

export interface InputHandlerInterface {
  setZoom(event: WheelEvent): void;
  setPan(event: MouseEvent, mouseDown: boolean): void;
  setMouseDown(event: MouseEvent, mouseDown: boolean): void;
}

class Grid implements InputHandlerInterface {
  canvasSize: WHInterface;
  controls: ControlInterface;

  constructor(canvasSize: WHInterface) {
    this.canvasSize = canvasSize;
    this.controls = {
      view: { x: 0, y: 0, zoom: 1 },
      viewPos: { prevX: null, prevY: null, isDragging: false },
    };
    new InputHandler(this);
  }


  update(tick: number) {
    //
  }

  draw(ctx: CanvasRenderingContext2D) {
    ctx.fillStyle = 'grey';
    ctx.fillRect(0, 0, 800, 800);

    ctx.translate(this.controls.view.x, this.controls.view.y);
    ctx.scale(this.controls.view.zoom, this.controls.view.zoom);
    ctx.fillStyle = 'white';
    ctx.fillRect(20, 20, 20, 20);
  }

  setMouseDown(event: MouseEvent, mouseDown: boolean) {
    const { x, y } = event;
    if (mouseDown) {
    this.controls.viewPos.isDragging = true;
    this.controls.viewPos.prevX = x;
    this.controls.viewPos.prevY = y;
    }
    if (!mouseDown) {
      this.controls.viewPos.isDragging = false;
      this.controls.viewPos.prevX = null;
      this.controls.viewPos.prevY = null;
    }
  }
  setPan(event: MouseEvent, mouseDown: boolean) {
    const { x, y } = event;
    if (mouseDown) {
      const pos = {x, y};
      let dx;
      let dy;
      if (this.controls.viewPos.prevX) dx = x - this.controls.viewPos.prevX;
      if (this.controls.viewPos.prevY) dy = y - this.controls.viewPos.prevY;

      if (this.controls.viewPos.prevX || this.controls.viewPos.prevY) {
        if (dx) this.controls.view.x += dx;
        if (dy )this.controls.view.y += dy;
        this.controls.viewPos.prevX = pos.x;
        this.controls.viewPos.prevY = pos.y;
      }
    }
  }

  setZoom(event: WheelEvent) {
    const {x, y, deltaY} = event;
    const direction = deltaY > 0 ? -1 : 1;
    const factor = 0.0005;
    const zoom = 1 * direction * factor;

    const wx = (x - this.controls.view.x) / (800 * this.controls.view.zoom);
    const wy = (y - this.controls.view.y) / (800 * this.controls.view.zoom);

    this.controls.view.x -= wx * 800 * zoom;
    this.controls.view.y -= wy * 800 * zoom;
    this.controls.view.zoom += zoom;
  }
}

export { Grid };

Y el manejador de entrada

import { InputHandlerInterface } from './grid';

class InputHandler {
  mouseDown: boolean;
  constructor(grid: InputHandlerInterface) {
    this.mouseDown = false;

    window.addEventListener(
      'wheel',
      (event: WheelEvent) => {
        grid.setZoom(event);
      },
      false
    );
    window.addEventListener(
      'mousedown',
      (event) => {
        this.mouseDown = true;
        grid.setMouseDown(event, true);
      },
      false
    );
    window.addEventListener(
      'mouseup',
      (event) => {
        this.mouseDown = false;
        grid.setMouseDown(event, false);
      },
      false
    );
    window.addEventListener(
      'mousemove',
      (event) => {
        grid.setPan(event, this.mouseDown);
      },
      false
    );
  }
}

export { InputHandler };