유튜브 '제주코딩베이스캠프'의 무료 강의 중 10000 시간의 법칙을 클론코딩 해보았다.

(광고아님)

 

에디터는 간편하게 repl.it을 이용하였다.

 

주된 기능은 인풋값을 받아 화면에 표시, 모달창 여닫기, 타임아웃 기능을 이용한 이미지 표시, URL카피 등이 있다. 

 

완성본 이미지


<!DOCTYPE html>
<html lang="ko">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>10000hours-alone</title>
  <link href="CSS/10000hours.css" rel="stylesheet" type="text/css" />
  <!-- Loads the Extensions API -->
  <script src="https://unpkg.com/@replit/extensions@0.24.0/dist/index.global.js"></script>
</head>

<body>
  <div id="main_wrap">
    <section id="contents">
      <div class="title_wrap wrappers">
        <img src="Images/clock.png" alt="" class="clock_img">
        <img src="Images/title.png" alt="" class="title_img">
      </div>
      <div class="quotes_wrap wrappers">
        <p>"연습은 어제의 당신보다 당신을 더 낫게 만든다."</p>
      </div>
      <div class="explain_wrap wrappers">
        <img src="Images/quotes.png" alt="" class="quotes_img">
        <p><span>1만 시간의 법칙</span>은<br>어떤 분야의 전문가가 되기 위해서는<br>최소한 1만 시간의 훈련이 필요하다는 법칙이다.</p>
      </div>
      <div class="input_wrap wrappers">
        <div class="input_expert_wrap">
          <p>나는<input
                  class="inputs"
                  id ='input_expert'
                  type="text"
                  placeholder="예)프로그래밍"
                 >전문가가 될 것이다.</p>
        </div>
        <div class="input_time_wrap">
          <p>그래서 앞으로 매일 하루에<input
                                  class="inputs"
                                  id='input_time'
                                  type="number"
                                  placeholder="예)5"
                             >시간씩 훈련할 것이다.</p>
        </div>
      </div>
      <div class="start_btn_wrap wrappers">
        <button class="start_btn">나는 며칠 동안 훈련을 해야 1만 시간이 될까?
          <img src="Images/click.png" alt="" class="click_img">
        </button>
      </div>
      <div class="loading_wrap wrappers">
        <img src="Images/loading.png" alt="시계가 돌아가는 중입니다.">
      </div>
      <div class="results_wrap">
        <div>당신은 <span class="expert_result">asdasd</span> 전문가가 되기위해서</div>
        <div>대략 <span class="time_result">asdasd</span> 일 이상 훈련하셔야 합니다! :)</div>
        <div class="result_btn_wrap wrappers">
          <button class="go_btn">훈련하러 가기 GO! GO!</button>
          <button class="share_btn">공유하러 가기</button>
        </div>
      </div>
      
    </section>
    <section id="modal">
      <div class="modal_wrap">
        <div class="cheer">
          <p class="cheer_top">화이팅!! ♡♡♡</p>
          <p class="cheer_bottom">당신의 꿈을 응원합니다!</p>
        </div>
        <div class="cheer_img_wrap">
          <img src="Images/licat.png" alt="응원하는 라이캣">
        </div>
        <button class="close_btn">종료하고 진짜 훈련하러 가기 GO! GO!</button>
        <p class="just">(그냥 닫기 버튼입니다.)</p>
      </div>
    </section>
    <section id="footer">
      <div class="footer_img_wrap">
        <img src="Images/logo.png" alt="위니브 로고입니다.">
      </div>
      <div class="footer_rights">
        <p>※ 본 서비스 내 이미지 및 콘텐츠의 저작권은 주식회사 WeNiv에 있습니다.<br>
수정 및 재배포, 무단 도용 시 법적인 문제가 발생할 수 있습니다.</p>
      </div>
    </section>
    <script src="JS/getExtensions.js" type="text/javascript"></script>
    <script src="JS/10000hours.js" type="text/javascript"></script>
  </div>
</body>

</html>

HTML 코드

 

배운 점

 

HTML 코딩은 div 태그를 활용해서 구역을 나누는 작업이 가장 어려운 것 같다.

많이 하다 보면 자연스럽게 늘겠지


/* 스타일 리셋 */
@import 'reset.css';
/* 커스텀 디자인 */
:root {
  --color-white : #FFF;
  --color-violet : #5b2386;
  --color-yellow : #F5DF4D;
  --color-grey : #BABCBE;

  --font-family-Noto : 'Noto Sans KR';
  --font-family-Enjoy : 'OTEnjoystoriesBA';
  --font-family-G-Medium : 'GmarketSansMedium';
  --font-family-G-Bold : 'GmarketSansBold';

  --animation-duration : 300ms;
}
/* 폰트 (4종류) */
@font-face {
    font-family: 'OTEnjoystoriesBA';
    src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_two@1.0/OTEnjoystoriesBA.woff') format('woff');
    font-weight: normal;
    font-style: normal;
}

