냄궁밈수 (밈 생성기)
🧤 프로젝트 개요
원하는 사진을 고른 뒤, 센스 있는 문구를 넣어 자신만의 재밌는 밈을 생성할 수 있는 웹 페이지
📲 링크
Git. https://github.com/hoonjoo-park/Namgoong-Meme-Soo
배포 주소. https://namgoong-meme-soo.vercel.app/
🤷 사이트 이름이 왜 냄궁밈수인가..?
사실 특별한 이유는 없다.. 그냥 친구들과 얘기를 나누던 중 ‘밈‘이 들어가는 사이트 이름 작명을 부탁해봤다.
그러다가 그냥 우스갯 소리로, 영화 설국열차에서 크리스 에반스의 인상 깊은 대사였던(?) “Are You 냄궁밈수?”라는 헛소리가 나왔다. 그래서 친구들과 웃다가 “그래~ 그냥 그걸로 하자~”하며 사이트 이름이 냄궁밈수가 되었다.
🚚 밈 텍스트 박스 드래그 이동 기능
입력한 밈 텍스트가 마우스 드래깅을 통해 원하는 위치로 이동될 수 있도록 기능을 구현했다.
밈 제작의 기본 중 기본은, 텍스트 엘리먼트를 마우스 드래깅을 통해 위치를 자유자재로 조정할 수 있도록 하는 것이라 생각했다. 이에 따라 여러 고민을 하던 중, 예전에 드래그&슬라이드 기능을 구현했던 방법과 유사한 접근 방법을 사용하여 엘리먼트 이동 기능을 구현해보기로 결정했다.
onMouseDown
(시작) →onMouseMove
(이동) →onMouseUp
(종료)
이와 같은 로직을 따르면 될 것 같았기에 위의 세 이벤트 리스너와 핸들링 함수를 사용했다.
onMouseDown
우선, 텍스트 박스 위에서 mouseDown
이 됐을 때 저장해야 하는 값들이 꽤 많았다.
- 마우스의 초기 좌표값 :
e.pageX
,e.pageY
- 텍스트 박스의 초기 위치값 :
offsetLeft
,offsetTop
- 마우스가 다운됐는지 여부 : isDown(
boolean
)
이러한 값들을
handleMouseDown
이라는 이벤트 핸들러 함수에서 이하와 같이 처리해줬다.
1 | const handleMouseDown = (e: React.MouseEvent<HTMLElement>) => { |
onMouseMove
mousemove
이벤트의 경우에는, 마우스가 텍스트 박스 위에서 움직일 때를 리스닝 하는 것이 아닌, window
위에서 마우스가 움직이는 것을 리스닝 하도록 처리해줘야 했다. 박스 위에서의 mousemove
를 리스닝하도록 하면, 마우스가 박스 밖으로 나갔을 때 텍스트박스가 이동을 멈추는 문제들이 발생했기 때문이다.
따라서, 위에서 사용했던
isDown
을 활용해window
에addEventListener
를 통해mousemove
이벤트 리스너를 추가해줬다.
1 | // isResizing은 아래에서 설명할 텍스트박스 리사이징 기능에서 활용되는 state다. |
즉, mouseDown
이 됐을 때 → useEffect
를 통해 window
에 mousemove
이벤트 리스너가 활성화 된다.
이제 마우스가 움직일 때를 핸들링하는 함수 handleMouseMove
에 대해 다뤄보도록 하겠다.
1 | const handleMouseMove = (e: MouseEvent) => { |
좀 더 쉽게 설명하자면, 위의 사진과 같이 마우스를 클릭한 뒤,마우스가 움직인 거리만큼 텍스트박스가 이동되는 것이다.
조금 더 자세히 toMoveLeft
와 연관지어 풀어 써보자면, 아래의 사진과 같이 표현할 수 있을 것이다.
이 때문에 위에서 const toMoveLeft = e.pageX - startX + startLeft
라는 계산식이 도출된 것이다.
이를 다시 풀어서 설명하면,
마우스의 현재위치(e.pageX) - 처음mouseDown
됐을 때의 위치(startX) = 마우스가 이동한 거리
startLeft = 원래 offsetLeft 위치
따라서 마우스가 이동한 거리 + 원래 offsetLeft 값
을 더해주면 → 마우스 드래그에 따른 박스의 위치(X)값이 계산되는 것이다.
이제 값을 구했으니 textMover라는 함수를 사용해 실제 박스를 해당 위치로 이동시켜주기만 하면 된다.
1 | // inputRef는 text박스를 의미한다 |
onMouseUp
이제 텍스트 이동이 끝났을 때 이벤트들을 모두 remove 해주기만 하면 기능 구현이 모두 끝난다.
만약, onMouseUp에 따른 이벤트 리무빙을 해주지 않는다면, 텍스트가 계속해서 마우스를 따라다닐 것이다.
따라서 아래와 같이 이벤트 리스너들을 제거해줘야 한다.
1 | const handleStopMoving = () => { |
이렇게 까지만 구현해주면, 텍스트 박스가 마우스 드래깅에 따라 원하는 위치로 이동되도록 하는 기능 구현이 모두 끝났다!
물론, 추후에 코드들을 조금 더 간결하고 가독성이 좋도록 리팩토링을 더 진행해볼 계획이다.
🕹 밈 텍스트 박스 리사이징 기능
사용자 입장에서 생각했을 때, 밈 텍스트의 위치조정 뿐만 아니라 텍스트 사이즈도 자유롭게 조절될 수 있으면 훨씬 더 편하게 밈을 만들 수 있을 것이라 생각했다.
따라서 텍스트 박스 엘리먼트의 크기가 조절될 수 있도록 함과 동시에, 폰트 사이즈 또한 박스 크기에 비례하여 줄어들고 늘어날 수 있도록 기능을 구현해보고자 했다.
resizer 버튼 배치하기
위의 gif에 나타나 있듯이, 텍스트 박스 모서리에 네 개의 리사이저를 배치했다.
1 | <Text |
mouseDown 이벤트 활용하기
그리고 각 리사이저들에
onMouseDown
이벤트 핸들러를 할당해줬다.
onMouseDown
에 할당된 resizeStart
함수는 위에서 사용했던 handleMouseDown
핸들러 함수와 굉장히 유사하다.
1 | const resizeStart = (e: React.MouseEvent, type: string) => { |
그리고 위와 동일하게, useEffect
를 활용하여 리사이징이 시작됐을 때(onMouseDown
) window에 전역적으로 mousemove
와 mouseup
이벤트 핸들러를 할당해 줬다.
1 | useEffect(() => { |
mouseMove 이벤트를 통해 박스 크기 조절하기
가장 어려웠던 부분이다.
처음에는 그냥 마우스의 이동 거리를 매 이벤트마다 재할당 해주며 사이즈를 줄이거나 늘려주면 될 것이라 생각했지만, mousemove 이벤트를 setState가 따라가지 못해서 정확한 사이징이 이루어지지 않았다.
따라서, 차라리 계속 변경되는 엘리먼트의 사이즈를 계속 업데이트 해주는 것 보다는, 초기 width값을 고정시켜두고 마우스의 매 이동거리가 아닌, 총 이동거리를 계속 차감해주는 방식을 선택했다.
코드는 이하와 같다.
1 | const mouseResizing = (e: MouseEvent) => { |
이 경우는 그냥 음수값만큼 startWidth에 더해주면 저절로 사이즈가 줄어들도록 값을 줄 수 있다.
하지만 이 경우는 movedX가 양수임에도, 의도한 기능은 width가 줄어들어야 하기 때문에 startWidth에서 해당 값을 차감해줘야 한다.
1 | const handleResize = (movedX: number, movedY: number) => { |
onMouseUp
위에서 사용한 mouseUp 핸들링 함수와 거의 유사하다.
1 | const stopResizing = () => { |
💾 DOM을 이미지로 저장하기
dom-to-image와 file-saver 라이브러리를 활용했다.
설치
1 | $ npm install dom-to-image file-saver |
코드 활용 로직
useRef
로 이미지 박스 DOM에 직접적으로 접근한다 → dom-to-image를 통해 해당 DOM을 스크린샷 찍은 후 파일 url을 얻는다 → 해당 url을 file-saver
에 전달하여 로컬에 사진이 저장될 수 있도록 한다.
1 | const saveImage = async () => { |
프로젝트 회고
음… 가벼운 마음으로 시작했지만, 굉장히 손도 많이 가고 꼼꼼함이 필요했던 프로젝트였던 것 같다.
그리고 처음으로 배포를 한 뒤에, 주변 지인들에게 사이트 활용을 부탁했고, 지인들로부터 보완사항 및 피드백을 받아 git issue에 등록했다. 그리고 매일 한 두개씩의 이슈들을 해결하려 노력했다.
특히 아래의 세 가지 기능들이 지인들로 부터 받은 피드백이었고, 해당 기능들을 추가 및 보완하기 위해 굉장히 애를 썼다.
- 텍스트 추가 및 삭제 기능
- 텍스트별 폰트 색상 적용 기능
- 텍스트박스 및 폰트 리사이징 기능
“우선 배포해라, 어차피 고쳐야 할 것들은 산더미다” 라는 말이 굉장히 와닿았던 프로젝트였다.
앞으로 다른 프로젝트를 할 때에도, 최대한 유저 입장에서 고민하며 개발을 하고, 배포 후에도 유저들의 피드백에 귀 기울일 수 있는 개발자가 되도록 노력해야겠다.
굉장히 유의미한 경험이었다. 🧏♂️