[#3] 바닐라JS로 웹 페이지 구현하기

🌿1. index.html

    <div id="weather">
        <span></span>
        <span></span>
    </div>
    <div id="clock">
        <span></span>
        <span></span>
    </div>
    <!-- 로그인 전환될 때 애니메이션 넣어서 딜레이 시키기 -->
    <form id="login">
        <label>Hello, what's your name?<br>
            <input type="text" required minlength="4" maxlength="10">
        </label>
    </form>
    <h2 class="hidden"></h2>
    <div id="quote">
        <span></span>
        <span></span>
    </div>
    <div id="todo"></div>
    <div id="img">
        <span></span>
        <span></span>
    </div>

기능을 추가하면서 div가 늘어났다.
To-do를 뺀 나머지 기능들은 거의 완성이 된 상태라 페이지 내의 위치에 따라 div의 순서도 바꾸었다.
우측 상단에 위치한 weather을 최상단으로, 좌측 하단에 위치한 img(배경사진 정보)를 최하단으로 옮겼다.

새로 추가한 div에 대한 자세한 사항은 아래로!


🌿2. weather.js

날씨 기능은 강의에서 배운 코드와 큰 차이가 없기 때문에 사용한 개념에 대한 설명만 남겨보자면, 먼저 순서 상으로는 이렇다.

  1. getCurrentPosition 메서드를 이용해서 사용자의 현재 위치 불러오기
  2. 위치 정보 불러오기에 성공했을 때 콜백할 함수 설정
    2-1. position 객체와 Weather API를 이용해서 날씨 정보 받아오기
    2-2. 받아온 데이터를 fetch 호출하여 JSON 형태로 가져오고, 이 중에서 필요한 데이터만 추려서 HTML에 반영
  3. 위치 정보 불러오기에 실패했을 때 콜백할 함수 설정

1. getCurrentPosition()

navigator 객체의 geolocation.getCurrentPosition 메서드를 이용하면 사용자의 현재 위치를 불러올 수 있다.
이때 해당 메서드는 옵션을 포함하여 3개의 매개변수를 가질 수 있는데,
각각 위치 정보 불러오기에 성공 및 실패했을 시 콜백할 함수와 그외 옵션이다.

이 메서드를 사용하면 브라우저가 사용자의 위치 정보를 받아오기 위해 GPS 수집 권한을 요청하고, 해당 요청이 허용되면 성공했을 때를 위해 설정해 둔 함수가, 요청이 차단되면 실패했을 때를 위해 설정해 둔 함수가 콜백된다.

직접 함수를 설정하고 실행해보면서 느낀 바로는, getCurrentPosition 메서드 자체는 GPS만 수집할 뿐 결과를 보여주지는 않는다. 수집된 위치 정보를 받고 싶으면 해당 기능을 가진 함수를 설정해 주어야 한다.

2. 위치 정보 불러오기에 성공했을 때 콜백할 함수

2-1. position 객체와 Weather API를 이용해서 날씨 정보 받아오기

사용자의 위치 정보를 받아서 페이지 내에 위젯처럼 표시하는 목적이기 때문에, success 함수에는 우선 getCurrentPosition으로 받아온 position 객체 내부의 정보가 필요하다.

position 객체에는 정보를 요청한 시간의 위치를 나타내는 Coords와 위치를 기록한 시간을 나타내는 Timestamp가 있고, 강의에서 사용한 OpenWeatherMap API는 coords에 있는 위도와 경도만 가지고도 날씨 정보를 불러올 수 있었다.
API마다 사용 방법이 다를 것이기 때문에 다른 API를 이용한다면 해당 홈페이지에서 사용법을 익힐 것!

https://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={API key}

API를 불러오는 데에 필요한 정보(lat, lon, API key)를 집어넣으면 사용자의 위치 정보가 담긴 url이 완성된다.

2-2. fetch API

url이 준비 되었으면 그곳에 담긴 정보를 가져오기 위해 fetch()를 이용한다. 순서는 아래와 같다.

  1. fetch 함수에 url을 넣어서 데이터를 불러오고자 하는 url에 접근한다.
  2. then 함수를 이용하여 url에 있는 객체 데이터를 JSON 포맷으로 불러온다.
  3. JSON으로 불러온 데이터를 이용해 원하는 기능을 구현한다.

솔직히 말하자면 fetch 함수는 아직 50퍼센트밖에 이해하지 못했다. 완벽하게 이해하기 위해서는 promisecallback에 대한 학습이 선행되어야 하므로, 지금 단계에서는 fetch 함수를 이용해서 비동기적으로 데이터를 받아오고 then 함수로 그 결과를 받아온 다음, 이를 이용해서 필요한 기능을 구현한다는 정도로 이해하는 게 최선인 듯하다.

3. 위치 정보 불러오기에 실패했을 때 콜백할 함수

GPS 수집을 허용하지 않은 경우이므로, We cannot find you! If you wanna know your weather status, please allow us! 라는 문구를 alert창으로 띄웠다.


🌿3. login.js

로그인 기능의 핵심은 event.preventDefaultlocalStorage 사용이다.

event.preventDefault 메서드는 form의 기본값인 제출 시 자동 새로고침 기능을 무효화로 만든다. 로그인 창에 데이터를 입력해도 페이지 전체가 리프레시되는 대신 환영 문구를 띄우기 위해 사용했다.

로그인 기능은 크게 로그인 처리 및 환영 인사의 두 가지 함수로 이루어져 있다.

로그인 처리 함수는 아래 순서를 따른다.
1. 사용자가 입력창에 이름을 넣으면 해당 데이터를 변수에 저장한다.
2. 저장된 변수를 localStorage에 저장하고, form 요소를 숨긴다.
3. 환영 인사 함수를 실행한다.

환영 인사 함수는 아래 순서를 따른다.
1. localStorage에 저장된 변수를 가져온다.
2. 불러온 변수가 포함된 환영 문구를 HTML에 삽입한다.
3. 숨겨져 있던 환영 문구를 띄운다.

로그인 처리 함수 내부에 환영 인사 함수를 넣은 이유는 로그인 기능과 환영 인사 기능을 분리할 필요가 있기 때문이다.
localStorage에 저장하면 브라우저를 껐다 켜도 저장된 데이터가 남아 있어서 페이지를 새로고침해도 사용자가 입력한 이름 역시 그대로 남아있다. 사용자 정보가 저장되어 있는 경우(환영 인사 띄우기)와 저장되지 않은 경우(로그인 요청하기)를 나누어서 페이지에 표시되는 내용에 차이를 두고 싶었기 때문에 함수를 둘로 분리해서 사용했다.

그리고 위와 같이 구분된 기능을 구현하기 위해서 if else 구문을 사용했다. 사용자의 이름을 저장하는 변수가 비어 있으면 로그인 창을 보여주고, 저장된 변수가 있으면 환영 인사를 띄우는 간단한 구문이었다.


🌿4. background.js

여러 이미지를 페이지의 배경으로 돌려 쓰고 싶었기 때문에 우선 array를 만들고, 안에 원하는 이미지 수만큼의 객체를 넣었다.
각 객체는 이미지명, 촬영된 장소, 촬영한 사람을 프로퍼티를 갖는다.

랜덤으로 이미지를 가져오는 기능은 quote와 동일하게 Math.random 함수를 이용했다. 해당 함수가 반환한 숫자를 array의 index로 사용해서 객체를 가져온 다음, 배경 이미지로 표시하는 동시에 이미지 정보가 페이지 좌측 하단에 뜰 수 있게끔 만들었다.

이미지가 촬영된 장소와 촬영한 사람을 띄우는 기능은 강의에는 없었고, quote와 동일한 방식을 이용해서 따로 추가했다.


🌿5. style.css

어느 정도 기능들이 구현되었으니 CSS도 손보았다. 기능별로 나눠서 살펴보자면

1. 태그

html {
    height: 100%;
}

body {
    font-family: 'Noto Sans KR', sans-serif;
    text-align: center;
    background-size: cover;
    color: white;
}

span {
    display: block;
}

div {
    margin-top: 20px;
    display: block;
}

h2 {
    font-size: 40px;
}

우선 body 자체에 font-family를 설정해서 내부에 있는 모든 텍스트의 폰트를 변경했고, 대부분의 텍스트가 가운데에 있기 때문에 text-align: center를 적용해서 일괄적으로 중앙 정렬했다.
그리고 배경 이미지가 페이지를 빈 곳 없이 꽉 채우는 동시에 이미지의 원본 비율을 유지해야 하므로 cover를 적용했으며, 배경과 텍스트를 구분하기 위해 텍스트 색상을 변경했다.

여기까지 적용했더니 cover를 적용했음에도 불구하고 브라우저 크기를 줄이면 이미지가 페이지의 하단부를 덮지 못하는 문제가 있었다. 열심히 구글링하면서 height: 100vh 따위를 (페이지 전체를 커버하기는 하지만 화면 크기가 커지면서 스크롤이 생김) 다 적용해 보았는데, 답은 HTML 영역을 브라우저 높이에 맞추는 것이었다. body가 HTML 요소의 속성을 그대로 적용 받으면서 덩달아 영역이 브라우저 높이만큼 확장되기 때문에 문제 해결!

다음으로 span과 div에 display: block을 적용해서 각각 페이지 내부에서 한 줄씩 차지하면서 일렬로 쌓이게 만들었다. 또한 div 사이에 여백이 있는 게 보기 편할 듯해 margin도 주었다.
환영 인사가 삽입된 h2의 폰트 크기도 조절했다.

2. 클래스

.hidden {
    display: none;
}

클래스는 로그인 입력창과 환영 인사 문구를 숨기고 보여주는 기능만 담당했다. 여태 예제를 따라하거나 문제를 풀 때는 class를 주로 사용했었는데, 이번에는 id를 훨씬 많이 사용했다.

3. 아이디

#login label {
    font-size: 40px;
}