@font-face {
    font-family: 'GmarketSansBold';
    src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2001@1.1/GmarketSansBold.woff') format('woff');
    font-weight: normal;
    font-style: normal;
}

@font-face {
    font-family: 'GmarketSansMedium';
    src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2001@1.1/GmarketSansMedium.woff') format('woff');
    font-weight: normal;
    font-style: normal;
}

@font-face {
    font-family: 'Noto Sans KR';
    font-style: normal;
    font-weight: 400;
    src: url("https://fonts.gstatic.com/ea/notosanskr/v2/NotoSansKR-Regular.woff2") format('woff2'),
    url("https://fonts.gstatic.com/ea/notosanskr/v2/NotoSansKR-Regular.woff") format('woff'),
    url("https://fonts.gstatic.com/ea/notosanskr/v2/NotoSansKR-Regular.otf") format('opentype');
}

html,body {
  height : 100%;
}

body {
  background-color: var(--color-violet);
  color: var(--color-white);
  font-family: var(--font-family-G-Medium);
}

.wrappers {
  margin : 40px 0;
}
/* 버튼 기본 스타일 */
button {
  border-radius: 20px;
  border: none;
  outline: none;
  padding: 8px 24px;
  cursor: pointer;
  font-size : 20px;
  color: var(--color-violet);
  background-color: var(--color-yellow);
  font-family: var(--font-family-G-Bold);
  line-height: 1.7;
  opacity: .77;
  transition : var(--animation-duration) all;
}

button:hover {
  opacity: 1;
  transform: scale(1.015);
}
  
#main_wrap {
  margin : 70px 35px;
  position : relative;
} 

#contents {
  display: flex;
  flex-direction: column;
  align-items: center;
}
/* 타이틀 */
.title_wrap {
  display: flex;
  justify-content: center;
  align-items: center;
  margin-bottom: 70px;
}

.title_wrap .clock_img {
  width : 256px;
  height : 256px;
  position: absolute;
}

.title_wrap .title_img {
  position : relative;
  width : 564px;
}
/* 명언 */
.quotes_wrap p {
  font-family: var(--font-family-Enjoy);
  color: var(--color-yellow);
  font-size: 36px;
}
/* 설명 */
.explain_wrap {
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
  position: relative;
}

.explain_wrap p {
  position: absolute;
  color: var(--color-yellow);
  line-height: 1.5;
}

.explain_wrap p span {
  font-family: var(--font-family-G-Bold);
  font-size: 20px;
}
/* 사용자 입력창 */
.input_wrap {
  margin: 70px 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  font-size: 20px;
  color: var(--color-yellow);
}

.input_expert_wrap, .input_time_wrap {
  width : 100%;
  margin: 20px 0;
}

.inputs {
  padding : 15px 20px;
  margin : 0 20px;
  border: none;
  border-radius: 10px;
  width : 200px;
  font-size : 20px;
}

.inputs::placeholder {
  color : var(--color-grey);  
}  
/* 계산하기 버튼 */
.start_btn_wrap {
  display: flex;
  justify-content: center;
  text-align: center;
}

.start_btn_wrap button {
  position: relative;  
}

.start_btn_wrap .click_img {
  position: absolute;
  right : -15%;
  top : -15%;
}
/* 로딩 이미지 */
@keyframes loading {
  100% {
    transform: rotate(360deg);
  }
}

.loading_wrap {
  display: none;
}

.loading_wrap img {
  width : 100px;
  height : 100px;
  animation: loading 1.2s infinite linear;
}
/* 결과 */
.results_wrap {
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  margin-bottom: 40px;
  line-height: 1.7;
}

.results_wrap p {
  font-size: 20px;
  padding: 20px 0;
}

.expert_result, .time_result {
  font-family: var(--font-family-G-Bold);
  padding : 0 20px;
  font-size: 70px;
}

.results_btn_wrap {
  display: flex;
  justify-content: center;
  align-items: center;
}

.share_btn {
  background-color: var(--color-white);
  margin-left: 20px;
}
/* 모달창 */
#modal {
  display : none;
  justify-content : center;
  width : 100%;
  height : 100%;
  position : fixed;
  top : 15%;
  left : 0;
  text-align : center;
  z-index : 999;
}

.modal_wrap {
  width : 600px;
  height : 620px;
  background-color: var(--color-white);
  color : var(--color-violet);
  border-radius : 20px;
}

