[Vanilla JS] About Me 프로젝트 - 페이지 구성하기
About Me 친구들이 생각하는 나는 어떤 사람일까? 친구들의 마음이 모여 나의 새싹을 키워보세요!
지난 SPA 구조 만들기 에 이어서 프로젝트의 페이지 및 컴포넌트 구성에 대해 이야기해보겠습니다.
About Me 컨셉은 나의 새싹을 만들어 키우는 것입니다. 나의 새싹을 만들면, 색상 및 종류가 랜덤하게 정해지고 id가 부여됩니다. id를 통해 링크가 생성되며 링크를 친구들에게 공유하여 나에 대한 생각을 받습니다. 친구들에게 입력받은 단어/문장들이 비가 되어 나의 새싹을 키우게 됩니다.
📃페이지 및 컴포넌트 구성 1 2 3 4 5 6 7 8 9 10 11 12 13 ... ├── components │ ├── Hader.js │ ├── AboutMe.js │ ├── MoneyLayer.js │ └── PasswordLayer.js ├── pages │ ├── New.js │ ├── About.js │ └── Home.js ...
페이지
생성 페이지(New.js): 새싹을 생성하는 페이지, 모든 사용자들이 입력했던 값들이 보여진다.
작성 페이지(About.js): 공유받은 링크로 들어와 친구에 대해 작성하는 페이지
메인 페이지(Home.js): 만든 새싹과 친구들에게 입력받은 단어/문장들이 보여지는 페이지
컴포넌트
헤더 컴포넌트(Header.js): 공유, 기부 버튼이 있는 헤더
메인 컴포넌트(AboutMe.js): 새싹이 보여지고, 단어/문장들이 비처럼 떨어지는 컴포넌트
기부 컴포넌트(MoneyLayer.js): 카카오 송금하기로 링크되는 레이어
비밀번호 컴포넌트(PasswordLayer.js): 비밀번호를 입력받는 레이어
🎊생성 페이지 pages/New.js components/AboutMe.js components/PasswordLayer.js
생성 페이지에서는 나의 새싹을 생성할 수 있는 버튼과 안내 문구가 필요합니다. 생성 버튼을 누르면 비밀번호를 입력할 수 있는 레이어가 노출됩니다. 그리고 뒷 배경으로 모든 사용자의 입력 값을 예시로 보여지도록 합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import AboutMe from "@/components/AboutMe" ;import PasswordLayer from "../components/PasswordLayer.js" ;export default class New { constructor ({ $target } ) { this .$target = $target; } async render ( ) { const $div = document .createElement ("div" ); $div.setAttribute ("class" , "new-container" ); const $button = document .createElement ("button" ); $button.setAttribute ("class" , "create-btn" ); $button.textContent = "나의 새싹 만들기" ; $button.addEventListener ("click" , async () => { new PasswordLayer ({$target : this .$target }).render (); }); const $info = document .createElement ("span" ); $info.setAttribute ("class" , "info" ); $info.innerHTML = "친구들의 관심을 모아 새싹을 자라나게 해주세요!<br>새싹 -> 중간 -> 트리 형태로 자라납니다<br>나무의 형태, 색상은 모두 랜덤으로 생성됩니다." ; $div.appendChild ($button); $div.appendChild ($info); this .$target .appendChild ($div); new AboutMe ({ $target : this .$target , id : "new" }).render (); } }
위와 같이 new-container를 클래스 명으로 갖는 div 태그를 만들고 그 안에 버튼과 문구를 만들어 넣어줍니다. button에는 click이벤트를 넣어주고 클릭 시 PasswordLayer가 랜딩될 수 있도록 구현합니다. 그리고 AboutMe 컴포넌트도 new라는 id를 넘겨주고 랜더링 시켜줍니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 export default class PasswordLayer { constructor ({ $target, callback } ) { this .$target = $target; this .callback = callback; } render ( ) { if (document .querySelector ('.layer-container' )) { document .querySelector ('.layer-container' ).outerHTML = '' ; return ; } const $div = document .createElement ('div' ); $div.setAttribute ('class' , 'layer-container' ); const $closeBtn = document .createElement ('button' ); $closeBtn.setAttribute ('class' , 'close-btn' ); const $closeImg = document .createElement ('img' ); $closeImg.setAttribute ('src' , require ('@/assets/images/close.svg' )); $closeImg.setAttribute ('alt' , 'close' ); $closeBtn.appendChild ($closeImg); $closeBtn.addEventListener ('click' , () => { $div.outerHTML = '' ; }); const $h3 = document .createElement ('h3' ); $h3.textContent = '비밀번호' ; const $form = document .createElement ('form' ); $form.setAttribute ('class' , 'pw-box' ); const $input = document .createElement ('input' ); $input.setAttribute ('class' , 'pw-input' ); $input.setAttribute ('type' , 'password' ); $input.addEventListener ('change' , () => { $input.classList .remove ('error' ); }); const $button = document .createElement ('button' ); $button.setAttribute ('class' , 'pw-btn' ); $button.textContent = '완료' ; $form.addEventListener ('submit' , (e ) => { e.preventDefault (); if ($input.value ) { this .callback ({ password : $input.value }); } else { $input.classList .add ('error' ); } }); $form.appendChild ($input); $form.appendChild ($button); $div.append ($closeBtn, $h3, $form); this .$target .appendChild ($div); } }
PasswordLayer는 닫기 버튼과 타이틀, 비밀번호 입력창, 완료 버튼으로 구성됩니다. 레이어의 중복 노출 방지를 위해 .layer-container 클래스를 갖는 요소를 찾아 랜딩되기 전 이미 랜딩되어있다면 닫히도록 처리했습니다. input과 button은 form 태그로 감싸서 submit 이벤트로 합쳐지게 처리했으며, e.preventDefault()를 통해 submit시 새로고침되는 것을 방지하였습니다. callback 함수를 인자로 받아서 레이어가 생성되도록 해서 레이어 이후 콜백함수를 처리할 수 있도록 구현하였습니다.
💻작성 페이지 pages/About.js, components/PasswordLayer.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 import PasswordLayer from '../components/PasswordLayer.js' ;export default class About { constructor ({ $target } ) { this .$target = $target; this .id = window .location .hash .split ('#/about/' )[1 ]; } render ( ) { const $div = document .createElement ('div' ); $div.setAttribute ('class' , 'wrap' ); const $title = document .createElement ('h1' ); $title.textContent = '나는 어떤 사람이야?' ; const $inputBox = this .createInputBox (); const $info = document .createElement ('span' ); $info.setAttribute ('class' , 'info' ); $info.innerHTML = 'ex) 마음이 따듯해. 예뻐. 냉정해. 극I...<br>친구를 떠올렸을 때 생각나는 말을 적어주세요.' ; const $goNew = document .createElement ('a' ); const $goNewImg = document .createElement ('img' ); $goNewImg.setAttribute ('src' , require ('@/assets/images/sprout.svg' )); $goNewImg.setAttribute ('alt' , 'goNew' ); $goNew.setAttribute ('class' , 'go-new' ); $goNew.textContent = '내 새싹 만들러가기' ; $goNew.appendChild ($goNewImg); $goNew.addEventListener ('click' , () => { window .urlChange ('/' ); }); const $goMy = document .createElement ('a' ); $goMy.setAttribute ('class' , 'go-my' ); $goMy.textContent = '내 새싹 보러가기💚' ; $goMy.addEventListener ('click' , () => { new PasswordLayer ({ $target : this .$target , callback : async ({ password }) => { ... }, }).render (); }); $div.append ($title, $inputBox, $info, $goNew, $goMy); this .$target .appendChild ($div); } createInputBox ( ) { const id = window .location .hash .split ('#/about/' )[1 ]; const $inputBox = document .createElement ('form' ); $inputBox.setAttribute ('class' , 'input-box' ); const $input = document .createElement ('input' ); $input.setAttribute ('type' , 'text' ); const $sendBtn = document .createElement ('button' ); $sendBtn.setAttribute ('class' , 'about-send-btn' ); const $sendBtnImg = document .createElement ('img' ); $sendBtnImg.setAttribute ('src' , require ('@/assets/images/send.svg' )); $sendBtnImg.setAttribute ('alt' , 'send' ); $sendBtn.appendChild ($sendBtnImg); $inputBox.appendChild ($input); $inputBox.appendChild ($sendBtn); $inputBox.addEventListener ('submit' , (e ) => { e.preventDefault (); ... }); return $inputBox; } }
작성 페이지는 타이틀과 사용자에게 입력받는 인풋창, 그리고 새싹 만들러 가기, 내 새싹 보러 가기 링크로 구성되어 있습니다. 내 새싹 보러 가기를 클릭하면 비밀번호 레이어가 노출되고, 비밀번호를 입력하면 만들었던 새싹 페이지로 랜딩되어야 합니다.
🖼메인 페이지 pages/Home.js components/AboutMe.js components/Header.js components/MoneyLayer.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import AboutMe from "../components/AboutMe.js" ;import Header from "@/components/Header" ;export default class Home { constructor ({ $target } ) { this .$target = $target; } render ( ) { const id = window .location .hash .split ("#/me/" )[1 ]; new Header ({ $target : this .$target }).render (); new AboutMe ({ $target : this .$target , id }).render (); } }
메인 페이지는 Header.js와 AboutMe.js 컴포넌트로 구성됩니다. Header.js에는 기부하기와 공유하기 기능이 있고, AboutMe.js에는 사용자의 새싹과 입력받은 문자들을 노출해주는 기능이 있습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 export default class AboutMe { constructor ({ $target, id } ) { this .$target = $target; this .id = id; } async render ( ) { const num = 1 const treeFilter = '' const groundColor = '' const aboutList = [] let type = 'sprout' ; let size = '50px' ; if (aboutList.length > 10 ) { type = `${num} /growing` ; size = '100px' ; } if (aboutList.length > 50 ) { type = `${num} /tree` ; size = '200px' ; } const $div = document .createElement ('div' ); $div.setAttribute ('class' , 'wrap' ); const $ground = document .createElement ('div' ); $ground.setAttribute ('class' , 'ground' ); $ground.style .backgroundColor = `${groundColor} ` ; $ground.style .borderColor = `${groundColor} ` ; const $sky = document .createElement ('div' ); $sky.setAttribute ('class' , 'sky' ); const $aboutMe = document .createElement ('img' ); $aboutMe.setAttribute ('class' , 'about-me' ); $aboutMe.setAttribute ('src' , `../assets/images/${type} .svg` ); $aboutMe.setAttribute ('alt' , 'aboutMe' ); $aboutMe.style .width = size; $aboutMe.style .height = size; $aboutMe.style .filter = treeFilter; $div.append ($sky, $aboutMe, $ground); const $textContainer = document .createElement ('div' ); $textContainer.setAttribute ('class' , 'text-container' ); aboutList.forEach ((al ) => { const $text = document .createElement ('span' ); $text.setAttribute ('class' , 'falling-text' ); $text.style .animationDuration = `${Math .random() * 10 + 4 } s` ; $text.style .left = `${Math .random() * 100 } %` ; $text.style .color = al.color ; $text.textContent = al.content ; $textContainer.appendChild ($text); }); $div.appendChild ($textContainer); this .$target .appendChild ($div); } }
num에 따라 트리의 종류가 결정되고, treeFilter와 groundColor로 새싹과 땅의 색을 지정해줍니다. 이 값들은 생성이 될 때 랜덤하게 생성되도록 설정된 값입니다. 아직은 덤프데이터로 놔두겠습니다. aboutList는 친구들에게 입력받은 나에 대한 문장/문구들이 담긴 배열입니다. {color, content} 형태의 객체로 담기게 되며 color도 생성될 때 랜덤하게 생성이 됩니다. 미리 저장해둔 경로에 맞게 새싹 이미지를 불러오도록 하고 사이즈와 필터도 지정해줍니다. svg파일을 img태그로 지정해놓았기때문에 색상 변동을 filter로 컨트롤합니다. aboutList의 결과값들도 만들어줍니다. 하늘에서 비처럼 떨어지는 애니메이션효과를 css로 주고, 각 요소마다 속도 차이를 주기 위해 랜덤하게 생성될 수 있도록 합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import MoneyLayer from './MoneyLayer' ;export default class Header { constructor ({ $target } ) { this .$target = $target; } render ( ) { const $header = document .createElement ('header' ); const $shareBtn = document .createElement ('button' ); $shareBtn.setAttribute ('class' , 'share-btn' ); const $shareImg = document .createElement ('img' ); $shareImg.setAttribute ('src' , '../assets/images/share.svg' ); $shareImg.setAttribute ('alt' , 'share' ); $shareBtn.appendChild ($shareImg); $shareBtn.addEventListener ('click' , () => { ... }); const $moneyBtn = document .createElement ('button' ); $moneyBtn.setAttribute ('class' , 'money-btn' ); const $moneyImg = document .createElement ('img' ); $moneyImg.setAttribute ('src' , '../assets/images/money.svg' ); $moneyImg.setAttribute ('alt' , 'money' ); $moneyBtn.appendChild ($moneyImg); $moneyBtn.addEventListener ('click' , () => { new MoneyLayer ({ $target : this .$target }).render (); }); $header.appendChild ($shareBtn); $header.appendChild ($moneyBtn); this .$target .appendChild ($header); } }
Header에는 버튼을 2개 만들고, 각 이미지를 지정해줍니다. share버튼의 클릭 이벤트를 만들어주고 추후에 링크가 복사될 수 있도록 구현할 예정입니다. money버튼은 기부를 할 수 있는 레이어를 띄우는 역할로 클릭 이벤트로 MoneyLayer가 노출될 수 있도록 합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 export default class MoneyLayer { constructor ({ $target } ) { this .$target = $target; } render ( ) { if (document .querySelector ('.layer-container' )) { document .querySelector ('.layer-container' ).outerHTML = '' ; return ; } const $div = document .createElement ('div' ); $div.setAttribute ('class' , 'layer-container' ); const $h3 = document .createElement ('h3' ); $h3.textContent = '개발자 밥 사주기🍚' ; const $closeBtn = document .createElement ('button' ); $closeBtn.setAttribute ('class' , 'close-btn' ); const $closeImg = document .createElement ('img' ); $closeImg.setAttribute ('src' , '../assets/images/close.svg' ); $closeImg.setAttribute ('alt' , 'close' ); $closeBtn.appendChild ($closeImg); $closeBtn.addEventListener ('click' , () => { $div.outerHTML = '' ; }); const $moneyBox = document .createElement ('div' ); $moneyBox.setAttribute ('class' , 'money-box' ); const $sojuBtn = this .createMoneyBtn ({ text : '🍺' , url : '...' , }); const $coffeeBtn = this .createMoneyBtn ({ text : '☕️' , url : '...' , }); const $chocolateBtn = this .createMoneyBtn ({ text : '🍫' , url : '...' , }); $div.appendChild ($closeBtn); $div.appendChild ($h3); $moneyBox.append ($sojuBtn, $coffeeBtn, $chocolateBtn); $div.appendChild ($moneyBox); this .$target .appendChild ($div); } createMoneyBtn ({ text, url } ) { const $button = document .createElement ('button' ); $button.setAttribute ('class' , 'dev-money-btn' ); $button.textContent = text; $button.addEventListener ('click' , () => { window .location .href = url; }); return $button; } }
PasswordLayer와 비슷한 형태로 구성되며 각 기부용 버튼을 만들어줍니다. url로는 기부할 수 있는 링크를 생성해서 넣어주면 됩니다. 저는 카카오 송금하기 링크를 활용했습니다.
여기까지 각 페이지와 컴포넌트를 만들어보았고, 다음에는 라우터 설정을 통해 페이지 간 이동을 구현해보도록 하겠습니다.