본문 바로가기
학원에서 배운 것/JavaScript

스케쥴러 HTML, CSS, JS 구현

by 쿠리의일상 2023. 2. 18.

 

수업 시간에 JS 구현을 위해 만들었던 스케쥴러를 좀더 보완하여 만들어보았다.

 

기존 <table> 과 <td> <tr>을 사용하던 HTML을 ul과 li, display flex로 만들었다.

 

CSS 상 약간의 디자인을 주었다. 특히 버튼의 경우, 코드펜에 올라온 것을 보고 사용..해보려고 했다. 내용이 길거나 스케쥴이 여러개면 디자인 요소를 해치므로 overflow hidden 처리 했다.

 

JS 기능은 수업 때 배운 것과 동일하게

  1. 날짜칸을 누르면 날짜 인풋에 날짜가 들어가게 (달력 부분에 입력을 받지만 요일 한자는 해당 사항 없게끔)
  2. 날짜가 들어간 상태에서 스케쥴을 입력하고 추가버튼을 누르면 해당 날짜 아래에 스케쥴이 추가되게
  3. 추가된 스케쥴은 e.target으로 접근하여 누르면 삭제되게

수업 때와 동일한 기능이지만 HTML 구조가 완전 다르기에 querySelector 사용이 까다로웠다.

 

 

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>스케쥴러 구현</title>
  <!-- Reset CSS -->
  <link href="https://cdn.jsdelivr.net/npm/reset-css@5.0.1/reset.min.css" rel="stylesheet">
  <!-- Main CSS -->
  <link href="./calendar.css" rel="stylesheet"/>
  <!-- Main JS -->
  <script defer src="./calendar.js"></script>
</head>
<body>
  <section class="calendar">
    <h1 class="part__year-month">2023년 2월</h1>
    <hr/>
    <div class="part__week-day">
      <ul class="week">
        <li class="sunday">日</li>						
        <li>月</li>
        <li>火</li>
        <li>水</li>
        <li>木</li>
        <li>金</li>
        <li class="saturday">土</li>
      </ul>
      <ul class="day">
        <li class="sunday"></li>
        <li> </li>
        <li> </li>
        <li>1
        </li>
        <li>2
        </li>
        <li>3
        </li>
        <li class="saturday">4
        </li>
      </ul>
      <ul class="day">
        <li class="sunday">5
        </li>
        <li>6
        </li>
        <li>7
        </li>
        <li>8
        </li>
        <li>9
        </li>
        <li>10
        </li>
        <li class="saturday">11
        </li>
      </ul>
      <ul class="day">
        <li class="sunday">12
        </li>
        <li>13
        </li>
        <li>14
        </li>
        <li>15
        </li>
        <li>16
        </li>
        <li>17
        </li>
        <li class="saturday">18
        </li>
      </ul>
      <ul class="day">
        <li class="sunday">19
        </li>
        <li>20
        </li>
        <li>21
        </li>
        <li>22
        </li>
        <li>23
        </li>
        <li>24
        </li>
        <li class="saturday">25
        </li>
      </ul>
      <ul class="day">
        <li class="sunday">26
        </li>
        <li>27
        </li>
        <li>28
        </li>
        <li></li>
        <li></li>
        <li></li>
        <li class="saturday"></li>
      </ul>
    </div>
  </section>
  <section class="contents">
    <div class="contents__date">
      <input type="text" placeholder="날짜를 클릭하세요." />
      <input type="text" placeholder="스케쥴을 입력해주세요."/>
      <button class="btn">추가</button>
    </div>
  </section>
</body>
</html>

 

section.calendar {
  position: relative;
  width: 75vw;
  box-shadow: 2px 2px 2px #744c0b;
  border: 1px solid #744c0b;
  margin: 30px auto;
}
section.calendar .part__year-month {
  font-size: 2.2rem;
  font-weight: 700;
  text-align: center;
  color: #bb8d43;
  padding: 50px 0 25px 0;
  text-shadow: 2px 2px 0 #513507;
  transition: all 0.5s ease;
}
section.calendar .part__year-month:hover {
  text-shadow: 0 0 0 #513507;
}