.cheer {
  display : flex;
  flex-direction : column;
  align-items : center;
  text-align : center;
  padding : 20px 0 10px 0;
  font-family : var(--font-family-Enjoy);
}

.cheer_top {
  font-size : 75px;
  margin-bottom : 10px;
}

.cheer_bottom {
  font-size : 40px;
}

@keyframes licat{
  100% {
    opacity : 1;
    transform : scale(1.05);
  }
}

.cheer_img_wrap img {
  opacity : .77;
  animation : licat 1.2s infinite linear;
}

.close_btn {
  margin : 20px 0;
  box-shadow : 4px 4px 4px 4px var(--color-grey);
}

.just {
  font-size : 16px;
  color : var(--color-grey);
}
/* 풋터 */
#footer {
  display : flex;
  flex-direction : column;
  align-items : center;
  text-align : center;
  padding-bottom : 70px;
}

.footer_img_wrap {
  margin : 20px 0;
}

.footer_rights {
  font-size : 12px;
  line-height : 1.5;
}

CSS 코드. reset.css 코드는 구글링 해서 복붙, 10000 hours.css 파일 맨 윗줄에 불러오기 하였다.

 

배운 점

 

keyframe을 활용한 애니메이션 효과가 역시 재미있었다.

수코딩님의 영상으로 눈 내리는 배경을 한참 따라 만들 때도 그랬지만 CSS 스킬은 끝이 없는 것 같다..

그리고 HTML과 비슷한 맥락으로 CSS의 박스모델과 포지션에 대한 공부도 더 필요할 것 같다.

마지막으로 line-height. 무지성으로 따라 치기만 했는데, 어떤 숫자를 넣으면 어떤 식으로 변하는지에 대해 다음에 더 알아봐야겠다.


'use strict';

const inputExpert = document.querySelector('#input_expert');
const inputTime = document.querySelector('#input_time');
const startButton = document.querySelector('.start_btn');
const closeButton = document.querySelector('.close_btn');
const goButton = document.querySelector('.go_btn');
const shareButton = document.querySelector('.share_btn');
const modal = document.getElementById('modal');
const loading = document.querySelector('.loading_wrap');
const results = document.querySelector('.results_wrap');

// 시작버튼 핸들러

function caculator() {
  const expertValue = inputExpert.value;
  const timeValue = inputTime.value;
  const timeValue_int = Number(timeValue);
  
  const expertResult = document.querySelector('.expert_result');
  const timeResult = document.querySelector('.time_result');

  expertResult.innerText = expertValue;
  timeResult.innerText = parseInt((10000/timeValue_int), 10);
}

function handleDisplay() {
  loading.style.display = 'block';
  results.style.display = 'none';
    setTimeout(() => {
      loading.style.display = 'none';
      results.style.display = 'flex';
  }, 1200);
}

function handleStart() {
  handleDisplay();
  setTimeout(() => {
    caculator();
  }, 1200);
}

startButton.addEventListener('click', handleStart);

// 계산하기 버튼 핸들러

function handleGo(){
  modal.style.display = 'flex';
}

// 훈련가기 버튼 핸들러

goButton.addEventListener('click', handleGo);

function handleClose() {
  modal.style.display = 'none';
}

window.onclick = (e) => {
  if(e.target == modal) {
    modal.style.display = 'none';
  }
}

closeButton.addEventListener('click', handleClose);

// 공유 버튼 핸들러

function handleShare() {
  const url = window.location.href;
  navigator.clipboard.writeText(url)
  .then(() => {
    alert('URL이 복사되었습니다.');
  }).catch((err) => {
    console.error('복사 실패: ', err);
  });
}

shareButton.addEventListener('click', handleShare);

마지막으로 JavaScript 코드

 

배운 점

 

마지막 handleShare 함수 부분을 제외하고는 어려운 부분은 없었다.

공유하기 버튼을 누르면 해당 페이지의 url 복사해 주는 기능을 구현했는데, 강의에서는 execCommand라는 기능을 사용했다.

나도 따라 쳐보니 특정 브라우저에서는 기능하지 않을 수도 있다는 경고문구가 보였기에 저 부분만 구글링해 navigator.clipboard 기능을 사용하기로 했다.

js는 기능이 너무나도 많기에 이렇게 모르는 것을 하나하나 알아가는 것이 꽤 재미있다.

 


https://github.com/mediumryan/10000hrous-alone 

 

GitHub - mediumryan/10000hrous-alone: 10000hours app with alone

10000hours app with alone. Contribute to mediumryan/10000hrous-alone development by creating an account on GitHub.

github.com