Overnap

Merge branch 'feature/canvas' into feature/room

1 +import React, { useCallback, useEffect, useRef, useState } from 'react';
2 +import { Vector } from './types';
3 +
4 +// 참고 : https://basketdeveloper.tistory.com/79
5 +
6 +export const Canvas: React.FC = () => {
7 + const canvasRef = useRef<HTMLCanvasElement>(null);
8 +
9 + const [mousePosition, setMousePosition] = useState<Vector>({ x:0, y:0 });
10 + const [isPainting, setIsPainting] = useState(false);
11 +
12 + const getCoordinates = useCallback((event: MouseEvent): Vector | undefined => {
13 + if (!canvasRef.current) {
14 + return;
15 + } else {
16 + return {
17 + x: event.pageX - canvasRef.current.offsetLeft,
18 + y: event.pageY - canvasRef.current.offsetTop
19 + };
20 + }
21 + }, []);
22 +
23 + const drawLine = useCallback((prev: Vector, current: Vector) => {
24 + if (canvasRef.current) {
25 + const context = canvasRef.current!.getContext('2d');
26 + if (context) {
27 + context.strokeStyle = 'black';
28 + context.lineJoin = 'round';
29 + context.lineWidth = 5;
30 +
31 + context.beginPath();
32 + context.moveTo(prev.x, prev.y);
33 + context.lineTo(current.x, current.y);
34 + context.closePath();
35 +
36 + context.stroke();
37 + }
38 + }
39 + }, []);
40 +
41 + const startPaint = useCallback((event: MouseEvent) => {
42 + const coordinates = getCoordinates(event);
43 + if (coordinates) {
44 + setIsPainting(true);
45 + setMousePosition(coordinates);
46 + }
47 + }, []);
48 +
49 + const paint = useCallback(
50 + (event: MouseEvent) => {
51 + // 드래그 방지
52 + event.preventDefault();
53 + event.stopPropagation();
54 +
55 + if (isPainting) {
56 + const newMousePosition = getCoordinates(event);
57 + if (mousePosition && newMousePosition) {
58 + drawLine(mousePosition, newMousePosition);
59 + setMousePosition(newMousePosition);
60 + }
61 + }
62 + },
63 + [isPainting, mousePosition]
64 + );
65 +
66 + const exitPaint = useCallback(() => {
67 + setIsPainting(false);
68 + }, []);
69 +
70 + useEffect(() => {
71 + if (canvasRef.current) {
72 + const canvas: HTMLCanvasElement = canvasRef.current;
73 +
74 + canvas.addEventListener('mousedown', startPaint);
75 + canvas.addEventListener('mousemove', paint);
76 + canvas.addEventListener('mouseup', exitPaint);
77 + canvas.addEventListener('mouseleave', exitPaint);
78 +
79 + return () => {
80 + canvas.removeEventListener('mousedown', startPaint);
81 + canvas.removeEventListener('mousemove', paint);
82 + canvas.removeEventListener('mouseup', exitPaint);
83 + canvas.removeEventListener('mouseleave', exitPaint);
84 + };
85 + }
86 + }, [startPaint, paint, exitPaint]);
87 +
88 + return (
89 + <div className='mx-3 px-2 py-1 rounded shadow'>
90 + <canvas ref={canvasRef} width='512' height='384' />
91 + </div>
92 + );
93 +}
...\ No newline at end of file ...\ No newline at end of file
...@@ -24,3 +24,8 @@ export interface ChatData { ...@@ -24,3 +24,8 @@ export interface ChatData {
24 sender: string; 24 sender: string;
25 message: string; 25 message: string;
26 } 26 }
27 +
28 +export interface Vector {
29 + x: number;
30 + y: number;
31 +}
......
1 import React, { useContext } from 'react'; 1 import React, { useContext } from 'react';
2 import { useLocation } from 'react-router'; 2 import { useLocation } from 'react-router';
3 import { Main } from '../components/common/Main'; 3 import { Main } from '../components/common/Main';
4 +import { Canvas } from '../components/room/Canvas';
4 import { Chat } from '../components/room/Chat'; 5 import { Chat } from '../components/room/Chat';
5 import { RoomInfo } from '../components/room/RoomInfo'; 6 import { RoomInfo } from '../components/room/RoomInfo';
6 import SocketContext from '../contexts/SocketContext'; 7 import SocketContext from '../contexts/SocketContext';
...@@ -9,7 +10,8 @@ export const Room: React.FC = () => { ...@@ -9,7 +10,8 @@ export const Room: React.FC = () => {
9 return ( 10 return (
10 <Main> 11 <Main>
11 <RoomInfo /> 12 <RoomInfo />
12 - <div className='w-full'> 13 + <div className='w-full flex'>
14 + <Canvas />
13 <Chat /> 15 <Chat />
14 </div> 16 </div>
15 </Main> 17 </Main>
......