[HTML, CSS, JS]아날로그 시계 만들기

김범기·2024년 6월 25일
1
post-thumbnail

개요

프로젝트 하나를 끝내고, 이번엔 뭘 만들어볼까 하면 하던 중 javascript의 감을 올리기 위해서 시계 만들기에 도전했다. 디지털 시계도 좋지만 아날로그의 감성을 컴퓨터에서 느끼는 것도 좋지 않을까. 하는 마음에 한 번 시도해봤다.
30분이면 끝날 줄 알았는데, 이것저것 손대고 문제점 때문에 손 좀 대고 하니 시간이 꽤 걸렸다.
그래도 완성.

참고 블로그1
참고 블로그2

만듦

이 글을 본다면 필자는 초보자니 그냥 참고만 하시길

이미 만들고 글을 쓰는 거라 이미 완성된 것을 보고 회고식으로 작성한다.

html, css, js 생성

우선 html파일과 css파일 js 파일을 만들고, html에 연결해주었다.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>아날로그 시계</title>
  <link rel="stylesheet" href="clock.css" />
</head>
<body>
  
  <script src="clock.js"></script>
</body>
</html>

기본 원 만들기

시계는 애쁠와치가 아니면 웬만한 시계는 원형으로 되어있다.
이것도 그래서 원형으로 만들어주었다.

view-body클래스 width와 height를 통해서 전체 화면이 내가 바라보는 사이즈임을 명시했다.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>아날로그 시계</title>
  <link rel="stylesheet" href="clock.css" />
</head>
<body>
  <div class="view-body">
    <div class="clock-container" id="clock_container">
      
    </div>
  </div>
  <script src="clock.js"></script>
</body>
</html>

vmin을 기준으로 시계틀을 잡았고, 나머지 또한 그러한 방식으로 했다.
background는 에메랄드색 시계를 만들어 보고 싶어서 저런식으로 그라데이션을 주면서 하는 방향으로 진행을 했다.

