1) 화면
<Stage>
<Layer>
<Img {...background} ref={backgroundRef} width={stageWidth} height={stageHeight}> // 2) 배경
{nodes & nodes.map(node => <Node {...node}/>} // 3) 노드
</Layer>
</Stage>
Konva의 Stage와 Layer를 활용해서 편집기 화면을 구성했다. 회고 2.의 레이아웃의 주 화면이다.
2) 배경
배경은 주 화면과 똑같은 크기의 Img 오브젝트를 배치해서 해당 오브젝트의 색을 변경하거나, 이미지를 삽입해 배경으로 쓸 수 있게 했다.
Konva는 오브젝트들을 순서대로 그린다. 즉, <Img>가 맨 앞에 위치해 있기 때문에 다른 노드들보다 뒤에 있어서 배경처럼 딱 보이게 된다. 회고 3.의 background 속성을 배경에 {...background}로 뿌려줘서 속성들을 반영하게 했다.
3) 노드
노드들은 속성을 <Node/>에 뿌려준다. 이 노드는 Konva에서 제공해주는 것이 아니고 내가 만들었다. node에는 type이 들어 있어 얘를 보고 이미지 인지 텍스트인지를 알 수 있다.
if (node.type === "TEXT") return <Text {...node}/>
else if (node.type === "IMAGE") return <Image {...node}/>
이렇게 조건부로 렌더링하게 했다.
4) 노드 추가
ㄱ. 텍스트
텍스트 추가는 버튼 입력시 화면 중앙에 "텍스트를 입력해주세요." 라고 써 있는 텍스트 상자를 추가하게 했다. 방식은 기존 nodes의 상태를 업데이트하는 것이다. setNodes(prev => [...prev, newText] 이런 식으로 만들었다.
ㄴ. 이미지
이미지도 화면에 보이게 하는 방식은 똑같다. 단지 내부의 이미지를 불러오거나 외부의 url을 활용해서 imgObject 생성 후에 newImage의 속성에 넣어준 것의 차이가 있다.
5) 편집
ㄱ. 배경 속성 변경 - 색상, 이미지, 밝기, 대조도
상태로 관리되는 배경의 속성을 변경하면 최상위 컴포넌트가 다시 렌더링되면서 Stage가 다시 그려진다. 그러면서 변경된속성이 자연스레 반영된다. 밝기, 대조도는 Slider를 활용해서 사용자들이 쉽게 조절하게 했다. Slider에 onChange 함수를 줘서 값이 변경될때 마다 상태를 업데이트하게 했다.
ㄴ. 노드 속성 변경
기본 원리는 배경 속성과 똑같다. 단지 텍스트의 경우에 더블클릭해서 textArea를 띄우고 텍스트 변경 후 다른 노드 or 배경 클릭 시 변경사항이 반영되게 했다. 처음에는 textArea에 onChange를 걸어서 했는데, 이렇게 했을 때 historyStack에 ["나", "나는", "나는1"] 이렇게 불필요한 상태가 많이 저장되게 됬다. 그래서 값 변경(typing)시 편집상자의 상태만 변경하고 다른 노드나 배경 클릭 시에 최상위 컴포넌트의 상태를 업데이트하게 했다.
-> 이 편집상자의 위치를 맞추는 것이 어려웠다. 사용자가 봤을 때 편집상자(TextArea)와 텍스트 상자(Konva의 <Text/>)가 똑같이 보여야하는데 이를 위해서 TextArea의 여러 속성들 (lineHeight, letterSpacing )을 매우 세밀하게 엄청 많이 조정해야 했다. 그리고 TextArea의 경우 세로 중앙 정렬을 지원하지 않는다. lineHeight과 height값을 똑같이 주는 꼼수가 있지만 텍스트를 여러 줄 쓰면 뭉개지기 때문에 다른 방법을 사용했다.
*** 편집상자 입력된 텍스트 중앙 정렬하기 ***
중앙 정렬 함수내에서 편집상자와 textarea를 하나 만들어서 먼저 입력 텍스트를 다 넣었다. 여러 style 값들을 똑같이 설정하고 입력된 텍스트가 몇 줄인지 구했다. 그 몇줄인지를 찾은 다음에 lines * target.fontSize 해서 이걸로 편집상자의 높이를 구했다.
정리하면, 입력하면서 n줄이면 n줄 * fontSize해서 현재 글씨들의 높이를 구한다. 그 다음 | 텍스트 상자 높이 - 글씨들 높이| / 2 해서 padding top, bottom을 구해서 넣어준다. 이런 식으로 세로 정렬을 했다. 매우 엄청 어려웠다.
-> "\n" 얘로 파싱하는 건 할 수가 없다. 왜냐면 엔터쳐서 줄바꿈이 되는 경우도 있지만 상자에 꽉차게 써서 넘어가는 경우도 있기 때문이다.
ㄷ. 노드 크기, 위치 변경
konva의 Image, Text 오브젝트는 onDrag, onDragEnd, onTransform, onTransformEnd 같은 좋은 이벤트를 제공한다. 그리고 드래그 시에는 PPT처럼 화면상에서 이동하는 걸로 보인다. 그래서 드래그 끝날 시에 상태를 업데이트해서 위치 변경을 시켜줬다.
크기 변경은 Transformer라는 좋은 오브젝트가 있다. 선택시 얘가 보이게 하고, 얘에 node로 선택된 node의 ref.current를 넣어줬다.
저 점선네모의 꼭짓점 네모를 드래그하면 화면상으로 노드가 커졌다 작아졌다 한다. 그리고 transform, transform 이벤트를 발생시킨다. 그래서 transformEnd일 때 상태를 변경하도록 해서 노드 크기 변경을 구현했다.
6) 보완할 점
(1) 배경 밝기, 대조도 변경 시 historyStack 저장 방식 변경하기 -> Debounce 적용하기
지금 편집기에 뒤로가기, 앞으로 가기 기능이 있는데 배경의 밝기, 대조도 변경시 값이 변경될 때마다 historyStack에 쌓인다. ex) stack [{..., 밝기: 0.1}, {..., 밝기: 0.2}] 그래서 밝기를 변경하고나면 stack이 다 차버려서 사용자가 이전 상태로 돌아가기가 힘들어진다. 그래서 상태 변경은 계속하되 historyStack에는 사용자 행위가 끝나면 저장하는 방식 (Debounce)를 적용해보면 좋을것 같다.
(2) 텍스트 줄 간격 조정하기
여기에 매우 많은 시간을 써서 떠블 클릭시에 상자 테두리가 점선에서 줄선으로 바뀌는 것 같다. 그렇지만 텍스트가 2줄을 넘어가면 약간 편집상자와 텍스트 상자에 차이가 발생한다.
_______ ________
| | | |
|텍스트| 더블클릭 -> | 텍스 |
|상자 | | 트 상자|
_______ _________
약간 이런느낌이다. 줄 당 글자가 다르게 들어간다. 이게 계산을 진짜 열심히 조정했지만 예외가 계속 발생하고 있다. 시간에 쫓겨서 일단 넘어갔지만 추후에 반드시 고치고 넘어가야 한다.
다음 편 예정 - 5. 상태 관리 리팩토링
'프로젝트 회고 > 이미지편집기 개발' 카테고리의 다른 글
6. 개발 - 뒤로가기(작업 취소), 앞으로 가기(복구) Z-index 조정 기능 (0) | 2022.07.20 |
---|---|
5. 개발 - 상태 관리 리팩토링 - useReducer (0) | 2022.07.18 |
3. 개발 - 기본 구조 (0) | 2022.06.19 |
2. 액션 정의하기, 앱 구조와 화면 레이아웃 잡기 (0) | 2022.06.15 |
1. 요구사항 정리와 라이브러리 선정하기 (0) | 2022.06.12 |