일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- todolist
- Timer
- DOM
- localStorage
- 웹사이트
- darknode
- Calculator
- React
- Typescript
- javascript
- Project
- calender
- CSS
- stopwatch
- html
- 브라우저
- mini_project
- Today
- Total
이야기 정리
타입스크립트 - 캘린더 만들기 본문
개발 동기
사이트 위젯이나, 핸드폰 어플로 흔한 것 중 하나가 바로 캘린더다. 날짜를 확인하는 것은 매일 일어나는 일상적인 일이니 당연하다.
그러나 흔하다고 해서 캘린더가 마냥 단순하지는 않다. 캘린더 마다 기능이나 특색이 다르기 때문이다. 단순히 날짜만 표시하는 캘린더가 있는가 하면, 어떤 캘린더는 일정을 추가할 수도 있다. 영화 예매 등에 쓰이는 캘린더라면 영화가 몇시에 있는지도 나타날 것이다. 이렇듯 캘린더의 변화는 무긍무진하다.
먼저 간단한 캘린더를 만들 것이다. 타입스크립트를 처음 쓰는 만큼 많은 기능을 추가할 수 없었기 때문이다. 캘린더에 날짜를 나타내고, 다른 월로 이동하는 기능을 만들려한다.
사용 툴
- HTML, CSS
- TypeScript
필요한 기능
- 현 날짜에 맞추어 달력을 출력하기
- 버튼 클릭 시 이전 달, 다음 달로 이동하기
- 저번달과 다음달 일자는 회색으로 표시하기
- 오늘 일자는 진한 색으로 표시하기
- 클릭한 A날짜에서 B날짜까지 색상 변경하기
완성된 코드
See the Pen Calender by beren-105 (@beren-105) on CodePen.
코드 전체보기 (깃허브) : https://github.com/beren-105/Mini-Project/tree/main/6_calender
발생한 문제(1)
- 다음달로 이동 시 점점 날짜가 맞지 않았다.
이번달의 날짜를 맞추는데는 성공해 후에 눈치챈 것인데, 다음달로 넘어갈수록 회색으로 표시한 이전달과 다음달이 맞지 않았다. 달을 넘길수록 점점 날짜가 틀어졌다.
해결
이전 달 일자를 계선할 때 기존에는 for 문의 조건인 i가 아닌, for 문 {}블럭 내에서 계산했다. i는 prevLast.getDay()의 날짜에서 증감하고 그 값을 {} 내에서 빼서 계산한 것이다. 그 방법에 문제가 있었다.
블로그를 보며 코드를 수정하고, 거기에 if (prevLast.getDay() !== 6) {}를 추가했다. 저번달 마지막 요일이 6일 경우 보여주지 않고 생략하는 기능을 추가해 열이 틀어지는 것을 수정했다.
//일자 출력하기
if (tableDay instanceof HTMLDivElement) {
if (prevLast.getDay() !== 6) {
for (let i=prevLast.getDate() - prevLast.getDay(); i<=prevLast.getDate(); i++) {
const p = document.createElement('p')
p.classList.add('prev')
p.innerHTML = i.toString()
tableDay.appendChild(p)
}
}
for (let i=1; i<=nowLast.getDate(); i++) {
const p = document.createElement('p')
p.classList.add('now')
p.innerText = i.toString()
tableDay.appendChild(p)
}
if (nowLast.getDay() !== 6) {
for (let i=1; i<=(6 - nowLast.getDay() === 0 ? 1 : 6 - nowLast.getDay()); i++) {
const p = document.createElement('p')
p.classList.add('next')
p.innerText = i.toString()
tableDay.appendChild(p)
}
}
}
발생한 문제(2)
- 이전/다음 달로 이동 시 기존 div가 수정되는 것이 아닌 새로운 div가 생겨난다.
이전/다음달 버튼을 클릭 시 기존 내용이 수정되지 않고 아래에 이전/다음 달 div가 생겨났다. Do to list를 만들었을 때의 경험을 떠올리며 만든 것이 문제였다. 함수 안에 태그를 만드는 것과, 내용을 바꾸는 코드가 동시에 들어가 있었기 때문이다. 이를 해결하기 위해 따로 만들어보기도 했지만, 오류가 발생해 실패했다.
해결
생각 방식을 바꾸었다. 꼭 새로운 div가 생기는 것이 나쁠까? 이걸 이용해서 애니메이션 효과를 만들어보자! 기존 생각한 방식에서는 애니메이션 효과가 없었기에 이런 선택을 했다.
버튼을 클릭하면 기존 날짜는 옆으로 밀려나고, 새로운 날짜만이 나타난다. 계속 div를 생성하기만 하면 불필요한 데이터가 늘어나니 애니메이션이 끝나면 필요없는 div를 삭제했다.
// 버튼 클릭 이벤트
const prevBtn = document.querySelector('.prevBtn')
const nextBtn = document.querySelector('.nextBtn')
let index = 0
prevBtn?.addEventListener('click', () => {
index++
prevLast = new Date(date.getFullYear(), date.getMonth()-index, 0)
nowLast = new Date(date.getFullYear(), (date.getMonth()+1)-index, 0)
day(nowLast, prevLast)
if (tableDays instanceof HTMLDivElement) {
tableDays.style.transition = '0.5s'
tableDays.style.transform = `translateX(-${364}px)`
}
setTimeout(remove,500)
})
nextBtn?.addEventListener('click', () => {
index--
prevLast = new Date(date.getFullYear(), date.getMonth()-index, 0)
nowLast = new Date(date.getFullYear(), (date.getMonth()+1)-index, 0)
console.log(prevLast.getMonth())
day(nowLast, prevLast)
if (tableDays instanceof HTMLDivElement) {
tableDays.style.transition = '0.5s'
tableDays.style.transform = `translateX(-${364}px)`
}
setTimeout(remove,500)
})
function remove() {
tableDays?.firstChild?.remove()
if (tableDays instanceof HTMLDivElement) {
tableDays.style.transition = '0s'
tableDays.style.transform = `translateX(0px)`
}
}
발생한 문제점(3)
- 일자 클릭 이벤트에서 querySelectorAll로 p태그가 선택되지 않는다.
3일을 클릭 후 6일을 클릭하면 3일~6일까지 활성화되는 이벤트를 만들던 중 발생한 문제다. day()함수에서 만든 p 태그들이 화면에는 보이지만, querySelector로는 선택되지 않았다. 아래와 같은 구조였다.
document.addEventListener('DOMContentLoaded', () => {
day(nowLast, prevLast)
})
type Days = (nowLast :Date, prevLast :Date) => void
let day: Days = function (nowLast) {
// 여기서 만든 p 태그들이 전역변수로 querySelector를 사용해도 선택되지 않았다.
}
해결
querySelectorAll 대신 array를 만들어 그 안에 element를 담았다.
클릭한 p 태그를 clickItem이란 array에 담고, clickItem을 이용해 p태그들을 다루었다.
// a 날짜에서 b 날짜까지 선택하기
let clickIndex = 0
let clickItem :Element[] = []
type PClick = (e :MouseEvent) => void
let pClick :PClick = function(e) {
clickIndex++
if (e.target instanceof HTMLParagraphElement) {
switch (clickIndex) {
case 1 :
e.target.classList.add('from')
clickItem.push(e.target)
break
case 2 :
e.target.classList.add('to')
clickItem.push(e.target)
break
case 3 :
clickItem.forEach((dayPTage) => {
dayPTage.classList.remove('from', 'to')
})
clickIndex=0
break
}
}
}
더불어 A일자~B일자까지 활성화하는 방법은 :has 가상 클래스를 사용해 만들었다. :has 클래스가 생각보다 재미있고 유용해 추후 자세히 다룰 예정이다.
.tableDay p.from, .tableDay p.to {background-color: var(--color-sky);}
.from ~ :has(~ .to), .to ~ :has(~ .from) {background-color: var(--color-tbody-hover);}
기타 : 흥미로웠던 점
const date = new Date()
let prevLast = new Date(date.getFullYear(), date.getMonth(), 0)
let nowLast = new Date(date.getFullYear(), date.getMonth()+1, 0)
new Date()에서 일자를 0으로 두면, 저번달의 마지막 날짜가 생성된다는 점이 재밌었다. 어떤 블로거는 달은 0부터 시작하는 일자는 1부터 시작하는 이유가 이것이 아닐까 생각하던데 흥미로운 내용이었다.
이번에는 달력을 생성하기만 하고 끝나는 간단한 내용이었지만, 추후 여기에 to do list 기능을 추가해도 재밌을 것 같다.
참조
https://codepen.io/RishabhMishra/pen/wbjzjr
'Project > TypeScript' 카테고리의 다른 글
타입스크립트 - 무한슬라이드(캐러셀) 만들기 (0) | 2023.03.20 |
---|---|
타입스크립트 - 피아노 만들기 (0) | 2023.03.13 |