#login input {
    background-color: transparent;
    border: none;
    font-size: 40px;
    color: white;
    text-align: center;
    border-bottom: 1.5px solid white;
}

#login input:focus {
    outline: none;
}

로그인은 처음에는 label 없이 form 안에 input과 button이 들어 있었는데, CSS를 적용하면서 수정했다. 우선 윈도우95에나 어울릴 법한 디자인이 페이지의 미관을 해치기도(...) 했고, Momentum에 있는 로그인 입력창처럼 심플한 느낌을 내고 싶었기 때문이다.
그래서 input 요소의 placeholder를 지우고 placeholder 역할을 하던 문구를 label로 옮겼다. 그런 다음 input의 배경을 투명화하고 테두리를 없앤 다음, 폰트 크기와 색상 등을 조정했다.
이렇게 하면 될 줄 알았는데 입력창을 클릭해서 focus를 주자 보이지 않던 테두리가 생기면서 또 못생겨지길래 focus 상태의 outline마저 없애주었다.

#quote span:first-child {
    font-style: italic;
}

#quote span:last-child {
    font-size: 12px;
    opacity: 0.85;
}

quote는 명언에 먼저 눈이 가게 하기 위해 명언 부분을 이탤릭체를 적용해서 기울이고, 명언을 남긴 사람의 이름 크기를 조금 줄인 다음 투명도를 적용했다.