section.calendar hr {
  border: 0;
  border-top: solid 1px #DDD;
  border-color : #C4B5A2;
  width: 80%;

}
section.calendar hr:hover::before {
  transform: translateX(-10px);
}
section.calendar hr:hover::after {
  transform: translateX(10px) rotate(180deg);
}
section.calendar hr::before,
section.calendar hr::after {
  content: '';
  transition: .5s;
  position: absolute;
  top: 105px;
  width: 52px;
  height: 28px;
  background-color: white;
  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGgAAAAcBAMAAAB/gedFAAAAA3NCSVQICAjb4U/gAAAAMFBMVEX///+ii2yii2yii2yii2yii2yii2yii2yii2yii2yii2yii2yii2yii2yii2yii2xf+K23AAAAEHRSTlMAESIzRFVmd4iZqrvM3e7/dpUBFQAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAJoSURBVDiNnVS/b9NQED7HaRSrFLkqCCKeIAMDAwJ3QYHBBMYuKRtskVhRLaZWAqmBgjp0iCPEGg90bstEJ4IZYAlJ/wCqApYqtUS1gMiNm9THvTSJnRopJR7eD/s+f/e+7+4BtB/JhBM8TO/bFmvyYIxQ2QluJUs9ARWz1CBVUREHUwkVRQxQSRbAYCrGo3yqUwRgP/jqyfN/hV95x8drPOqDT1yWiZsWo4jZMCaGuEYTnYACA8wm5wbQXPV7GHTRMhw+qybrnWGcU3GiCORxrk5vLqe736J3acgsYgOSnIoTjXdyA7jOY4uKhrN/YEG03S7I8JQU3J5Fp61b5ncnHFjNJDVpKdUsqTm6IXm3cCt5hBEP8Y2tj62VdLWiUzz5wiomV3+yLDOLmcLKZOlZZHW6eHOvEe+cV8jl917tjBwkzzcndmVmqqZQnuBIi2RYUYTyVQvitr1Nnhkt+agWmS5UXfJmxvNyoH4sy+IuSUGOkgJChaRjHklx7uUlmiRsVlscVG2VUAfR/nX/MaVq049UohFrPsgmh6bWU/ksMFzPt6UwGktoQqzxYmmZ3PJ8UC+9hAMXWu/dmEMpRaI5Doqnk5SqljV+Vt+CVvDT6wkB+Yeb6TvzOe3pl4OpjnqR5dWvn534/pno5o0G+EL4khM/8xLfYq0EYlc9xEI1e7pgmzx7X/KAuY9kDRfrcE8obXfNnWnKZyEzh47wqc9cv4yAyuhBPVx7mddURtBXRsGCzbjzG2HQ2L5RP16wgdaI46ESBolbmINQa/hNmJoOYwBGFvh4rAmHavehLpbhrrChLsv/vJb/AvJXJlGPw7QyAAAAAElFTkSuQmCC);
	background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIGlkPSJMYXllcl8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjEwNHB4IiBoZWlnaHQ9IjI4cHgiIHZpZXdCb3g9IjAgMCAxMDQgMjgiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDEwNCAyOCIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8Zz4NCgk8Zz4NCgkJPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGZpbGw9IiNBMjhCNkMiIGQ9Ik01NiAxMy44OWgwLjA4bDEuNzIgMS43M2wxLjU4LTEuMzZjMCAwIDIuNy0wLjA4IDMuMiAxLjEgYzAgMCAwLjcgMS4yMi0wLjkxIDEuNmMwIDAtMS40MiAwLjE1LTEuMzUtMS4xNGMwIDAtMS4yIDEuOCAxLjEgMi45NmMwIDAgMy4xIDAuOCAzLjM4LTIuNzNsMC40MiAwLjI4bDIuMDYgMi4wOGwzLjkxLTMuOTYgaDEuNzJ2LTAuNTlWMTMuOHYtMC42aC0xLjhsLTMuODMtMy44N2wtMS44MSAxLjgybC0wLjY3IDAuNDZjLTAuMy0zLjU3LTMuMzgtMi43My0zLjM4LTIuNzNjLTIuMzMgMS4xNC0xLjEzIDIuOTYtMS4xMyAzIGMtMC4wNy0xLjI5IDEuMzUtMS4xNCAxLjM1LTEuMTRjMS41OCAwLjQgMC45IDEuNiAwLjkgMS41OWMtMC40NiAxLjIyLTMuMTYgMS4xNC0zLjE2IDEuMTRsLTEuNTgtMS4zNmwtMS43MiAxLjczSDU2IGwwLjA0IDAuMDRMNTYgMTMuODl6IE02My45NiAxMy44OWwzLjMxLTMuMzRsMy4zIDMuMzRsLTMuMyAzLjMzTDYzLjk2IDEzLjg5eiIvPg0KCQk8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZmlsbD0iI0EyOEI2QyIgZD0iTTg0Ljg1IDUuMTJsNC4zOSA0LjQ0bC0zLjc5IDMuODNsLTQuNjYtNC41MSBjMCAwLTMuNjggNC4zMi01Ljg2IDMuMTljMCAwLTIuNjMtMC4wOC0yLjctMi45NmMwIDAgMC40NS0yLjgxIDIuMjUtMi4yOGMwIDAgMC42IDAuNjgtMC4yMyAxLjE0YzAgMC0xLjI3IDAuNjgtMC4yMiAyIGMwIDAgMSAxLjcgMi43IDAuMjJjMCAwIDEuMDUtMS4yMSAwLjQ1LTIuNzNjMCAwLTAuOS0yLjItMy4xNS0yLjA1YzAgMC0yLjMzIDAuMjMtMy4xNiAyLjc0YzAgMC0wLjE1IDIuNiAxLjYgMy45IGMwIDAgMS40IDEuNyA0LjUgMS4xM2wwLjMtMC4yM2MwLjgzLTAuMzQgMi4wMS0xLjA5IDMuNTMtMi43M2wzLjgzIDMuNjRsLTMuODMgMy42NGMtMS41Mi0xLjY0LTIuNy0yLjM5LTMuNTMtMi43M2wtMC4zLTAuMjMgYy0zLjA4LTAuNTMtNC41MSAxLjE0LTQuNTEgMS4xNGMtMS43MyAxLjIxLTEuNTggMy44Ny0xLjU4IDMuODdjMC44MyAyLjUgMy4yIDIuNyAzLjIgMi43M2MyLjI1IDAuMSAzLjE1LTIuMDUgMy4xNS0yLjA1IGMwLjYtMS41MS0wLjQ1LTIuNzMtMC40NS0yLjczYy0xLjczLTEuNTItMi43IDAuMjMtMi43IDAuMjNjLTEuMDUgMS40IDAuMiAyIDAuMiAyLjA1YzAuODMgMC41IDAuMiAxLjEgMC4yIDEuMSBjLTEuOCAwLjUzLTIuMjUtMi4yOC0yLjI1LTIuMjhjMC4wNy0yLjg4IDIuNy0yLjk2IDIuNy0yLjk2YzIuMTgtMS4xNCA1LjkgMy4yIDUuOSAzLjE5bDQuNTQtNC40bDMuOTEgMy45NWwtNC4zOSA0LjQgTDg5LjkyIDI4bDUuMDctNS4xMmwtNC40LTQuNDRsMy44My0zLjg3bDQuNTEgNC41NUwxMDQgMTRsLTUuMDctNS4xMmwtNC41MSA0LjU1bC0zLjgzLTMuODdsNC40LTQuNDRMODkuOTIgMEw4NC44NSA1LjEyeiBNODYuMiAyMi44OGwzLjcyLTMuNzZsMy43MSAzLjc2bC0zLjcxIDMuNzVMODYuMiAyMi44OHogTTg2LjIgMTRsMy43Mi0zLjc2TDkzLjYzIDE0bC0zLjcxIDMuNzZMODYuMiAxNHogTTk1LjIxIDE0bDMuNzItMy43NiBsMy43MiAzLjc2bC0zLjcyIDMuNzZMOTUuMjEgMTR6IE04Ni4yIDUuMTJsMy43Mi0zLjc1bDMuNzEgMy43NWwtMy43MSAzLjc2TDg2LjIgNS4xMnoiLz4NCgk8L2c+DQoJPGc+DQoJCTxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBmaWxsPSIjQTI4QjZDIiBkPSJNNDggMTMuODloLTAuMDhsLTEuNzIgMS43M2wtMS41OC0xLjM2YzAgMC0yLjctMC4wOC0zLjE2IDEuMSBjMCAwLTAuNjcgMS4yIDAuOSAxLjZjMCAwIDEuNCAwLjEgMS4zNS0xLjE0YzAgMCAxLjIgMS44Mi0xLjEzIDIuOTZjMCAwLTMuMDggMC44My0zLjM4LTIuNzNsLTAuNDIgMC4yOGwtMi4wNiAyLjEgbC0zLjkxLTMuOTZIMzEuMXYtMC41OVYxMy44di0wLjZoMS44bDMuODMtMy44N2wxLjgxIDEuODJsMC42NyAwLjQ2YzAuMy0zLjU3IDMuMzgtMi43MyAzLjM4LTIuNzNjMi4zMyAxLjEgMS4xIDMgMS4xIDMgYzAuMDctMS4yOS0xLjM1LTEuMTQtMS4zNS0xLjE0Yy0xLjU4IDAuMzgtMC45MSAxLjU5LTAuOTEgMS41OWMwLjQ2IDEuMiAzLjIgMS4xIDMuMiAxLjE0bDEuNTgtMS4zNmwxLjcyIDEuNzNINDhsLTAuMDQgMCBMNDggMTMuODl6IE00MC4wNCAxMy44OWwtMy4zMS0zLjM0bC0zLjMgMy4zNGwzLjMgMy4zM0w0MC4wNCAxMy44OXoiLz4NCgkJPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGZpbGw9IiNBMjhCNkMiIGQ9Ik0xOS4xNSA1LjEybC00LjM5IDQuNDRsMy43OSAzLjgzbDQuNjYtNC41MSBjMCAwIDMuNyA0LjMgNS45IDMuMTljMCAwIDIuNjMtMC4wOCAyLjctMi45NmMwIDAtMC40NS0yLjgxLTIuMjUtMi4yOGMwIDAtMC42IDAuNyAwLjIgMS4xNGMwIDAgMS4zIDAuNyAwLjIgMiBjMCAwLTAuOTcgMS43NC0yLjcgMC4yMmMwIDAtMS4wNS0xLjIxLTAuNDUtMi43M2MwIDAgMC45LTIuMiAzLjE1LTIuMDVjMCAwIDIuMyAwLjIgMy4yIDIuNzRjMCAwIDAuMSAyLjY1LTEuNTggMy45IGMwIDAtMS40MyAxLjY2LTQuNTEgMS4xM2wtMC4zLTAuMjNjLTAuODMtMC4zNC0yLjAxLTEuMDktMy41My0yLjczbC0zLjgzIDMuNjRsMy44MyAzLjY0YzEuNTItMS42NCAyLjctMi4zOSAzLjUzLTIuNzNsMC4zLTAuMjMgYzMuMDgtMC41MyA0LjUgMS4xIDQuNSAxLjE0YzEuNzMgMS4yIDEuNiAzLjkgMS42IDMuODdjLTAuODMgMi41LTMuMTYgMi43My0zLjE2IDIuNzNjLTIuMjUgMC4xNS0zLjE1LTIuMDUtMy4xNS0yLjA1IGMtMC42LTEuNTEgMC40NS0yLjczIDAuNDUtMi43M2MxLjczLTEuNTIgMi43IDAuMiAyLjcgMC4yM2MxLjA1IDEuMzYtMC4yMiAyLjA1LTAuMjIgMi4wNWMtMC44MyAwLjQ1LTAuMjMgMS4xNC0wLjIzIDEuMSBjMS44IDAuNSAyLjI1LTIuMjggMi4yNS0yLjI4Yy0wLjA3LTIuODgtMi43LTIuOTYtMi43LTIuOTZjLTIuMTgtMS4xNC01Ljg2IDMuMTktNS44NiAzLjE5bC00LjU0LTQuNGwtMy45MSAzLjk1bDQuMzkgNC40IEwxNC4wOCAyOGwtNS4wNy01LjEybDQuNC00LjQ0bC0zLjgzLTMuODdsLTQuNTEgNC41NUwwIDE0bDUuMDctNS4xMmw0LjUxIDQuNTVsMy44My0zLjg3bC00LjQtNC40NEwxNC4wOCAwTDE5LjE1IDUuMTJ6IE0xNy44IDIyLjg4bC0zLjcyLTMuNzZsLTMuNzEgMy43NmwzLjcxIDMuNzVMMTcuOCAyMi44OHogTTE3LjggMTRsLTMuNzItMy43NkwxMC4zNyAxNGwzLjcxIDMuNzZMMTcuOCAxNHogTTguNzkgMTRsLTMuNzItMy43NiBMMS4zNSAxNGwzLjcyIDMuNzZMOC43OSAxNHogTTE3LjggNS4xMmwtMy43Mi0zLjc1bC0zLjcxIDMuNzVsMy43MSAzLjc2TDE3LjggNS4xMnoiLz4NCgk8L2c+DQo8L2c+DQo8L3N2Zz4=), none;
	background-repeat: no-repeat;
	background-position: 0 center;
}
section.calendar hr::before {
  left: 25px;
}
section.calendar hr::after {
  right : 25px;
  transform: rotate(180deg);
}