body{
  margin: 0;
}
.view-body{
  height: 100vh;
  width: 100vw;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
.clock-container{
  width: 60vmin;
  height: 60vmin;
  border: 1px solid #000000;
  border-collapse: collapse;
  border-radius: 30vmin;
  position: relative;
  background: radial-gradient(circle, #ffffff 0%, #3ed4be 100%);
}

시간표시선

이제 여기서 시간을 확인할 수 있는 표를 만들기 위해서 코드를 진행했다.

js파일에
clock_container를 찾아서 총 30개의 라인을 그어주었다.

const clock_container = document.getElementById("clock_container")


// 시간 표시 라인
for (let i = 0; i < 30; i++) {
  const gradation = document.createElement('div')
  gradation.classList.add('line')
  gradation.style.transform = `rotate(${6 * i}deg)`
  if (i % 5){
    gradation.classList.add('thin')
  }else{
    gradation.classList.add('thick')
  }
  clock_container.appendChild(gradation)
}

css에서는 아래를 추가해주었다.

.line{
  position: absolute;
  width: 60vmin;
  background: #000000;
  z-index: 1;
}
.thin{
  height: 0.25vmin;
  top: calc(50% - 0.125vmin);

}
.thick{
  height: 0.5vmin;
  top: calc(50% - 0.25vmin);
  z-index: 3;
}

시간표시선 가리기

시간표시선을 가리기 위해 위해 다시 div로 덮어주었다.

2개의 div로 가려줬으며, absolute와 top, left를 이용해서 중심을 잡고, 시간표시선을 지워주도록했다.

이 때, 큰 선은 내 취향대로 일부러 여기선 남겨놨다.
이 과정에서 z-index를 사용해서 뒤로 더 보낼 수도 있다.

<body>
  <div class="view-body">
    <div class="clock-container" id="clock_container">
      <div class="clock-body-add1"></div>
      <div class="clock-body-add2"></div>
    </div>
  </div>
  <script src="clock.js"></script>
</body>

css에서 z-index를 사용해 줬고, background는 이전과 마찬가지로 에메랄드 색상을 위해 이렇게 배경을 넣어봤다.(근데 에메랄드 색이 안나온다. 눈물)

.clock-body-add1{
  width: 58vmin;
  height: 58vmin;
  border-radius: 29vmin;
  position: absolute;
  top: calc(50% - 29vmin);
  left: calc(50% - 29vmin);
  z-index: 3;
  background: radial-gradient(circle, #ffffff 0%, #a4ebe0 100%);
}
.clock-body-add2{
  width: 55vmin;
  height: 55vmin;
  border-radius: 27.5vmin;
  position: absolute;
  top: calc(50% - 27.5vmin);
  left: calc(50% - 27.5vmin);
  z-index: 3;
  background: radial-gradient(circle, #ffffff 0%, #ecfffc 100%);
  background: #ecfffc;
}

내가 원하는 대로 시간 표시선 가림

html은 아래처럼 해주었다.
clock-body에서 이제 시침과 분침, 초침을 집어넣을 것이고, 추가 기능도 넣어줄 것이다.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>아날로그 시계</title>
  <link rel="stylesheet" href="clock.css" />
</head>
<body>
  <div class="view-body">
    <div class="clock-container" id="clock_container">
      <div class="clock-body-add1"></div>
      <div class="clock-body-add2"></div>
      <div class="clock-body" id="clock">
      
      </div>
    </div>
  </div>
  <script src="clock.js"></script>
</body>
</html>

그리고 css에서 clock-body를 시계의 주요 기능인 초침이 들어갈 수 있게 짰다.

.clock-body{
  width: 52vmin;
  height: 52vmin;
  border-radius: 26vmin;
  position: relative;
  top: calc(50% - 26vmin);
  left: calc(50% - 26vmin);
  background: #ffffff;
  z-index: 4;
}

시간 넣기

이제 시계가 조금 더 시계처럼 보이기 위해 시간을 넣을 것이다. 그런데 난 로마자로 넣어봤다. 이유는 그게 더 머찌니까.

<div class="num-div"></div>

위 코드를 clock-container안에 추가해주었다.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>아날로그 시계</title>
  <link rel="stylesheet" href="clock.css" />
</head>
<body>
  <div class="view-body">
    <div class="clock-container" id="clock_container">
      <div class="clock-body-add1"></div>
      <div class="clock-body-add2"></div>
      <div class="num-div"></div>
      <div class="clock-body" id="clock">
      
      </div>
    </div>
  </div>
  <script src="clock.js"></script>
</body>
</html>

사용할 css로 다음을 이용했다.

.num-div{
  width: 50vmin;
  height: 10%;
  position: absolute;
  top: calc(50% - 5%);
  left: calc(50% - 25vmin);
  z-index: 5;
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-weight: 700;  
}
.font1{
  font-size: calc(0.5rem + 2vmin);
  font-weight: 700;
}

그리고 js에서

const clock = document.getElementById("clock")

로 clock을 선언해주었다.

로마자를 쓰기위해 rome_number 변수안에 입력해주고, 이를 for문을 통해 사용해주었다.

// clock-container안에 포함
// 로마자 시간 표시
const rome_number = ['Ⅰ','Ⅱ','Ⅲ','Ⅳ','Ⅴ','Ⅵ','Ⅶ','Ⅷ','Ⅸ','Ⅹ','Ⅺ','Ⅻ']
for (let j = 0; j < 6; j++) {
  const number_div = document.createElement('div')
  number_div.style.transform = `rotate(${30 * j + 120}deg)`
  const span1 = document.createElement('span')
  const span2 = document.createElement('span')
  span1.innerText = rome_number[j]
  span2.innerText = rome_number[6 + j]
  span1.style.transform = `rotate(${-(30 * j + 120)}deg)`
  span2.style.transform = `rotate(${-(30 * j + 120)}deg)`
  number_div.appendChild(span1)
  number_div.appendChild(span2)
  number_div.classList.add('num-div')
  number_div.classList.add('font1')
  clock_container.appendChild(number_div)
}


이게 어떻게되어있는 거냐면 다음과 같다.

div태그에 span을 넣고 양쪽으로 위치를 잡아주도록 space-between을 이용했다. 그리고 이 div태그들을 각각의 시각에 맞도록 회전시켜준 것 되겠다.

그런데 이 때, 그냥 회전 시켜주면 그러니까

span1.style.transform = rotate(${-(30 * j + 120)}deg)
span2.style.transform = rotate(${-(30 * j + 120)}deg)

위의 js코드중 이 코드가 없으면, 다음처럼 보이게 된다.

그래서 span태그는 돌아가면 안되니까 span태그에는 div를 회전시킨만큼 역회전시켜주었다.

고장난 시계도 하루 2번은 맞게하기

이제 시계가 작동할 수 있도록, 시침, 분침, 초침, 그리고 가운데 고정핀도 시계다운 느낌을 위해 만들어주자.

html에 각각을 그려줄 div를 넣어주자.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>아날로그 시계</title>
  <link rel="stylesheet" href="clock.css" />
</head>
<body>
  <div class="view-body">
    <div class="clock-container" id="clock_container">
      <div class="clock-body-add1"></div>
      <div class="clock-body-add2"></div>
      <div class="num-div"></div>
      <div class="clock-body" id="clock">
        <div class="clock-center"></div>
        <div class="niddle hours" id="hours-time"></div>
        <div class="niddle minutes" id="minutes-time"></div>
        <div class="niddle seconds" id="seconds-time"></div>
      </div>
    </div>
  </div>
  <script src="clock.js"></script>
</body>
</html>

그리고 그에 맞게 css를 그려주었다. 나는 12시가 기준이 되게 만들어서 height가 width보다 긴편이라 position: absolute;에서 left를 50%에서 나의 widht의 절반을빼주도록 했다.

또한 회전을 위해서 transform: rotate(몇deg);를 기입하는데 이때, transform-origin : 50% 100%;을 해주면 내가 어디를 기준으로 회전을 하게 될 지를 정할 수 있다. 자세한 것은 검색 ㄱㄱ

나의 경우 아까도 말했지만, 12시를 0도 기준으로 회전하기 때문에, 시침은 300deg, 분침은 60deg, 초침은 180deg를 향하도록 만들었다.
왜냐! 시계 국룰 10시 10분!

.clock-center{
  background: #ffffff;
  width: 3vmin;
  height: 3vmin;
  border: 1px solid #000000;
  border-collapse: collapse;
  border-radius: 1.5vmin;
  position: absolute;
  top: calc(50% - 1.5vmin);
  left: calc(50% - 1.5vmin);
  z-index: 6;
}
.niddle{
  height: 30%;
  width: 2%;
  position: absolute;
  left: calc(50% - 1%);
  border-radius: 12px;
  transition: transform 0.05s linear;
}
.hours{
  top: calc(20%);
  background: #000000;
  z-index: 4;
  transform-origin : 50% 100%;
  transform: rotate(300deg);
}
.minutes{
  height: 40%;
  top: calc(10%);
  background: #8c32e7;
  z-index: 4;
  transform-origin : 50% 100%;
  transform: rotate(60deg);
}
.seconds{
  height: 48%;
  top: calc(2%);
  width: 0.5%;
  left: calc(50% - 0.25%);
  background: #ff1d1d;
  z-index: 5;
  transform-origin : 50% 100%;
  transform: rotate(180deg);
}

고장난 시계 고치기

이제 하루 2번만 맞는 시계가 아닌 매초 맞는 시계로 수리해보자.
js파일에서 setInterval을 사용하자.

먼저 아래 코드를 수가해서 수리를 진행했다.

let hours_time = document.getElementById("hours-time")
let minutes_time = document.getElementById("minutes-time")
let seconds_time = document.getElementById("seconds-time")

// 이전 시간을 이용하기 위 등록
let previous_seconds_degree = 0
let previous_minutes_degree = 0
let previous_hours_degree = 0

// 이전 시간을 이용하기 위 등록
let previous_seconds_degree = 0
let previous_minutes_degree = 0
let previous_hours_degree = 0

이 이전 시간을 이용하기 위 등록이라 주석달린 코드는 59초에서 0초로 넘어갈때, 예시로 354deg에서 0deg로 넘어갈 때, 시계방향이 아닌 반시계방향으로 넘어가는 문제를 해결하기 위해 등록한 변수이다.

setClock = () => {
  let current_time = new Date()
  let hours = current_time.getHours()
  let minutes = current_time.getMinutes()
  let seconds = current_time.getSeconds()
  let milliseconds = current_time.getMilliseconds()

  // 1초당 6도 + 6도를 1000으로 나눈 만큼 milliseconds값 더해서 부드럽게.
  let seconds_degree = 6 * seconds + (6/1000 * milliseconds)
  // 1분당 6도 + 그리고 6도를 60초로 나눠서 0.1씩 초마다 더움직이게, 그리고 밀리초까지 계산
  let minutes_degree = 6 * minutes + (0.1 * seconds) + (6 / 60 / 1000 * milliseconds)
  // 1시간당 30도 + 그리고 30도를 60분으로 나눠서 0.5씩 분마다 더움직이게, 그리고 초와 밀리초까지 계산
  let hours_degree = 30 * (hours % 12) + (0.5 * minutes) + (30 / 3600 * seconds) + (30 / 60 / 60 / 1000 * milliseconds)
  
  seconds_time.style.transform = `rotate(${seconds_degree}deg)`
  minutes_time.style.transform = `rotate(${minutes_degree}deg)`
  hours_time.style.transform = `rotate(${hours_degree}deg)`

  // Math.abs(현재 값 - 이전 값) > 180이면
  // 예) 59초 -> 0초 이므로 이때 발생하는 반시계 방향 돌기 문제를 해결하기 위함
  // 잠시 transition을 꺼놓고 이외에는 켜놓는 방식을 사용
  if (Math.abs(seconds_degree - previous_seconds_degree) > 180) {
    seconds_time.style.transition = 'none'
  } else {
    seconds_time.style.transition = 'transform 0.05s linear'
  }
  if (Math.abs(minutes_degree - previous_minutes_degree) > 180) {
    minutes_time.style.transition = 'none'
  } else {
    minutes_time.style.transition = 'transform 0.05s linear'
  }
  if (Math.abs(hours_degree - previous_hours_degree) > 180) {
    hours_time.style.transition = 'none'
  } else {
    hours_time.style.transition = 'transform 0.05s linear'
  }

  previous_seconds_degree = seconds_degree
  previous_minutes_degree = minutes_degree
  previous_hours_degree = hours_degree
  }
setInterval(setClock, 50)

위 코드를 보면 알 수 있듯이 new Date()를 이용해서 현재의 시간, 분, 초, 밀리초까지 구해주었다.
난 여기서 조금 더 시계와 유사한 모습을 위해서 밀리초까지 포함해서 시침, 분침, 초침의 위치를 정할 수 있게 해주었다.

그 결과 아래와 같은 코드를 사용했다.

 // 1초당 6도 + 6도를 1000으로 나눈 만큼 milliseconds값 더해서 부드럽게.
  let seconds_degree = 6 * seconds + (6/1000 * milliseconds)
  // 1분당 6도 + 그리고 6도를 60초로 나눠서 0.1씩 초마다 더움직이게, 그리고 밀리초까지 계산
  let minutes_degree = 6 * minutes + (0.1 * seconds) + (6 / 60 / 1000 * milliseconds)
  // 1시간당 30도 + 그리고 30도를 60분으로 나눠서 0.5씩 분마다 더움직이게, 그리고 초와 밀리초까지 계산
  let hours_degree = 30 * (hours % 12) + (0.5 * minutes) + (30 / 3600 * seconds) + (30 / 60 / 60 / 1000 * milliseconds)

그리고 이것이 작동하는 것은 디지털이 아닌 아날로그이므로 각 침들이 회전을 해야하기에 .style.transform을 이용해서 rotate시켜주었다.

seconds_time.style.transform = `rotate(${seconds_degree}deg)`
minutes_time.style.transform = `rotate(${minutes_degree}deg)`
hours_time.style.transform = `rotate(${hours_degree}deg)`

그리고

setInterval(setClock, 50)

왜 1000이 아닌 50으로 했냐고 하면 좀 더 정밀함을 요구했기 때문이다. 사실 1000이나 500으로 해도 되지만, 코옴퓨타라고 항상 밀리초까지 완벽한 타이밍에 데이터를 제공해주진 않기에, 0.05초마다 확인하게하고 움직이게 했다. 사실 주기가 짤아서 그리 좋은 방식은 아니다.

이제 잘 작동하는 모습을 보여 준다.

추가 기능

기능은 잘 작동하지만 좀 더 멋쟁이 시계를 만들기 위해 기능을 추가했다.

오늘 날짜

사실 이건 시계가 아닌 html에 추가한거다.

<div class="date-container" id="date_container"></div>

를 추가해서 오늘 날짜를 추가했다.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>아날로그 시계</title>
  <link rel="stylesheet" href="clock.css" />
</head>
<body>
  <div class="view-body">
    <div class="date-container" id="date_container"></div>
    <div class="clock-container" id="clock_container">
      <div class="clock-body-add1"></div>
      <div class="clock-body-add2"></div>
      <div class="num-div"></div>
      <div class="clock-body" id="clock">
        <div class="clock-center"></div>
        <div class="niddle hours" id="hours-time"></div>
        <div class="niddle minutes" id="minutes-time"></div>
        <div class="niddle seconds" id="seconds-time"></div>
      </div>
    </div>
  </div>
  <script src="clock.js"></script>
</body>
</html>

css에는 아래 코드를 추가했다.

.date-container{
  margin: 5vmin;
}

js에는 setClock 함수에 아래를 더 추가 해줬다.


  // 날짜 갱신
  let year = current_time.getFullYear()
  let month = current_time.getMonth() + 1 // 월은 0부터 시작하므로 1을 더해줌
  let date = current_time.getDate()
  let day = current_time.getDay().toString()
  const change_day_str = {
    '1' : '월',
    '2' : '화',
    '3' : '수',
    '4' : '목',
    '5' : '금',
    '6' : '토',
    '0' : '일',
  }

  // 시계 위 날짜 표시를 위한 JS
  const date_container = document.getElementById('date_container')
  // date_container를 완전히 비워서기존 날짜를 지우고 새로 추가
  date_container.innerHTML = '' 
  let input_date_container = [year, month, date, day]
  for (let k = 0; k < 4; k++) {
    const span = document.createElement('span')
    if(k == 3){
      span.innerText =`(${change_day_str[input_date_container[k]]})`
    }else{
      span.innerText =`${input_date_container[k]}.`
    }
    date_container.appendChild(span)
  }

new Date()없이 바로 getFullYear()를 들어간 이유는 위에서 이미 손을 대지 않은 new Date()가 있기에 따로 추가하지 않았다.

시계 이니셜 넣기

유명한 시계들은 이니셜이 박혀있다.
나도 나만의 시계를 만들어보자.

로고를 넣은 html코드를 입력해주었다.

<div class="clock-logo font1">
	<span>KBG</span>
</div>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>아날로그 시계</title>
  <link rel="stylesheet" href="clock.css" />
</head>
<body>
  <div class="view-body">
    <div class="date-container" id="date_container"></div>
    <div class="clock-container" id="clock_container">
      <div class="clock-body-add1"></div>
      <div class="clock-body-add2"></div>
      <div class="num-div"></div>
      <div class="clock-body" id="clock">
        <div class="clock-logo font1">
          <span>KBG</span>
        </div>
        <div class="clock-center"></div>
        <div class="niddle hours" id="hours-time"></div>
        <div class="niddle minutes" id="minutes-time"></div>
        <div class="niddle seconds" id="seconds-time"></div>
      </div>
    </div>
  </div>
  <script src="clock.js"></script>
</body>
</html>

css에 아래 코드를 추가해줬다.

.clock-logo{
  width: 100%;
  text-align: center;
  position: absolute;
  top: 25%;
}

(근데 시간이 시간인지라 가려졌다.)

시계안에 날짜 표시

또 고오오급시계들을 보니 시계 안에 날짜가 표시되는 것을 알 수 있었다.(아님말고..)
그래서 시계안에 날짜를 표시할 수 있게도 진행해봤다.
부채꼴을 만드는 css가 참으로 쉽지 않았다.

우선 html에 아래를 추가해줬다.

<div class="inner-date" id="inner_date"></div>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>아날로그 시계</title>
  <link rel="stylesheet" href="clock.css" />
</head>
<body>
  <div class="view-body">
    <div class="date-container" id="date_container"></div>
    <div class="clock-container" id="clock_container">
      <div class="clock-body-add1"></div>
      <div class="clock-body-add2"></div>
      <div class="num-div"></div>
      <div class="clock-body" id="clock">
        <div class="inner-date" id="inner_date"></div>
        <div class="clock-logo font1">
          <span>KBG</span>
        </div>
        <div class="clock-center"></div>
        <div class="niddle hours" id="hours-time"></div>
        <div class="niddle minutes" id="minutes-time"></div>
        <div class="niddle seconds" id="seconds-time"></div>
      </div>
    </div>
  </div>
  <script src="clock.js"></script>
</body>
</html>

css에는 다음의 코드를 추가해주었다.
.inner-date-div는 js에서 추가해줄 것이다.

inner-date의

background: conic-gradient(transparent 60deg,rgba(218, 253, 248, 0.5) 60deg, rgba(218, 253, 248, 0.5) 120deg, transparent 120deg);

를 통해서 부채꼴 형태로 시계의 날짜를 볼 수 있도록 하는 배경을 만들었다.

.inner-date::before를 이용해서 다른 부분은 원래는 피자형태의 부채꼴이 되어야 하는 것을 도넛형태의 부채꼴이 되도록 눈속임을 해주었다.
즉 배경을 안보이게 해버렸다는 뜻.

이 부채꼴에서 나는 전날, 오늘, 내일 날짜를 볼 수 있게 할 것이다.

.inner-date {
  width: 40vmin;
  height: 40vmin;
  position: relative;
  top: calc(50% - 20vmin);
  left: calc(50% - 20vmin);
  border-radius: 50%;
  background: conic-gradient(transparent 60deg,rgba(218, 253, 248, 0.5) 60deg, rgba(218, 253, 248, 0.5) 120deg, transparent 120deg);
}

.inner-date::before {
  content: '';
  width: 30vmin;
  height: 30vmin;
  background: white;
  border-radius: 50%;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
.inner-date-div{
  position: absolute;
  width: 37vmin;
  top: calc(50% - 0.5rem);
  left: calc(50% - 18.5vmin);
  display: flex;
  justify-content : flex-end;
  align-items : center;
}

이제 그림은 그려졌으니, 오늘 날짜가 보이게 코드를 작성해주자.
JS의 setClock함수에 다음을 추가해주면 된다. 이유는 내일인지 오늘인지 컴퓨터는 모르니까 0.05초마다 시계 확인할 때마다 날짜도 확인해줘야해서 그렇다.

  // 시계 안에 날짜를 표시하는 JS
  const inner_date = document.getElementById('inner_date')
  // inner_date 완전히 비워서기존 날짜를 지우고 새로 추가
  inner_date.innerHTML = '' 
  // 지난 달의 마지막 날, 이번 달의 마지막 날 구하는 코드
  // 이번 달의 마지막날
  let closing_d = new Date()
  closing_d= new Date(closing_d.getFullYear(), closing_d.getMonth()+1, 0)
  // console.log(closing_d.toLocaleDateString()) // 이번달의 마지막날
  let closing_date = closing_d.getDate()
  // 지난 달의 마지막날
  let pre_d = new Date()
  let pre_closing_d = new Date(pre_d.getFullYear(), pre_d.getMonth(), 0)
  // console.log(pre_closing_d.toLocaleDateString()) // 이전 달의 마지막 날
  let pre_closing_date = pre_closing_d.getDate()
  for (let l = 0; l < 3; l++) {
    // date가 이번 달의 첫날이면, 전 날은 이전 달의 마지막 날
    let write_date
    if(date == 1 && l == 0){
      write_date = pre_closing_date
    }else if(date == closing_date && l == 2){
      write_date = 1
    }else{
      write_date = date + l - 1
    }
    const div = document.createElement('div')
    div.classList.add('inner-date-div')
    div.classList.add('font2')
    const span = document.createElement('span')
    span.innerText = `${write_date}`
    div.appendChild(span)
    div.style.transform = `rotate(${l * 20 - 20}deg)`
    inner_date.appendChild(div)
  }

JS에서

// 이번 달의 마지막날
let closing_d = new Date()
closing_d= new Date(closing_d.getFullYear(), closing_d.getMonth()+1, 0)
// console.log(closing_d.toLocaleDateString()) // 이번달의 마지막날
let closing_date = closing_d.getDate()

// 지난 달의 마지막날
let pre_d = new Date()
let pre_closing_d = new Date(pre_d.getFullYear(), pre_d.getMonth(), 0)
// console.log(pre_closing_d.toLocaleDateString()) // 이전 달의 마지막 날
let pre_closing_date = pre_closing_d.getDate()

이번 달의 마지막날과 지난 달의 마지막 날을 구해서, 이를 이용해 오늘이 1일 또는 이번 달의 마지막일 경우에 전날과 다음날의 이상해지는 경우를 방지하였다.


마침 글을 작성하는 시점이 25일에서 26일로 넘어가는 시점이라 잘 작동하는지 확인했는데, 아주 잘 작동하는 것을 확인할 수 있었다.

이제 나 만의 시계 완성이다.

전체 코드

html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>아날로그 시계</title>
  <link rel="stylesheet" href="clock.css" />
</head>
<body>
  <div class="view-body">
    <div class="date-container" id="date_container"></div>
    <div class="clock-container" id="clock_container">
      <div class="clock-body-add1"></div>
      <div class="clock-body-add2"></div>
      <div class="num-div"></div>
      <div class="clock-body" id="clock">
        <div class="inner-date" id="inner_date"></div>
        <div class="clock-logo font1">
          <span>KBG</span>
        </div>
        <div class="clock-center"></div>
        <div class="niddle hours" id="hours-time"></div>
        <div class="niddle minutes" id="minutes-time"></div>
        <div class="niddle seconds" id="seconds-time"></div>
      </div>
    </div>
  </div>
  <script src="clock.js"></script>
</body>
</html>

CSS

body{
  margin: 0;
}
.view-body{
  height: 100vh;
  width: 100vw;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
.clock-container{
  width: 60vmin;
  height: 60vmin;
  border: 1px solid #000000;
  border-collapse: collapse;
  border-radius: 30vmin;
  position: relative;
  background: radial-gradient(circle, #ffffff 0%, #3ed4be 100%);
}
.line{
  position: absolute;
  width: 60vmin;
  background: #000000;
  z-index: 1;
}
.thin{
  height: 0.25vmin;
  top: calc(50% - 0.125vmin);

}
.thick{
  height: 0.5vmin;
  top: calc(50% - 0.25vmin);
  z-index: 3;
}
.clock-body-add1{
  width: 58vmin;
  height: 58vmin;
  border-radius: 29vmin;
  position: absolute;
  top: calc(50% - 29vmin);
  left: calc(50% - 29vmin);
  z-index: 3;
  background: radial-gradient(circle, #ffffff 0%, #a4ebe0 100%);
}
.clock-body-add2{
  width: 55vmin;
  height: 55vmin;
  border-radius: 27.5vmin;
  position: absolute;
  top: calc(50% - 27.5vmin);
  left: calc(50% - 27.5vmin);
  z-index: 3;
  background: radial-gradient(circle, #ffffff 0%, #ecfffc 100%);
  background: #ecfffc;
}
.clock-body{
  width: 52vmin;
  height: 52vmin;
  border-radius: 26vmin;
  position: relative;
  top: calc(50% - 26vmin);
  left: calc(50% - 26vmin);
  background: #ffffff;
  z-index: 4;
}
.num-div{
  width: 50vmin;
  height: 10%;
  position: absolute;
  top: calc(50% - 5%);
  left: calc(50% - 25vmin);
  z-index: 5;
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-weight: 700;
}
.font1{
  font-size: calc(0.5rem + 2vmin);
  font-weight: 700;
}
.clock-center{
  background: #ffffff;
  width: 3vmin;
  height: 3vmin;
  border: 1px solid #000000;
  border-collapse: collapse;
  border-radius: 1.5vmin;
  position: absolute;
  top: calc(50% - 1.5vmin);
  left: calc(50% - 1.5vmin);
  z-index: 6;
}
.niddle{
  height: 30%;
  width: 2%;
  position: absolute;
  left: calc(50% - 1%);
  border-radius: 12px;
  transition: transform 0.05s linear;
}
.hours{
  top: calc(20%);
  background: #000000;
  z-index: 4;
  transform-origin : 50% 100%;
  transform: rotate(300deg);
}
.minutes{
  height: 40%;
  top: calc(10%);
  background: #8c32e7;
  z-index: 4;
  transform-origin : 50% 100%;
  transform: rotate(60deg);
}
.seconds{
  height: 48%;
  top: calc(2%);
  width: 0.5%;
  left: calc(50% - 0.25%);
  background: #ff1d1d;
  z-index: 5;
  transform-origin : 50% 100%;
  transform: rotate(180deg);
}
.date-container{
  margin: 5vmin;
}
.clock-logo{
  width: 100%;
  text-align: center;
  position: absolute;
  top: 25%;
}

.inner-date {
  width: 40vmin;
  height: 40vmin;
  position: relative;
  top: calc(50% - 20vmin);
  left: calc(50% - 20vmin);
  border-radius: 50%;
  background: conic-gradient(transparent 60deg,rgba(218, 253, 248, 0.5) 60deg, rgba(218, 253, 248, 0.5) 120deg, transparent 120deg);
}

.inner-date::before {
  content: '';
  width: 30vmin;
  height: 30vmin;
  background: white;
  border-radius: 50%;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
.inner-date-div{
  position: absolute;
  width: 37vmin;
  top: calc(50% - 0.5rem);
  left: calc(50% - 18.5vmin);
  display: flex;
  justify-content : flex-end;
  align-items : center;
}

JS

const clock_container = document.getElementById("clock_container")
const clock = document.getElementById("clock")

// 시간 표시 라인
for (let i = 0; i < 30; i++) {
  const gradation = document.createElement('div')
  gradation.classList.add('line')
  gradation.style.transform = `rotate(${6 * i}deg)`
  if (i % 5){
    gradation.classList.add('thin')
  }else{
    gradation.classList.add('thick')
  }
  clock_container.appendChild(gradation)
}

// clock-container안에 포함
// 로마자 시간 표시
const rome_number = ['Ⅰ','Ⅱ','Ⅲ','Ⅳ','Ⅴ','Ⅵ','Ⅶ','Ⅷ','Ⅸ','Ⅹ','Ⅺ','Ⅻ']
for (let j = 0; j < 6; j++) {
  const number_div = document.createElement('div')
  number_div.style.transform = `rotate(${30 * j + 120}deg)`
  const span1 = document.createElement('span')
  const span2 = document.createElement('span')
  span1.innerText = rome_number[j]
  span2.innerText = rome_number[6 + j]
  span1.style.transform = `rotate(${-(30 * j + 120)}deg)`
  span2.style.transform = `rotate(${-(30 * j + 120)}deg)`
  number_div.appendChild(span1)
  number_div.appendChild(span2)
  number_div.classList.add('num-div')
  number_div.classList.add('font1')
  clock_container.appendChild(number_div)
}

let hours_time = document.getElementById("hours-time")
let minutes_time = document.getElementById("minutes-time")
let seconds_time = document.getElementById("seconds-time")

// 이전 시간을 이용하기 위 등록
let previous_seconds_degree = 0
let previous_minutes_degree = 0
let previous_hours_degree = 0

setClock = () => {
  let current_time = new Date()
  let hours = current_time.getHours()
  let minutes = current_time.getMinutes()
  let seconds = current_time.getSeconds()
  let milliseconds = current_time.getMilliseconds()

  // 1초당 6도 + 6도를 1000으로 나눈 만큼 milliseconds값 더해서 부드럽게.
  let seconds_degree = 6 * seconds + (6/1000 * milliseconds)
  // 1분당 6도 + 그리고 6도를 60초로 나눠서 0.1씩 초마다 더움직이게, 그리고 밀리초까지 계산
  let minutes_degree = 6 * minutes + (0.1 * seconds) + (6 / 60 / 1000 * milliseconds)
  // 1시간당 30도 + 그리고 30도를 60분으로 나눠서 0.5씩 분마다 더움직이게, 그리고 초와 밀리초까지 계산
  let hours_degree = 30 * (hours % 12) + (0.5 * minutes) + (30 / 3600 * seconds) + (30 / 60 / 60 / 1000 * milliseconds)
  
  seconds_time.style.transform = `rotate(${seconds_degree}deg)`;
  minutes_time.style.transform = `rotate(${minutes_degree}deg)`
  hours_time.style.transform = `rotate(${hours_degree}deg)`

  // Math.abs(현재 값 - 이전 값) > 180이면
  // 예) 59초 -> 0초 이므로 이때 발생하는 반시계 방향 돌기 문제를 해결하기 위함
  // 잠시 transition을 꺼놓고 이외에는 켜놓는 방식을 사용
  if (Math.abs(seconds_degree - previous_seconds_degree) > 180) {
    seconds_time.style.transition = 'none'
  } else {
    seconds_time.style.transition = 'transform 0.05s linear'
  }
  if (Math.abs(minutes_degree - previous_minutes_degree) > 180) {
    minutes_time.style.transition = 'none'
  } else {
    minutes_time.style.transition = 'transform 0.05s linear'
  }
  if (Math.abs(hours_degree - previous_hours_degree) > 180) {
    hours_time.style.transition = 'none'
  } else {
    hours_time.style.transition = 'transform 0.05s linear'
  }

  previous_seconds_degree = seconds_degree
  previous_minutes_degree = minutes_degree
  previous_hours_degree = hours_degree
  
  // 날짜 갱신
  let year = current_time.getFullYear()
  let month = current_time.getMonth() + 1 // 월은 0부터 시작하므로 1을 더해줌
  let date = current_time.getDate()
  let day = current_time.getDay().toString()
  const change_day_str = {
    '1' : '월',
    '2' : '화',
    '3' : '수',
    '4' : '목',
    '5' : '금',
    '6' : '토',
    '0' : '일',
  }

  // 시계 위 날짜 표시를 위한 JS
  const date_container = document.getElementById('date_container')
  // date_container를 완전히 비워서기존 날짜를 지우고 새로 추가
  date_container.innerHTML = '' 
  let input_date_container = [year, month, date, day]
  for (let k = 0; k < 4; k++) {
    const span = document.createElement('span')
    if(k == 3){
      span.innerText =`(${change_day_str[input_date_container[k]]})`
    }else{
      span.innerText =`${input_date_container[k]}.`
    }
    date_container.appendChild(span)
  }
  
    // 시계 안에 날짜를 표시하는 JS
    const inner_date = document.getElementById('inner_date')
    // inner_date 완전히 비워서기존 날짜를 지우고 새로 추가
    inner_date.innerHTML = '' 
    // 지난 달의 마지막 날, 이번 달의 마지막 날 구하는 코드
    // 이번 달의 마지막날
    let closing_d = new Date()
    closing_d= new Date(closing_d.getFullYear(), closing_d.getMonth()+1, 0)
    // console.log(closing_d.toLocaleDateString()) // 이번달의 마지막날
    let closing_date = closing_d.getDate()
    // 지난 달의 마지막날
    let pre_d = new Date()
    let pre_closing_d = new Date(pre_d.getFullYear(), pre_d.getMonth(), 0)
    // console.log(pre_closing_d.toLocaleDateString()) // 이전 달의 마지막 날
    let pre_closing_date = pre_closing_d.getDate()
    for (let l = 0; l < 3; l++) {
      // date가 이번 달의 첫날이면, 전 날은 이전 달의 마지막 날
      let write_date
      if(date == 1 && l == 0){
        write_date = pre_closing_date
      }else if(date == closing_date && l == 2){
        write_date = 1
      }else{
        write_date = date + l - 1
      }
      const div = document.createElement('div')
      div.classList.add('inner-date-div')
      div.classList.add('font2')
      const span = document.createElement('span')
      span.innerText = `${write_date}`
      div.appendChild(span)
      div.style.transform = `rotate(${l * 20 - 20}deg)`
      inner_date.appendChild(div)
    }
}
setInterval(setClock, 50)

다음엔 뭘 만들어 볼까나

깃허브 코드 보러가기

profile
반드시 결승점을 통과하는 개발자

0개의 댓글