#clock {
    margin-top: 240px;
    line-height: 6.5em;
}

#clock span:first-child {
    font-size: 140px;
}

#clock span:last-child {
    font-size: 30px;
}

시계는 우선 페이지의 정중앙에 위치시키기 위해 margin-top을 주었는데, 반응형 웹을 전혀 고려하지 않은 사안이라 나중에 업데이트를 할 예정이다. position: fixed를 이용하면 되지 않을까 싶지만... 미래의 나에게 맡겨야지.
시간과 일자 사이의 간격이 너무 멀어서 line-height를 조절하였고, 각자의 폰트 크기도 조정하였다.

#weather {
    position: fixed;
    margin-top: 0.2em;
    top: 0.2em;
    right: 1em;
    text-align: right;
}

#weather span:first-child {
    font-size: 18px;
}

#weather span:last-child {
    font-size: 12px;
    opacity: 0.85;
}

날씨는 우측 상단에 고정하기 위해 position: fixed를 적용했고, 다른 속성들을 추가해서 위치를 조절했다. 별 생각없이 em 단위를 사용했는데, 반응형 웹으로 손 볼 때 이 부분도 업데이트를 해야 할 듯하다.
우측 상단에 고정한 만큼 텍스트도 오른쪽 정렬하였고, quote와 마찬가지로 날씨와 기온에 포커스를 두기 위해 도시 이름의 크기와 투명도를 조절하였다.