section.calendar .part__week-day {}

section.calendar .part__week-day .week {
  display: flex;
  justify-content: space-around;
  margin-top: 35px;
  margin-bottom: 40px;
}
section.calendar .part__week-day .week > li {
  font-size: 1.2rem;
  font-weight: 500;
}

section.calendar .part__week-day .day {
  display: flex;
  justify-content: space-around;
  margin: 20px 0;
}
section.calendar .part__week-day .day > li {
  font-size: 1rem;
  text-align: center;
  width: 80px;
  height: 80px;
  overflow: hidden;
}
section.calendar .part__week-day .day > li:hover {
  font-size: 1.2rem;
  font-weight: 600;
}
/* section.calendar .part__week-day .day > li .day__contents {
  width: 80px;
  height: 70px;
  overflow: hidden;
} */
section.calendar .part__week-day .day > li > div {
  padding: 4px 2px;
  margin: 4px auto;
  border: 1px solid #513507;
  border-radius: 4px;
  background-color: #513507;
  color: white;
  font-weight: 500;
  font-size: 0.8rem;
  width: 75px;
  height: 18px;
  line-height: 20px;
  overflow: hidden;
}

section.calendar .part__week-day .sunday {
  color : red;
}
section.calendar .part__week-day .saturday {
  color : blue;
}


