๐ JS๋ก ๋ฌ๋ ฅ ๋ง๋ค๊ธฐ
๋ค๋ฅธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ด Vanilla JS๋ง์ผ๋ก ๋ฌ๋ ฅ์ ์์ฑํด๋ณด์. ์ฐ์ํํ ํฌ์บ ํ ๋น์ 3๋ฒ์งธ ํ๋ก์ ํธ "๋ฑ ํฌ์๋ฌ๋ ํด๋ก ๋ง๋ค๊ธฐ"์์ ๋ด๊ฐ ๋ด๋นํ๋ ํํธ์ด๋ค. ๋ณต์ตํ๊ณ ์ ๋ฆฌํ๋ ์ฐจ์์์ ๋ค์ ์์ฑํด๋ณด๊ธฐ๋ก ํ๋ค.
์์ฑ๋ณธ ๋ฏธ๋ฆฌ๋ณด๊ธฐ
๋ชฉํ
- DOM ์์ฑ ์ดํ DOM ์กฐ์ํ์ง ์๊ธฐ. ์ฆ ๋ชจ๋ ์์
์ ํ์ด์ง๋ฅผ ์์ฑํ๋ ์์ ์์ ๋๋ด๊ธฐ. document.querySelector์ ๊ฐ์ DOM API๋ฅผ ์ฌ์ฉํ๊ณ ์ ์๊ณ ์ ํ๋ ๊ฒ์ด๋ค. ์ด๋ "์ด์ชฝ์์ A๋ก ์กฐ์ํ๊ณ ์ ์ชฝ์์ B๋ก ์กฐ์ํ๊ณ ๋ ๋ฐ๋ํธ์์ C๋ก ์กฐ์ํ๊ณ ์์ ๊ผฌ์ด๊ณ ์ฝ๋ ์ํค๊ณ " ๊ฐ์ ํ์์ ๋ฐฉ์งํ๊ธฐ ์ํจ์ด๋ค. ๋จ, ์ฒ์ ์ฑ์ ์ ํํ์ฌ initializeํ ๋๋ง
document.querySelector('#App')์ ์ฌ์ฉํ๋ค. - OOP ์๋ฐ์คํฌ๋ฆฝํธ ๋์ ์์ ํจ์๋ค๋ก ์์ฑํ๊ธฐ
์ฌ์ฉํ ์คํ
- Date Object (Vanilla JS)
- Display: Grid (CSS)
1. index.html ์์ฑํ๊ธฐ
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Calendar + Grid</title>
</head>
<body>
<div id="App"></div>
</body>
</html>
VS Code์ ๋ณด์ผ๋ฌ ํ๋ ์ดํธ๋ฅผ ์ฌ์ฉํ๋ค.
2. calendar.js ์์ฑํ๊ธฐ
์ฐ์ index.html์ head ํ๊ทธ ์์ ์ฝ๋๋ฅผ ์ฐ๊ฒฐํ์.
<script src="calendar.js"></script>
2-1. calendar.js์ ์ฌ์ฉํ util ํจ์ ์์ฑํ๊ธฐ
html string highlighting์ ์ํ html ํจ์๋ฅผ ์ถ๊ฐํ๋ค. ์ด ํจ์๋ฅผ ์ถ๊ฐํ๊ณ backtick์ผ๋ก ๊ฐ์ธ์ง JS String ์์ html ๊ธ์๋ฅผ ์ถ๊ฐํ๋ฉด JS String์ html์ฒ๋ผ ํ์ด๋ผ์ดํ
ํ์ฌ ์ฌ์ฉํ ์ ์๋ค.
const html = (s, ...args) => s.map((ss, i) => `${ss}${args[i] || ''}`).join('')
๋งค์ง๋๋ฒ๋ฅผ ์์ ๊ธฐ ์ํด const๋ค์ ์ถ๊ฐํด์ฃผ์๋ค. ์ฝ๋ ์ค ๊ฐ์๊ธฐ 7์ด ํ์ด๋์ค๋ฉด ์ด๋ ๋งฅ๋ฝ์ 7์ธ์ง ์๊ธฐ ์ด๋ ต๊ธฐ ๋๋ฌธ์ด๋ค.
const NUMBER_OF_DAYS_IN_WEEK = 7
const NAME_OF_DAYS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']
๊ฐ์ฅ ๊ธฐ์ด๊ฐ ๋ renderCalendar๋ฅผ ์์ฑํด์ฃผ์๋ค. ๋ํ renderCalendar๋ฅผ ๊ธฐ์กด index.html ์ตํ๋จ์ ์ฐ๊ฒฐํด์ฃผ์๋ค.
const renderCalendar = ($target) => {
$target.innerHTML = getCalendarHTML()
}
<script>
renderCalendar(document.querySelector('#App'))
</script>
๋ฌ๋ ฅ์ ๊ทธ๋ฆฌ๊ธฐ ์ํด์ ์ด 4๊ฐ์ Date ๊ฐ์ฒด๊ฐ ํ์ํ๋ค. ๋ฌผ๋ก ๋ ์ ์ Date ๊ฐ์ฒด๋ก ์ฒ๋ฆฌํ ์๋ ์๋ค. ๋ฌ๋ ฅ์ ๊ตฌํํ ๊ธฐ๋ฅ๋ค์ ๋ฐ๋ผ ํ์ํ Date ๊ฐ์ฒด์ ๊ฐ์๊ฐ ๋ฌ๋ผ์ง๋ค.
- ์ง๋ ๋ฌ ๋ง์ง๋ง ๋ : ๋ฌ๋ ฅ์ ์ง๋ ๋ฌ์ ๊ทธ๋ฆด ๋ ์ผ์์ผ์ ํ์ด๋ผ์ดํธํ๊ธฐ ์ํด ํ์ํ๋ค.
- ์ด๋ฒ ๋ฌ ์ฒซ ๋ : ์ด๋ฒ ๋ฌ์ ํ ์์ผ๊ณผ ์ผ์์ผ์ ํ์ ํ์ฌ ํ์ด๋ผ์ดํธ ํ๊ธฐ ์ํด ํ์ํ๋ค. ๋ํ ์ด๋ฒ ๋ฌ ์ฒซ ๋ ์ ์์ผ์ ํตํด ์ง๋ ๋ฌ ๋ง์ง๋ง ์ฃผ๋ฅผ ๋ฌ๋ ฅ์ ํ์ํ ๋ ํ์ํ๋ค.
- ์ด๋ฒ ๋ฌ ๋ง์ง๋ง ๋ : ์ด๋ฒ ๋ฌ์ ๋ฌ๋ ฅ์ for statement๋ก ๊ทธ๋ฆด ๋ ํ์ํ๋ค.
- ๋ค์ ๋ฌ ์ฒซ ๋ : ๋ฌ๋ ฅ์ ๋ค์๋ฌ์ ๊ทธ๋ฆด ๋ ํ ์์ผ์ ํ์ด๋ผ์ดํธํ๊ธฐ ์ํด ํ์ํ๋ค.
์ด 4๊ฐ์ ๋ ์ง๋ฅผ object๋ก ๋ฌถ์ด returnํด์ฃผ๋ ํจ์๋ฅผ ๋ง๋ค์๋ค. argument๋ก Date ๊ฐ์ฒด 1๊ฐ๋ฅผ ๋ฐ์ผ๋ฉฐ ์ด ๋ฌ๋ ฅ์์๋ "์ค๋"์ ํด๋นํ๋ Date ๊ฐ์ฒด๋ฅผ ๋ฃ์ด์ค ๊ฒ์ด๋ค.
const processDate = (day) => {
const date = day.getDate()
const month = day.getMonth()
const year = day.getFullYear()
return {
lastMonthLastDate: new Date(year, month, 0),
thisMonthFirstDate: new Date(year, month, 1),
thisMonthLastDate: new Date(year, month + 1, 0),
nextMonthFirstDate: new Date(year, month + 1, 1),
}
}
2-2. getCalendarHTML ๋ง๋ค๊ธฐ
์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก ๋ฌ๋ ฅ์ ๊ทธ๋ ค๋ณด์. ๋ฌ๋ ฅ์ ๋ค์ด๊ฐ ๋ด์ฉ์ HTML๋ก ๋ฐํํด์ฃผ๋ getCalendarHTML ํจ์๋ฅผ ๋ง๋ค์๋ค. getCalendarHTML ํจ์๋ ์กฐ๊ธ ๊ฑฐ๋ํด์ ๋จผ์ ํ์ ์ก์์ฃผ์๋ค.
const getCalendarHTML = () => {
let today = new Date()
let { lastMonthLastDate, thisMonthFirstDate, thisMonthLastDate, nextMonthFirstDate } = processDate(today)
let calendarContents = []
// ...
return calendarContents.join('')
}
๋งจ ์์ ์์ผ์ ํ์ํ ์ค์ ์ถ๊ฐํ์. ์ฒ์์ ์ถ๊ฐํ const๋ฅผ ์ฌ์ฉํด์ ๋งค์ง๋๋ฒ๋ฅผ ์ ๊ฑฐํ๋ค.
for (let d = 0; d < NUMBER_OF_DAYS_IN_WEEK; d++) {
calendarContents.push(html`<div class="${NAME_OF_DAYS[d]} calendar-cell">${NAME_OF_DAYS[d]}</div>`)
}
๊ทธ ๋ค์ ์ง๋ ๋ฌ์ ๊ทธ๋ฆฌ์. ์๋ฅผ ๋ค์ด ์ด๋ฒ ๋ฌ์ ์ฒซ ๋ ์ด ์์์ผ์ด๋ผ๋ฉด ์ผ์์ผ, ์์์ผ, ํ์์ผ์ ํด๋นํ๋ ์ง๋ ๋ฌ์ ๊ทธ๋ ค์ฃผ๋ ์ญํ ์ด๋ค. ์ผ์์ผ์ ํด๋นํ๋ ๋ ์ sun HTML Class๋ฅผ ์ถ๊ฐํด์ค๋ค.
for (let d = 0; d < thisMonthFirstDate.getDay(); d++) {
calendarContents.push(
html`<div
class="
${d % 7 === 0 ? 'sun' : ''}
calendar-cell
past-month
"
>
${lastMonthLastDate.getMonth() + 1}/${lastMonthLastDate.getDate() - thisMonthFirstDate.getDay() + d}
</div>`
)
}
๋น์ทํ ์๋ฆฌ๋ก ์ด๋ฒ ๋ฌ์ ๊ทธ๋ฆฌ์. ์ค๋์ ํด๋นํ๋ ๋ ์ today HTML Class์ " today" String์ ์ถ๊ฐํด์ค๋ค. ๋ง์ฐฌ๊ฐ์ง๋ก ํ ์์ผ๊ณผ ์ผ์์ผ์๋ ๊ฐ๊ฐ sat๊ณผ sun HTML Class๋ฅผ ์ถ๊ฐํด์ค๋ค.
for (let d = 0; d < thisMonthLastDate.getDate(); d++) {
calendarContents.push(
html`<div
class="
${today.getDate() === d + 1 ? 'today' : ''}
${(thisMonthFirstDate.getDay() + d) % 7 === 0 ? 'sun' : ''}
${(thisMonthFirstDate.getDay() + d) % 7 === 6 ? 'sat' : ''}
calendar-cell
this-month
"
>
${d + 1} ${today.getDate() === d + 1 ? ' today' : ''}
</div>`
)
}
๋ง์ง๋ง์ผ๋ก ๋จ์ ์นธ๋ค์ ๋ค์ ๋ฌ์ ๋ ์ง๋ค์ ๊ทธ๋ ค์ค๋ค.
let nextMonthDaysToRender = 7 - (calendarContents.length % 7)
for (let d = 0; d < nextMonthDaysToRender; d++) {
calendarContents.push(
html`<div
class="
${(nextMonthFirstDate.getDay() + d) % 7 === 6 ? 'sat' : ''}
calendar-cell
next-month
"
>
${nextMonthFirstDate.getMonth() + 1}/${d + 1}
</div>`
)
}
3. CSS ์์ฑํ๊ธฐ
3-1. display: grid ์ด์ฉํ๊ธฐ
ํ๋์ element์ display: grid๋ฅผ ์ฌ์ฉํ๋ฉด ๊ทธ ์์ element๋ค์ ๊ทธ๋ฆฌ๋(ํ) ์์ ๊น๋ํ๊ฒ ๋ฃ์ ์ ์๋ค.
grid-template-columns: column์ ์ด๋ป๊ฒ ๋ฐฐ์น์ํฌ์ง์ ๋ํ ์ ๋ณด.1fr์1 fraction์ด๋ผ๋ ๋ป์ผ๋ก ์ฌ๊ธฐ์๋ ์ด 7๋ฒ ์์ฑํ์ผ๋ ๋๋น๊ฐ ๋์ผํ 7๊ฐ์ column์ด ์์ฑ๋๋ค.grid-template-rows: row์ ํฌ๊ธฐ๋ฅผ ์ ์ํด์ค ์ ์๋ค. ์ฌ๊ธฐ์๋3remํ๋๋ง ์์ผ๋ ์ฒซ๋ฒ์งธ row๋ฅผ 3rem์ด๋ผ๊ณ ์ ์ํ ๊ฒ์ด๋ค.grid-auto-rows: ์ดํ์ row๋ฅผ ํฌ๊ธฐ๋ฅผ ์ ์ํด์ค ์ ์๋ค. ์ฌ๊ธฐ์๋6rem์ด๋ผ๊ณ ๋์ด ์์ผ๋ ์ดํ์ ๋ชจ๋ row๋ row ํฌ๊ธฐ๊ฐ 6rem์ธ ๊ฒ์ด๋ค.
์๋์๋ ์ถ๊ฐ์ ์ธ ์คํ์ผ์ ์ ์ํด์ฃผ์๋ค.
#App {
/* grid */
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
grid-template-rows: 3rem;
grid-auto-rows: 6rem;
/* style */
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
border: 1px solid black;
max-width: 720px;
margin-left: auto;
margin-right: auto;
}
- ํ ๋ฑ์ ๊ทธ๋ฆด ๋ ๋ง์น ์์
์ฒ๋ผ ๋ชจ๋ ์นธ์ ๊ท ์ผํ ํ
๋๋ฆฌ๋ก ๊ฐ์ธ๊ณ ์ถ์๋ฐ ๊ฐ์ฅ ๋ฐ๊นฅ์ชฝ์ ์
๋ค๋ง ์ ์ด ์์ ๊ฒฝ์ฐ๊ฐ ์๋ค. HTML๋ก ๋ฐ์ง์๋ฉด
th์td์๋ง border๋ฅผ ์ ์ฉํ ์ํ๋ค. - ๋๋ ์ด๊ฒ์ "๋ชจ๋ ์
๋ค ํ
๋๋ฆฌ์ n px, ํ ํ
๋๋ฆฌ์ n px" border๋ฅผ ์ ์ฉํ๋ ๊ฒ์ ์ ํธํ๋ค. ์ด๋ ๊ฒ ํ๋ฉด ์ ์ฒด์ ์ผ๋ก
2n px์ ๊ท ์ผํ ํ ๋๋ฆฌ๊ฐ ์๊ธด๋ค.
.calendar-cell {
border: 1px solid black; /* #App์ ์ ์ฉํ border์ ํจ๊ป 2px์ ๊ท ์ผํ ํ
๋๋ฆฌ๊ฐ ์๊ธด๋ค.*/
padding: 0.5rem;
}
3-2. ํ ์์ผ๊ณผ ์ผ์์ผ, ์ค๋ ํ์ด๋ผ์ดํ
.past-month,
.next-month {
color: gray;
}
.sun {
color: red;
}
.sat {
color: blue;
}
.past-month.sun {
color: pink;
}
.next-month.sat {
color: lightblue;
}
.today {
color: #e5732f;
}
๋๋ ์
- ์ฒ์์ JS์ ์ฐ๊ฒฐํ์ฌ ๋ฌ๋ ฅ์ "initialize"ํ ๋ ์ด์ง ํค๋งธ๋ค.
renderCalendar๋ฅผbody์๋จ์ ์ฐ๊ฒฐํ๊ธฐ ๋๋ฌธ์ด๋ค. DOM์ ์์ฐจ์ ์ผ๋ก ์คํ๋๊ธฐ ๋๋ฌธ์ ์๋จ์ ์ฐ๊ฒฐํ ๊ฒฝ์ฐ#Appdiv๊ฐ ๋ํ๋์ง ์์์ ๋renderCalendar๊ฐ ์คํ๋์ด DOM element๋ฅผ ์ฐพ์ง ๋ชปํ๋ค. - ๋ JS์ ์ฐ๊ด ๊ด๊ณ๋ก ํํ๋ ์ ์๋ ์ฝ๋๋ค์ ์ด๋ป๊ฒ ํ๋ฉด์ ๋ ๋๋งํ๋์ง ๊ธฐ์ต์ด ์ ๋์ง ์์๋ค. ๋จ์ํ ๊ฐ์ฅ ์ฒซ index.js ์ญํ ์ ํ๋ js์์ ์ฑ์ querySelectํ ํ innerHTML๋ก ๋ฃ์ด์ฃผ๋ ๊ฒ์ด์๋ค.
- ์ฐ์ํํ ํฌ์บ ํ ํ๋ก์ ํธ์์๋ ๋งค์ง ๋๋ฒ๋ฅผ ์ฌ์ฉํ์๋ค. ์ด๋ฒ์๋ ๋งค์ง ๋๋ฒ๋ฅผ ์ ๊ฑฐํ์ฌ ๊ฐ๋ ์ฑ์ ํฅ์์์ผฐ๋ค.
- ์ฐ์ํํ ํฌ์บ ํ ํ๋ก์ ํธ๋ Object Orientedํ ์๋ฐ์คํฌ๋ฆฝํธ(๋ ์ ํํ๊ฒ๋ Singleton ํจํด)๋ก ์์ฑ๋์ด ์์๋๋ฐ, ์ด๋ฒ์๋ ์์ ํจ์๋ค๋ก ์ชผ๊ฐ์ ์์ฑํ๋ค.
- ES6+ ๋ฌธ๋ฒ์ ์ฌ์ฉํ๊ธฐ ์ํด ๋ ธ๋ ฅํ๋ค. ์๋ฅผ ๋ค์ด ๋ฐฑํฑ์ ๋ณ์๋ฅผ ๋ฃ๊ฑฐ๋ processDate์ ๋ฐํ ๋ฐ์ดํฐ๋ฅผ destructuringํด์ ์ฌ์ฉํ๋ค. ๋ let๊ณผ const๋ฅผ ์ฃผ๋ก ์ฌ์ฉํ๋ค.
getCalendarHTML๋ฅผ ์กฐ๊ธ ๋ ์งง๊ฒ ์์ฑํ ์ ์์๋์ง ์์ฌ์์ด ๋จ๋๋ค.