-
인터랙티브 웹툰 Turn Off 회고Project 2023. 2. 6. 15:46
졸업작품 뭐하지? 4학년 1학기를 종강한 후 머리에 스친 생각... 사실 1년도 넘게 하던 걱정이었는데 진짜 시작해야겠다는 자각이 현실로 닥쳐와 버렸다. 1학년 때 같았으면 모션그래픽이나 그래픽 디자인 아니면 웹툰을 그려서 낼 생각이었지만, 그사이에 코딩을 접해버린 나에게 졸업작품은 무조건 그와 연관시켜버리고 싶다는 의욕이 생겼다. 그럼 다시 돌아와서 "졸업작품 뭐하지?"라는 질문을 던졌을 때 나온 결론은 '디자인 or 그림 그리고 웹 개발을 접목할 수 있는 콘텐츠를 만들어야겠다!'였다.
다음 단계는 구체적으로 무슨 콘텐츠를 만들까였다. 스크롤에 따라 움직이는 애니메이션, 온라인 방 탈출, 등등... 생각나는 콘텐츠들은 많이 있었다. 그중에서도 가장 현실적으로 완성할 수 있는 콘텐츠는 웹툰이었다. 웹툰은 웹 개발이랑 별로 상관없다고 생각하겠지만 나는 웹툰에 참여형 요소를 넣은 인터랙티브 웹툰을 만들어야겠다고 생각했다. 하일권 작가의 '마주쳤다'의 경우 독자가 직접 웹툰에 등장하여 다양한 기술 요소를 통해 이야기가 전개되는 형식을 보고 나 또한 비슷한 형식으로 만들고 싶었다.
사실 무슨 이야기를 담아야 하는지에 대한 고민이 제일 컸다. 다양한 장르와 소재들을 마구잡이로 생각해내다가 결국엔 다 포기하였고 원점으로 돌아가 다시 천천히 생각했다. 지금 내가 제일 잘할 수 있는 이야기와 사람들이 공감할만한 이야기를 떠올려보니 그건 바로 '불면증'에 관한 이야기였다. 그 당시 12시에 누워도 6시까지 잠에 들지 않아 괴로웠던 기억들이 많았고 잠이 들지 않은 경험은 사람들이 한 번씩은 경험했으리라 생각했다. 그래서 이를 토대로 이야기를 발전시켜나가게 되었다.
스토리
늦은 밤, 승현은 잠이 오지 않아 괴로워한다. 승현의 반려견 뭉게는 꿈속에서 승현의 생각(컴퓨터)과 연결하게 되고, 컴퓨터 종료할 수 있도록 도와준다. 5개의 프로그램들을 종료 후 컴퓨터를 끄게 된 뭉게. 아침이 되고 언제 잠이 든 건지 개운하게 일어난 승현은 기운차게 하루를 시작한다.
자고 싶어도 못 자는 이유는 ‘생각이 많아서’라는 결론이 항상 나왔다. 미래에 대한 걱정, 온갖 중요하지 않은 잡생각, 인간관계, 방금 보았던 유튜브 동영상 등 머릿속이 복잡하기 때문에 ‘아직 잠이 들 준비가 되지 않았구나’라는 생각이 들었다. 그리고 이와 비슷하다고 느꼈던 환경이 바로 내가 사용하고 있는 노트북(컴퓨터)이었다. 항상 수많은 프로그램, 인터넷 페이지를 열어 놓은 작업표시줄을 보면 마치 나의 복잡한 머릿속과 같다고 느껴졌다. 그래서 내 생각을 컴퓨터에 빗대어 만약 실행된 프로그램들을 종료하고 컴퓨터를 종료하게 된다면 비로소 잠을 잘 수 있는 준비가 되었다고 할 수 있지 않을까?라는 생각이 들었다. 따라서 ‘실행된 프로그램을 종료하기’를 사용자와 상호작용 할 수 있는 미션으로 적용하게 되었다. 게임적 요소들을 넣어 단계별로 미션을 해결해 나아가는 구성으로 계획을 하였다.
또한 이 이야기를 보면 ‘승현’의 반려견 ‘뭉게’가 미션을 수행하는 주인공의 역할이게 된다. 어렸을 때부터 강아지를 키워본 입장에서 아침 일찍 등교할 때 항상 이불 속에서 곤히 잠들어 있는 강아지의 모습을 보고선 아무 때나 걱정 없이 잠을 잘 수 있다는 점이 내심 부러웠다. 또 어떤 꿈을 꾸고 있을지도 궁금하였다. 이런 마음을 토대로 이야기에 반영하게 되었다.
이제 실질적인 제작에 대한 이야기를 시작한다.
주요 라이브러리
- gatsby
- styled-components
- framer-motion
처음엔 create-react-app을 통해 프로젝트를 셋업 하였다. 이유는 그 라이브러리가 익숙했기 때문에 초반을 어느 정도 빠르게 만들고 나서 학과에 보여주어 컨펌 받을 생각이었다. 내가 하고자하는 작품은 전에 누가 했을 가능성이 적고 확실하게 이런 걸 만들거다!라는 모습을 보여주고 싶었다. 먼저 이후 개발 중간에 gatsby로 마이그레이션을 하였다. 이미지가 많은 사이트인데 gatsby에서 최적화하기에 좋은 플러그인이 있다는 글을 보았다. 다른 이유로는 컴포넌트 폴더 네이밍을 전체적으로 바꾸어 관리하고 싶어서 마이그레이션 하는 김에 변경 하였다.
스타일링에서는 컴포넌트별로 스타일이 종속되길 원했고 자바스크립트와 CSS의 상태 공유를 통해 스타일을 동적으로 작성하기를 원해서 styled-components을 선택하였다. 그리고 css animation을 편리하게 사용하기 위해 framer-motion 라이브러리를 사용하였다.
[메인화면]
세로 스크롤 기법을 사용 중인 기존의 웹툰들을 반영하여 화면 레이아웃을 세로로 긴 형식으로 만들었다.
메뉴는 새로시작, 이어보기, 진행 방식 총 3가지로 구성된다. 새로시작은 웹툰을 처음부터 감상하게 되고 이어보기는 독자의 진행 상태에 따라 이어보기를 할 수 있다. 진행 상태를 저장하기 위해 React Context API를 사용하였고 로컬 스토리지를 사용하여 브라우저 창을 닫아도 계속 볼 수 있게 하였다. 진행 방식은 말 그대로 이 웹툰을 어떻게 진행하는지에 대한 설명을 적어놓았다. 오른쪽 상단을 보면 2개의 버튼이 보이는데 하나는 전체화면 기능이고 하나는 홈 버튼이다. 전체화면의 경우 react-full-screen 라이브러리를 사용하였다.
[웹툰]
INTRO
늦은 밤, 승현은 불면증에 괴로워한다. 뭉게는 승현을 위로하기 위해 다가가지만 소통이 되지 않아 슬퍼한다. 이후 승현은 잠이 든 뭉게를 보고 부러워한다. 한편 뭉게는 꿈속에서 승현의 생각 컴퓨터와 연결된다.MAIN
승현의 생각 컴퓨터와 연결된 뭉게, 컴퓨터를 종료하려면 진행 중인 작업을 모두 완료하라는 메시지 창을 받는다. 이를 본 뭉게는 현재 실행 중인 프로그램들을 종료하기 시작한다.
프로그램을 종료하기 위해 총 6단계로 진행이 된다. 게임 형식으로 미션을 진행해야 다음 미션으로 넘어간다. 잠에 방해하는 요소들을 비유적으로 표현하거나 실제 컴퓨터나 핸드폰에 있는 애플리케이션들을 표현하였다.
1. 메모장 지우기
- 주인공의 중요하지 않은 잡생각들을 표현. 메모장을 지움으로써 복잡한 생각들을 지워버림
- 메모장들을 정신없이 화면에 꽉 차게 보이게 하여 복잡함을 표현
2. 메시지 답장
- 주인공의 인간관계. 주인공을 항상 응원하는 가족들에 대한 고마움을 표현
- 답장 시 부적절한 단어에 대한 필터링을 적용
3. 사진 보정
- 주인공의 꿈, 디자이너로서 사용하는 프로그램(ex포토샵)을 표현
4. 영상 시청 기록 지우기
- 흔히 많은 사람이 밤에 유튜브 영상을 시청하다가 늦게 자는 경우가 생각이 났고, 시청 기록과 영상의 알고리즘을 끔으로써 역시 잠이 들 준비를 하는 과정을 표현
5. 보안 최적화, 치료 작업 진행
- 최종적으로 여러 항목에 대한 최적화 및 치료 작업을 진행함으로써 완전히 안정되고 편안한 상태임을 표현
6. 휴지통 비우기
- 휴지통을 비움으로써 얻는 정리되고 상쾌한 기분을 표현OUTRO
아침이 되고 승현은 일어난다. 오랜만에 개운한 상태로 일어나서 의아해한다. 아직 자는 뭉게를 보며 귀여워하며 웃는 승현, 오늘 하루도 힘차게 산다고 다짐한다.웹툰의 첫장면. 버튼을 누르면 낮 -> 밤으로 바뀌고 다음 장면으로 이동할 수 있다. 요즘 많이 사용되는 다크모드에서 영감을 받았고 작품에 꼭 사용해보고 싶었다. 아이콘과 말풍선 애니메이션 등 움직이는 요소들은 framer-motion의 도움을 받았다. styled-components에서도 적용이 가능하다.
import { motion } from "framer-motion"; <motion.div animate={{ opacity: [0, 1], y: [10, 0] }}></div> //styled-components const DIV = styleds(motion.div)``; <DIV animate={{ opacity: [0, 1], y: [10, 0] }}></div>
위와 같이 텍스트 애니메이션과 요소를 드래그하는 기능도 framer-motion을 사용하였다. 편리하게 멋진 모션들을 적용할 수 있어 좋았다.
css filter 속성을 이용하여 이미지 보정 기능을 만들었다. 조절은 input의 range 타입을 적용했다. 나름 포토샵에서 보정할 때의 기분이 나서 만들면서 재밌어했다. ㅎㅎ 사실 구현보다는 이미지에 들어갈 그림을 그리는 게 더 힘들었다... 이미지를 보정한다는 느낌이 더 살게 그라데이션이랑 채색할 때 단색보다는 명암을 주는 걸로 결정했다. (하지만 채색을 어려워해서 맨날 단색으로 칠하는 나) 그래도 나름 열심히 그렸다.
gatsby-plugin-image의 사용
gatsby에서 이미지를 최적화하기 플러그인으로 gatsby-plugin-image를 제공한다. 내 이미지들을 정적 이미지이기 때문에 StaticImage를 사용하였다. 레이아웃이나 크기들을 구성할 수 있다.
import { StaticImage } from "gatsby-plugin-image" export function Dino() { return ( <StaticImage src="../images/dino.png" alt="A dinosaur" placeholder="blurred" loading="eager" layout="fixed" width={200} height={200} /> ) }
나의 콘텐츠는 만화의 컷, 말풍선 등 이미지로 주로 이루어진다. 그렇기 때문에 이미지 Layout Shift 현상이 생겨 독자는 레이아웃이 깨진 화면을 볼 수 있다. 이때 width와 height를 지정하여 넣으면 이미지의 공간을 유지하여 이 현상을 방지할 수 있었다.
(관련글)
Loading behavior for the image. You should set this to "eager" for above-the-fold images to ensure they start loading before React hydration.
또한 loading 속성은 이미지의 로드 동작을 정할 수 있다. 디폴트 값은 "lazy"이다. React hydration 전에 이미지 로드를 시작하려고 하려면 "eager"로 지정해주면 된다고 한다. 스크롤이 발생하지 않은 페이지라 모든 이미지에 eager 값을 주었다. hydration이라는 개념이 생소한데 클라이언트 자바스크립트를 사용하여 서버에서 렌더링 된 HTML에 인터랙티브을 추가하는 과정이라고 한다.
gatsby에서 HTML 콘텐츠가 React DOM 서버 측 API를 사용하여 정적으로 생성된다. 이 정적 HTML 콘텐츠는 React hydration을 통해 클라이언트 측 JavaScript를 사용하여 인터랙티브한 웹페이지를 만들게 된다.
이미지 Webp 포맷의 사용
'web'을 위해 만들어진 효율적인 이미지 포맷 형식인 Webp를 사용하였다. 기존의 PNG, JPG 파일보다 훨씬 파일의 크기가 작다. 이미지가 들어갈 화면의 max-width를 500px으로 작성했다. 그보다 큰 픽셀로 작업한 그림을 포토샵을 이용해 사이즈 조정을 하였고 포토샵 Webp 플러그인을 설치하여 Webp 포맷으로 이미지를 저장하였다. (알아보니 Photoshop 23.2 부터는 WebP 파일 포맷을 완벽하게 지원한다고 한다!)
폰트 최적화
폰트는 나눔고딕체를 사용하였다. @font-face를 사용하여 폰트에 관한 설정을 할 수 있다. src 속성을 이용하면 사용될 파일을 지정할 수 있다. local은 독자의 로컬 환경에서 이미 해당 폰트가 있을면 그 폰트를 사용할 수 있다. 없다면 콤마로 계속 지정해나갈 수 있는데 보통 woff2, woff, ttf 등의 순으로 지정한다. woff 포맷은 기존의 oft, ttf 포맷보다 압축된 형식이라 웹에서 사용하기 최적화되어 있다. woff2는 기존의 woff 보다 더 압축된 형식이다.
텍스트를 렌더링하는 방식으로는 FOIT과 FOUT이 있다. FOIT은 웹폰트가 적용되기 전까지는 텍스트를 보여주지 않다가 적용되면 나타나게 하고 FOUT은 웹폰트가 적용되기 전에도 텍스트를 보여준다. 크롬 같은 경우 FOIT 방식이다. 나의 경우 텍스트가 항상 보이게 하고 싶어 font-display 속성에 swap을 지정하였다.
@font-face { font-family: "NanumGothicBold"; src: local("NanumGothicBold"), url("fonts/NanumGothicBold.woff2") format("woff2"), url("fonts/NanumGothicBold.woff") format("woff"); font-display: swap; }
Lazy Loading (전)
Lighthouse를 사용하여 웹의 성능을 확인하였더니 Performance 부분에서 낮은 점수가 나왔다. 이유는 'Reduce unused JavaScript'였다. 하나의 파일에 아직 사용되지 않는 모든 코드가 들어있다 보니 최초의 로딩 시간이 길어졌기 때문이다. 이를 해결하기 위해 코드 분할 (Code Splitting)을 사용하여 Laze Loading을 하였다. 나의 콘텐츠의 경우 Cut이라는 단위, 독자가 보게 되는 화면을 기준으로 나누게 된다.
import React, { Suspense, lazy } from "react"; const Cut1 = lazy(() => import("components/step/act1/Cut1"));
createPages와 @loadable/component (후)
진행 단계에 따라 페이지를 나누는 방향으로 리팩토링을 진행하였다. gatsby의 createPages API를 사용하여 사전에 미리 페이지를 생성 후 컴포넌트를 매칭시켰다.
exports.createPages = async ({ actions }) => { const { createPage } = actions for (let i = 1; i <= 80; i++) { createPage({ path: `/play/${i}`, component: path.resolve("src/components/StepControl.jsx"), context: { step: i, }, }) } }
stepControl 컴포넌트에서는 넘겨준 context의 step에 따라 컴포넌트를 매칭시켜주었다. 이때 컴포넌트들을 다 import하니 이전처럼 초기 로딩 시간이 길어져서 이번엔 @loadable/component 라이브러리를 사용하였다. 이전엔 컴포넌들을 하나하나씩 lazy 코드을 쓰느라 코드가 길어졌는데 컴포넌트 경로를 담은 배열에 따라 라이브러리에서 제공하는 dynamic import를 사용하니 코드 길이가 짧아져서 만족스러웠다. 특정 step 페이지에 왔을때 step+1 페이지의 컴포넌트들을 프리로드하는 preload 메소드를 사용하였다.
const Component = loadable(({ path }) => import(`components/step/${path}`), { cacheKey: (props) => props.path, }) const stepComponentsPath = [ null, "act1/Act1Cut1", ...생략 ] const StepControl = ({ pageContext }) => { const { step } = pageContext const { action } = useControl() useEffect(() => { action.set(step) if (step < stepComponentsPath.length - 1) { preloadComponent(stepComponentsPath[step + 1]).preload() } }, [step, action]) return <Component path={stepComponentsPath[step]} /> }
Electron으로 데스크탑 앱 배포
학과 조교님과의 면담을 통해 완성된 작품을 제출할 시 인터넷에 의존적인 환경에서 말고도 작품을 보관할 수 있었으면 하는 요청이 있었다. 그래서 인터넷 환경에선는 Netlify를 통해 배포를 진행하였고 따로 제출을 위해 Electron으로 데스크탑 앱을 들게 되었다. 의외로(?) 간단히 코드 몇 줄로 만들 수 있어 신기하였다.
그 외 아쉬운 점
- 앞서 웹툰의 진행을 위해 숫자를 이용하여 관리하였는데 이후 교수님의 피드백을 수용하여 컷을 더 추가해야 하는 상황이 생겼다. 중간에 컷을 추가하면 그 뒤 컷의 숫자들을 모두 수정해야 하는 상황이 생겨버린다. 보다 더 유연하게 삭제, 추가를 할 수 있는 방법이 없을까에 대한 고민이 있다.
아무튼 졸업작품을 무사히 끝마치게 돼서 후련한 마음이 든다.
내가 만들고 싶던 콘텐츠를 완성하였고, 좋은 경험이었다고 생각한다.
+ 또한 제출 후에 리팩토링을 진행하였는데 코드를 보면서 그때 생각하지 못한 점들이 지금은 보여서 신기하였다.
반응형'Project' 카테고리의 다른 글
마피아 G 마스터 회고 (0) 2023.11.02 [Chrome Extension] 크롬 확장 프로그램 Text Highlighter (2) 2023.06.09 ideal idea 회고 (0) 2023.04.03 MOVIE ROOM 회고 (0) 2023.02.12