section.contents {
  width: 50vw;
  margin: auto;
}
section.contents input {
  border-radius: 3px;
  padding: 10px 6px;
  font-size: 1rem;
}

section.contents .contents__date {
  margin-bottom: 30px;
  /* text-align: center; */
  position: relative;
}

section.contents .contents__date > input:first-child {
  width: 25%;
}
section.contents .contents__date > input:nth-child(2) {
  width :50%;
  border-color: #d59631;
  margin: 0 4px;
  transition: .4s;
}
section.contents .contents__date > input:nth-child(2):focus {
  background-color: #e2b46b;
  color: black;
  border-color: #d59631;
  outline: #744c0b;
}

section.contents .contents__date .btn {
  font-size: 1.2rem;
  background-color: #744c0b;
  color: white;
  padding: 1px 10px;
  position: absolute;

  right: 3px;
  width: 68px;
  z-index: 2;
  line-height: 40px;
  cursor: pointer;
}
section.contents .contents__date .btn:hover {
  border: none;
}
section.contents .contents__date .btn::before,
section.contents .contents__date .btn::after {
  position: absolute;
  content: "";
  width: 0%;
  height: 0%;
  border: 2px solid;
  z-index: -1;
  transition: all 0.4s ease;
}
section.contents .contents__date .btn::before {
  top: 0;
   left: 0;
   border-bottom-color: transparent;
   border-right-color: transparent;
   border-top-color: #000;
   border-left-color: #000;
}
section.contents .contents__date .btn::after {
  bottom: 0;
  right: 0;
  border-top-color: transparent;
  border-left-color: transparent;
  border-bottom-color: #000;
  border-right-color: #000;
}
section.contents .contents__date .btn:hover::before,
section.contents .contents__date .btn:hover::after {
  border-color: #000;
  height: 100%;
  width: 100%;
}

 

