Canvas와 mouse event를 이용하여 원하는 위치에 object를 그릴 수 있습니다.
GitHub
https://github.com/hhnn0/drawing-tree
onClick
해당 컴포넌트에서 마우스 클릭이 발생할 때 작동
onMouseMove
해당 컴포넌트에서 마우스 이동이 발생할 때 작동
Canvas
트리 및 그림이 그려질 컴포넌트
SideBar
object 리스트를 제공할 컴포넌트
const [selectedObj, setSelectedObj] = useState<string>('');
선택된 object
const [mousePosition, setMousePosition] = useState<mousePosition>({
positionX: null,
positionY: null,
});
interface mousePosition {
positionX: number|null;
positionY: number|null;
}
현재 마우스의 위치
renderObjList
object list 컴포넌트 생성
handleSelectedObj
selectedObj 할당
handleMousePositionInSideBar
사이드바에서 mousePosition 할당
drawObject
캔버스에 object를 그려줌.
const [objList, setObjList] = useState<string[]>(
[treeObj1, treeObj2, treeObj3, treeObj4, treeObj5, treeObj6, treeObj7]
);
objList의 초기값을 모두 할당해주었습니다.
추후에 object 추가 기능을 염두하여 state로 선언해주었습니다.
const renderObjList = () => {
return objList.map((el, idx) => {
return (
<ObjContainer
backgroundImg={el}
onClick={(e) => {
handleSelectedObj(el);
handleMousePositionInSideBar({
positionX: e.clientX,
positionY: e.clientY,
});
}}
/>
);
});
};
const ObjContainer = styled.div<ObjContainerProps>`
cursor: pointer;
width: 100px;
height: 100px;
background-image: url(${(props)=>props.backgroundImg});
background-size:cover;
overflow:visible;
`
objList 배열의 원소들로 컴포넌트들을 생성해줍니다.
각 컴포넌트에 대해 onClick을통해 selectedObj와 mousePosition을 할당해주는 이벤트를 추가해주었습니다.
<CanvasContainer backgroundImg={Tree}>
<canvas
ref={canvasRef}
width={window.innerWidth}
height={window.innerHeight}
/>
</CanvasContainer>
const CanvasContainer = styled.div<ObjContainerProps>`
position:relative;
width:calc(100% - 120px);
background-image:url(${(props)=>props.backgroundImg});
background-position-x: center;
background-position-y: -130px;
background-size:853px 1280px;
background-repeat: no-repeat;
background-color: #8aacbf87;
`
canvas
의 width와 height는 window 전체로 해주었습니다.
CanvasContainer
의 경우 center 에 트리 이미지를 넣어주었고, SideBar의 크기만큼 width를 빼주었습니다.
//Wrapper component
onMouseMove={(e) => {
if(selectedObj!=='') {
setMousePosition({ ...mousePosition, positionX: e.clientX, positionY: e.clientY });
}
}}
선택된 object가 있을 경우에만 mousePosition에 마우스 위치 할당해줍니다.
마우스를 따라다니는 컴포넌트입니다.
{selectedObj !== "" &&
mousePosition.positionX &&
mousePosition.positionY ? (
<SelectedObj
backgroundImg={selectedObj}
style={{
position: "absolute",
left: mousePosition.positionX,
top: mousePosition.positionY,
}}
onClick={(e) => {
setSelectedObj("");
drawObject({ positionX: e.clientX, positionY: e.clientY });
}}
/>
) : null}
const SelectedObj = styled.div<ObjContainerProps>`
width: 100px;
height: 100px;
background-image: url(${(props) => props.backgroundImg});
background-size: cover;
transform: translate(-50%, -50%);
overflow: visible;
`;
selectedObj state 값이 ''이 아니고(SideBar에서 object 클릭이 일어났을 경우),
mousePosition의 positionX와 positionY가 모두 null이 아닐 경우 생성됩니다.
마우스에 object의 중앙이 오도록 transform: translate(-50%, -50%)
를 해주었습니다.
클릭이 발생할 경우 해당 위치를 drawObject의 parameter로 전달해주고,
selectedObj state를 초기화해줍니다.
const drawObject = (mouseEndPosition: mousePosition) => {
const canvasCur = canvasRef.current as HTMLCanvasElement;
const ctx = canvasCur.getContext("2d");
const objImage = new Image();
objImage.src = selectedObj;
if(ctx === null) return ;
if(!mouseEndPosition.positionX) return ;
if(!mouseEndPosition.positionY) return ;
ctx.drawImage(objImage,mouseEndPosition.positionX-50,mouseEndPosition.positionY-50, 100, 100);
}
object의 크기를 100px로 할당해주었기 때문에,
클릭한 지점에 object의 중앙이 그려지도록 x와 y위치에 각각 -50씩 해주었습니다.