OPY-bbt / OPY-bbt.github.io

my webpage
https://opy-bbt.github.io/
0 stars 0 forks source link

React中使用声明式操作canvas #21

Open OPY-bbt opened 4 years ago

OPY-bbt commented 4 years ago
import React, { useRef, useState, useEffect } from 'react';

const { Provider, Consumer } = React.createContext();

function App() {
  const mouseIsDown = useRef(false);
  const initialX = useRef(null);
  const initialY = useRef(null);
  const downX = useRef(null);
  const downY = useRef(null);
  const [pos, setPos] = useState({ x: 5, y: 5 });

  function handleMouseDown(ev) {
    mouseIsDown.current = true;
    initialX.current = pos.x;
    initialY.current = pos.y;
    downX.current = ev.clientX;
    downY.current = ev.clientY;
  }

  function handleMouseMove(ev) {

    if (mouseIsDown.current) {
      setPos({
        x: initialX.current + ev.clientX - downX.current,
        y: initialY.current + ev.clientY - downY.current,
      });
    }
  }

  function handleMouseUp() {
    mouseIsDown.current = false;
  }

  return (
    <Canvas
      width={400}
      height={600}
    >
      <Box
        width={100}
        height={100}
        x={pos.x}
        y={pos.y}
        mousedown={handleMouseDown}
        mousemove={handleMouseMove}
        mouseup={handleMouseUp}
      />
    </Canvas>
  );
}

function Canvas(props) {
  const canvasRef = useRef(null);
  const elements = useRef({});

  useEffect(function() {
    draw();
  });

  function draw() {
    const ca = canvasRef.current;
    const ctx = ca.getContext('2d');

    ctx.clearRect(0,0,props.width, props.height);
    Object.values(elements.current).forEach(function(element) {
      element.draw(ctx);
    });
  }

  function respondToEvent(ev) {
    Object.values(elements.current).forEach(function(element) {
      const { props } = element;
      if (ev.type in props) {
        const pos = relativeMousePos(ev);
        const isInside = getIsInside(pos, props);
        if (isInside) {
          props[ev.type](ev);
        }
      }
    });
  }
  function getIsInside(pos, props) {
    const { x, y } = pos;
    const { x: ex, y: ey, width, height } = props;
    return x >= ex && x <= ex + width && y>= ey && y <= ey + height;
  }
  function relativeMousePos(ev) {
    const ca = canvasRef.current;
    const rect = ca.getBoundingClientRect();
    return {
      x: ev.clientX - rect.left,
      y: ev.clientY - rect.top,
    }
  }
  function updateElement(payload) {
    elements.current = {
      ...elements.current,
      [payload.id]: payload,
    };
  }

  const {width, height} = props;

  return (
    <Provider value={{updateElement}}>
      <canvas
        ref={canvasRef}
        width={width}
        height={height}
        onMouseDown={respondToEvent}
        onMouseMove={respondToEvent}
        onMouseUp={respondToEvent}
      >{props.children}</canvas>
    </Provider>
  );
}

const genID = () =>
'_' +
Math.random()
  .toString(36)
  .substr(2, 9);

function Box(props) {
  return (
    <Consumer>
      {
        ({updateElement}) => (
          <BoxElement
            {...props}
            updateElement={updateElement}
          ></BoxElement>
        )
      }
    </Consumer>
  );
}

function BoxElement(props) {
  const id = useRef(genID());
  function draw(ctx) {
    const { color, height, width, x, y } = props;
    ctx.fillStyle = color || 'green';
    ctx.fillRect(x, y, width, height);
    ctx.save();
    ctx.beginPath();
    ctx.rect(x, y, width, height);
    ctx.restore();
  }

  useEffect(function() {
    props.updateElement({
      id: id.current,
      draw,
      props,
    });
  });
  return null;
}

export default App;