const dayItem = document.querySelector('.part__week-day');
const dateInputField = document.querySelector('.contents__date > input:first-child');

const addItem = document.querySelector('.day > li');
const textInputField = document.querySelector('.contents__date > input:nth-child(2)');
const addItemBtn = document.querySelector('.btn');

let curTarget;

// 날짜 입력
let clickDateInfo = function(e) {
  curTarget = e.target;
  let tmpDate;

  // 에러 해결- 텍스트 노드가 있을 때만
  if((e.target.childNodes[0] !== undefined)) {
    tmpDate = `${e.target.childNodes[0].textContent.trim()}`;
  } else {
    return;
  } 
  if(curTarget.tagName === "LI") {
    const dateNum = /[0-9]/;
    if(dateNum.test(tmpDate) === true) {
      dateInputField.value = `2023년 2월 ${tmpDate}일`;
    }
  }
}
dayItem.addEventListener('click', clickDateInfo);

// 추가 버튼
let addItemLi = function(e) {
  if(textInputField.value === "") {
    textInputField.placeholder = "스케쥴 입력이 필요합니다.";
    return;
  }
  const contentsItem = document.createElement('div');
  contentsItem.textContent = textInputField.value;
  contentsItem.addEventListener('click', function(e) {
    e.target.remove();
  });

  if(curTarget.tagName === "LI") {
    curTarget.appendChild(contentsItem);
  }

  textInputField.value = "";
}

addItemBtn.addEventListener('click', addItemLi);

재미있지만 아직은 어렵기만하다~