#img {
    position: fixed;
    bottom: 0.2em;
    text-align: left;
}

#img span:last-child {
    font-size: 10px;
    opacity: 0.85;
}

배경 이미지 정보도 날씨와 마찬가지로 좌측 하단에 고정하였다.


🌿6. 가벼운 회고

위의 기능들이 구현된 현재까지의 결과물이다!
역시 CSS를 조금 만져주니 그럴 듯한 페이지처럼 보이는군 후후...
여기에 To-do 기능만 넣으면 원래 계획했던 기능은 전부 구현한 셈이 된다.

하지만 사람은 늘 그렇듯, 만들면서 보완하고 싶은 점이 하나둘 생기기 마련이다.

우선 가장 시급한 건 반응형 웹 페이지로서 기능할 수 있도록 만드는 것이다. 지금은 컴퓨터 브라우저 화면만을 생각하고 코드를 짰기 때문에 브라우저 크기가 줄어든다거나 모바일로 연다거나 하는 경우를 전혀 염두에 두지 않았다. 지금은 아득해진 FlexboxGrid를 복습한 뒤 업데이트해야겠다.

그리고 소소하지만 transition 기능을 추가하고 싶어졌다.
Momentum 페이지를 보면 이미지 촬영 장소와 명언 부분에 마우스를 올리면 각각 촬영한 사람과 명언을 남긴 사람의 이름이 스르륵 등장한다. 그리고 입력창에 이름을 입력하고 환영 문구가 나올 때도 보다 부드러운 액션을 취한다. 아직까지 transition 개념을 제대로 연습한 적이 없는데 이 기능을 구현하면 조금이나마 능숙해지지 않을까 싶다.

아, 인터넷 연결이 끊겼을 때 띄울 페이지도 추가하고 싶다!

방금 말한 것까지 다 완성하고 나면 포트폴리오로 쓸 수 있으려나.
강의는 자바스크립트로 기능을 구현하는 데에 그치고, CSS에 대한 부분은 없으니까 세네 번 더 업데이트 하면 아주 엉성하고 아기자기한 포폴 땔감 정도로는 써도 되지 않을까 싶다ㅋㅋㅋㅋ

확실히 기본적인 문법을 배운 다음에 뭔가를 만드니 재미있고 보람찬 것 같다. 오롯이 혼자만의 힘으로 만든 건 절대 아니지만, 이렇게 살을 붙여가면서 가볍게 알았던 것을 깊이 파고들고, 내가 지금 모르는 부분이 어디인지 알게 되니 더 열심히 해야겠다는 생각이 샘솟는다🌞

또 얼른 최소한의 지식만 먼저 쌓고 무언가를 만들어 봐야지🔥🔥🔥


참고 자료: MDN 홈페이지

좋은 웹페이지 즐겨찾기