1. 🔍 프로젝트 미리보기


2. 💠 Lighthouse 점수 분석하기 - 기존 Lighthouse 점수

2.1. Performance
OPPORTUNITIES
리소스 관점에서 웹 페이지의 더 나은 성능을 위해 어떤 점을 개선시킬 수 있는지 제안해준다. 즉, 로딩 성능을 개선하는 방법을 제시한다.

Reduce unused JavaScript
추후 JavaScript 기능을 넣기 위해 <script src=""></script>코드를 미리 세팅해두었더니 사용되지 않는 JavaScript 코드를 삭제하라며 성능 점수를 떨어트렸다.
DIAGNOSTICS
페이지의 실행 관점에서 개선할 수 있는 방법을 제시한다. (⇒ 렌더링 성능)

Serve static assets with an efficient cache policy
정적 리소스는 캐시를 사용하라는 의미로, 변화 주기가 짧다면 캐시를 사용하는 것이 렌더링 성능을 높힐 수 있다.
캐시에 정적 리소스를 저장해두면 캐시의 유효 기간 내에서는 서버로 리소스를 매번 요청하지 않고 캐시에서 가져와 사용할 수 있기 때문이다.

Avoid chaining critical requests
Lighthouse에서도 페이지 로드를 개선하기 위해 체인 길이(css가 연결된 개수)를 줄이거나, 불필요한 리소스의 다운로드를 나중에 하는(lazy-loading) 방법을 추천해주었다.
2.2. Accessibility

form element인 <textarea>가 <label>과 연결되어 있지 않다.

웹 접근성을 위한 명암비가 고려되지 않았다.
저시력자, 고령자 등도 인식할 수 있도록 콘텐츠와 배경 간의 명도 대비는 4.5:1 이상이어야 한다.
큰 텍스트, 확대된 화면의 경우 3:1 이상이어야 한다.
이는 네이버 접근성 가이드 페이지에도 기재되어 있다.
2.3. Best Practices

크롬 개발자 도구의 Issues 부분에 이슈가 발생한다고 얘기해주고 있다.
위 오류에 대한 Lighthouse 깃허브의 이슈와 stackoverflow 링크이다.
Lighthouse reports issues logged in Issues tab, but no issues are in the tab · Issue #12525 · GoogleChrome/lighthouse
Provide the steps to reproduce opened local website in Chrome ran lighthouse in dev tools What is the current behavior? Lighthouse best practices score 93, states "Issues were logged in the Issues ...
github.com
Issue: Audit usage of navigator.userAgent, navigator.appVersion, and navigator.platform
I started a new project in vue.js. I added navbar. At one point, I noticed issue in the console: Audit usage of navigator.userAgent, navigator.appVersion, and navigator.platform I don't understand...
stackoverflow.com
다른 부분이 우선이라고 생각이 들어 아직 이 문제를 해결해보지 못했지만, 추후에 동일한 문제가 발생하면 참고하고자 작성해놓으려 한다.
2.4. SEO


SEO(Search Engine Optimization, 검색엔진최적화)를 위한 meta description 추가하기
meta description이란 웹 페이지를 요약 설명한 것으로 구글 검색 결과시 사이트 아래에 보일 설명을 의미한다. 페이지를 방문하지 않고도 어떤 페이지인지 알 수 있도록 정보를 제공해주기 때문에 SEO를 높일 수 있다.
meta description은 아래와 같이 작성할 수 있다.
<code />
<meta name="description" content="페이지 설명">
3. 💠 기존 코드에서 리팩토링 한 부분
- TAB키를 사용했을 때 집중도 버튼의 외곽선 색상이 구분 되어있지 않다.


접근성을 고려하여 outline의 색상은 포커스 시의 배경색과 대비되게 설정하여 포커스 여부를 나타낼 수 있어야한다.
- 너무 많은 css 파일로 분리하지 말 것
기존에 분리된 css 파일 목록은 아래와 같다.
<bash />
how-is-lion-today
├─ css
│ ├─ button.css # 버튼 스타일
│ ├─ flex.css # flex-box 클래스 모음
│ ├─ font.css # 폰트 사이즈에 대한 클래스 모음
│ ├─ index.css # 레이아웃 스타일
│ ├─ main.css # <main> 구조 안에서 사용되는 스타일
│ ├─ normalize.css # CSS 초기화
│ └─ variables.css # 변수 모음
...
css 파일이 많아지면 브라우저에서 다운 받아야 할 파일이 많아져 성능상 좋지 않다. 따라서 간단한 부분이라면 굳이 파일로 분리를 하지 않는 것이 좋다.
실제로, flex.css나 font.css 등은 각각 5줄, 20줄 정도의 파일이었다. 추후에는 성능 최적화를 위해 css 파일을 min.css
라는 하나의 파일로 압축하기 때문에 파일을 분리하는 것에 큰 이점이 없다면 신경쓰지 않아도 되는 부분이다.
따라서 button, flex, font에 대한 css 파일은 프로젝트에서 공통된 부분을 클래스명으로 두어 관리하고 있었기 때문에 common.css
라는 파일안에 모두 포함시켰다.
index.css
는 사용자 에이전트 스타일 초기화와 레아이웃에 대한 css, main.css
는 <main>
안의 요소들을 스타일링한 것이다. 두 파일을 합쳐서 작성하여도 파일의 의미를 훼손시키지 않기 때문에 하나의 index.css
파일로 합쳤다.
min.css
공백과 줄바꿈을 제거하고 파일을 압축하여 용량을 줄인 css 파일
<aside>
의 역할 다시 생각해보기
HTML 요소 참고서 - HTML: Hypertext Markup Language | MDN
메타데이터는 스타일, 스크립트, 각종 소프트웨어(검색 엔진 (en-US), 브라우저 등)의 탐색 및 렌더링을 도와줄 데이터 등 페이지에 대한 정보를 가집니다. 스타일과 스크립트 메타데이터는 페이
developer.mozilla.org
<aside>
는 문서의 주요 내용과 간접적으로만 연관된 부분을 나타낸다. 주로 사이드바 혹은 콜아웃 박스를 표현한다.
문서의 주요 플로우에 포함되어야 하는 부분은 <aside>
로 나타내면 안되기 때문에 문맥상 페이지와 관련된 내용은 <section>
이나 <article>
을 사용하는 것이 좋다.

기존에는 좌측 부분을 <aside>
로 마크업 했었다. 이 부분은 우측의 메인 페이지와는 관련 없는 부분이라고 생각했는데, 생각해보니 현재 페이지를 소개하고 있기 때문에 관련이 있다고 볼 수 있다. 따라서 <aside>
가 아닌 <section>
으로 구분해주었다.
<header>
의 쓰임새 생각하기
<header>
와 <footer>
는 div처럼 감싸는 역할만을 한다. 특히 그중 header는 소개 및 탐색에 도움을 주는 콘텐츠를 나타내어 제목, 로고, 검색 폼, 작성자 이름 등의 요소를 포함할 수 있다.
<article>
의 경우 독립적으로 <header>
, <footer>
가 있으면 좋지만 그 외의 경우에는 사용을 고려해봐야 한다. 예를 들어, <header>
안에 블록 요소가 하나 밖에 존재하지 않는다면 굳이 사용할 필요는 없다.
이 부분에서 특히나 고민되고 헷갈렸던 부분은 header가 제목 요소를 포함할 수 있다는 것이다. 따라서
<section> 등 구획 요소 안에서는 무조건 제목을 <header>안에 넣는게 시멘틱할 것이라는 생각을 했었다.
- 사용자 입력을 받고 웹 사이트나 서버와의 통신이 필요하다면
<form>
을 사용하자
<form>
에서 사용자 입력을 받기 위해서는 <input>
을 사용해야한다. 상황에 따라 알맞는 type
속성을 사용하고, 스타일 커스텀을 할 수 있다.
기존에는 집중도를 버튼으로 구현하였는데 <form>
을 사용하게 되면서 <input type="radio">
로 스타일을 커스텀하여 변경할 수 있었다.
<html />
<div class="focus-check">
<h3>1교시(09:00 - 09:50)</h3>
<p>얼만큼 집중했나요?</p>
<div class="focus-level">
<button type="button">1</button>
<button type="button">2</button>
<button type="button">3</button>
<button type="button">4</button>
<button type="button">5</button>
</div>
<p>이번 교시를 세 줄 이내로 요약해보세요!</p>
<textarea name="class-textarea" rows="3" class="textarea"></textarea>
</div>
<html />
<form action="#" class="focus-check">
<h3>1교시(09:00 - 09:50)</h3>
<fieldset>
<legend>얼만큼 집중했나요?</legend>
<ol class="focus-level">
<li>
<label>
<span class="a11y-hidden">집중도: 매우 낮음</span>
<input type="radio" name="radio">
<span>1</span>
</label>
</li>
...
</ol>
</fieldset>
<label>이번 교시의 배움을 세 줄 이내로 요약해보세요!
<textarea name="class-summary" rows="3" class="textarea" maxlength="98"></textarea>
</label>
</form>
<textarea>
는<label>
과 연결해주기
- label 텍스트를 사용하면 좋은 이유
- 웹 접근성 높이기
- 스크린 리더는 폼 입력(form input)에서 label을 읽어 스크린 리더 사용자가 입력해야 하는 텍스트가 무엇인지 더 쉽게 이해할 수 있게 한다.
- label을 클릭하면 연결된 input에 초점을 맞추거나 활성화시킬 수 있어 누를 수 있는 영역(hit area)이 늘어나 터치 스크린 사용자를 포함한 모든 사용자에게 이점을 준다.
- 웹 접근성 높이기
- label을 input과 연결하는 또 다른 방법
<html />
<label>Do you like peas?
<input type="checkbox" name="peas">
</label>
label 태그 안에 input 태그를 넣으면 연관이 암시적이기 때문에 for 및 id 속성이 필요없다.
- 성능 최적화를 위해 줄일 수 있는 부분 줄이기
overflow
속성
<css />
/* 기존 코드 */
/* y축으로만 스크롤이 생기게 설정 */
overflow: scroll;
overflow-x: hidden;
/* 수정 코드 */
overflow-y: auto;
- initial value 생각하기
<css />
/* 기존 코드 */
text-align: start;
text-align
의 초기값은 start이므로 굳이 작성하지 않아도 되는 코드이다.
이처럼 initial value를 알고 있으면 코드를 한 줄이라도 더 줄일 수 있다.
4. 💠 Lighthouse 점수 분석하기 - 개선된 Lighthouse 점수

기존의 80점
에서 95점
으로 웹 접근성 점수가 올라갔지만 성능은 91점
에서 70점
으로 떨어지게 되었다. 이를 분석해보자!
4.1. Ensure text remains visible during webfont load

글꼴은 로드 시간이 느린 대용량 파일인 경우가 많다. 일부 브라우저는 글꼴이 로드될 때까지 텍스트를 숨겨 보이지 않는 텍스트(FOIT)가 번쩍이게 한다.
Ensure text remains visible during webfont load - Chrome Developers
Learn how to use the font-display API to make sure your web page text will always be visible to your users.
developer.chrome.com
FOIT(Flash of Invisible Text)
브라우저가 웹 글꼴을 다운로드하기 전에 텍스트가 보이지 않는 현상
4.1.1. 해결방법
font-display: swap
사용자 지정 글꼴이 로드되는 동안 보이지 않는 텍스트가 표시되지 않도록 하는 가장 쉬운 방법은 시스템 글꼴을 일시적으로 표시하는 것이다. css 속성인 font-display: swap
을 @font-face
에 포함하면 대부분의 최신 브라우저에서 FOIT를 피할 수 있다.
font-display
는 글꼴이 표시되는 방식을 지정한다. swap
속성 값은 시스템 글꼴을 사용하여 즉시 표시되어야 함을 브라우저에게 알리고, 사용자 지정 글꼴이 로드되면 시스템 글꼴을 대체한다.
다만 font-display
를 지원하지 않는 브라우저의 경우 글꼴 로드에 대한 기본 동작을 따르기 때문에 지원하는 브라우저를 확인하고 사용해야 한다.
브라우저 | 브라우저별 기본 글꼴 로드 동작 |
Edge | 글꼴이 준비될 때까지 시스템 글꼴을 사용하고, 준비가 됐다면 글꼴을 바꾼다. |
Chrome | 최대 3초 동안 텍스트를 숨긴다. 텍스트가 아직 준비되지 않은 경우 글꼴이 준비될 때까지 시스템 글꼴을 사용한 후 글꼴을 바꾼다. |
Firefox | 최대 3초 동안 텍스트를 숨긴다. 텍스트가 아직 준비되지 않은 경우 글꼴이 준비될 때까지 시스템 글꼴을 사용한 후 글꼴을 바꾼다. |
Safari | 글꼴이 준비될 때까지 텍스트를 숨긴다. |
rel='preload'
<html />
<head>
<!-- ... -->
<link rel="preload" href="폰트 리소스 URL" as="font" type="폰트 타입" crossorigin>
</head>
as="font" type="폰트타입"
속성은 브라우저에 이 리소스를 글꼴로 다운로드 하도록 지시하고, 리소스 대기열의 우선 순위를 지정하는 데 도움을 준다.
crossorigin
속성은 글꼴이 다른 도메인에서 올 수 있으므로 CORS 요청으로 리소스를 가져와야 하는지 여부를 나타낸다.
이 속성이 없으면 브라우저에서 미리 로드된 글꼴을 무시한다. (CORS 요청을 할 수 없다)
crossorigin
또는 crossorigin=""
는 crossorigin="anonymous"
와 동일하다. anonuymous
는 쿠키를 통한 user credentials(사용자 자격 증명) 교환이 필요 없음을 의미한다.
- 구글 폰트
구글 폰트를 사용하는 중이라면 URL 끝에 &display=swap
매개변수를 추가한다.
<html />
<link
href="https://fonts.googleapis.com/css?family=Roboto:400,700&display=swap"
rel="stylesheet"
/>
FontFaceObserver
라이브러리 사용
GitHub - bramstein/fontfaceobserver: Webfont loading. Simple, small, and efficient.
Webfont loading. Simple, small, and efficient. Contribute to bramstein/fontfaceobserver development by creating an account on GitHub.
github.com
스크롤 이벤트를 사용하여 글꼴 로드를 감지한다.
<link rel="preload">
+font-display: optional
을 함께 사용한다.
4.1.2. 적용한 방법
우선은 빠른 방법인 font-display: swap
을 사용했다.
규모가 크지 않은 단일 페이지 프로젝트여서 라이브러리를 사용하기에는 과하기도 했고, IE를 제외한 최신 웹 브라우저만을 고려했기 때문에 font-display
를 사용하여도 문제가 되지 않았다.

Reference
[Avoid invisible text during font loading](https://web.dev/avoid-invisible-text/)
[Preload web fonts to improve loading speed](https://web.dev/codelab-preload-web-fonts/)
[Prevent layout shifting and flashes of invisible text (FOIT) by preloading optional fonts](https://web.dev/preload-optional-fonts/)
[HTML attribute: crossorigin - HTML: Hypertext Markup Language | MDN](https://developer.mozilla.org/ko/docs/Web/HTML/Attributes/crossorigin)
4.2. Serve static assets with an efficient cache policy
앞서 봤던 캐시 정책을 사용하는 것에 대한 내용이다.
영어를 직역하면 네트워크를 통해 리소스를 가져오는 것은 느리기 때문에 효율적인 캐시 정책으로 정적 리소스(자산)을 제공하라는 의미이다.
빠른 응답을 위해서는 브라우저와 서버 간에 많은 요청과 응답이 오고가야 하는데 여기서 중요한 리소스가 완전히 다운로드될 때까지 페이지는 로드되지 않는다.
따라서 개인이 제한된 모바일 데이터 요금제로 사이트에 액세스하는 경우 많은 네트워크 요청이 요구될 경우 비용이 낭비되기도 한다.
이를 해결하는 방법 중 하나는 브라우저의 HTTP 캐시이다.
HTTP 캐싱 설정을 하기 위한 방법은 웹 서버가 각 발신 응답에 추가하는 헤더인 응답 헤더를 설정하는 것이다.
응답 헤더의 종류
Cache-Control
서버는 Cache-Control 지시문을 반환하여 브라우저 및 기타 중간 캐시가 개별 응답을 캐시하는 방법과 기간을 지정할 수 있다.
ETag
브라우저가 만료된 캐시 응답을 찾으면 작은 토큰(일반적으로 파일 내용의 해시)을 서버로 보내 파일이 변경되었는지 확인할 수 있습니다. 서버가 동일한 토큰을 반환하면 파일이 동일하므로 다시 다운로드할 필요가 없다.
Last-Modified
이 헤더는 `ETag`와 동일한 목적을 위해 사용되지만 시간 기반 전략을 사용하여 ETag의 콘텐츠 기반 전략과 반대로 리소스가 변경되었는지 여부를 판단한다.
일부 웹 서버는 기본적으로 응답 헤더를 설정할 수 있도록 지원하는 기능이 내장되어 있는 반면, 다른 웹 서버는 헤더를 명시적으로 구성하지 않는 한 헤더를 완전히 생략한다.
여기서 자주 사용되는 웹 서버에는 아래와 같은 것들이 있다.
현재 나는 Github-Pages를 사용하고 있기 때문에 헤더가 생략되어 캐시 정책이 없는 것 같다. 추후 Netlifty 등을 사용하여 업그레이드 해보자.
4.3. Reduce unused JavaScript / Minify JavaScript

FOIT 현상을 해결하고 사용하지 않는 script 태그를 제거하니 70점
에서 89점
으로 성능 점수가 확 올라갔다. 하지만 아직까지도 존재하는 문제가 있었다..!

사용하지 않는 script 태그를 지웠음에도 Reduce unused JavaScript와 Minify JavaScript 문제가 발생하고 있음을 확인할 수 있었다.
문제가 무엇인지 자세히 보니 크롬 확장 도구에서 찾을 수 있었다.

현재 사용하고 있는 개발자 도구 중에서 React Developer Tools에 의해 생겼던 문제였다.

React Developer Tools를 모든 사이트에서 사용하게끔 설정해두었는데 이를 확장 프로그램을 클릭할 경우에만 동작하도록 하여 설정을 변경하니 문제가 해결되면서 성능 점수가 91점
으로 오르게 되었다.
5. 💠 리팩토링을 하면서 추가로 알게된 점
5.1. radio
- 같은 주제의 radio 버튼은
name
속성으로 연결한다. TAB
으로 radio 버튼을 이동할 수 없고, 방향키로 이동 가능하다.- 만약
TAB
으로 이동하고 싶다면 onKeydown 등의 이벤트로 적용할 수 있지만, 기본적인 라디오 버튼의 키보드 접근성은 방향키이기 때문에 굳이 tab을 고려할 필요는 없다.
5.2. radio custom하기
Pure CSS Custom Styled Radio Buttons | Modern CSS Solutions
Learn to create custom, cross-browser, theme-able, scalable radio buttons in pure CSS and ensuring styles remain accessible across states.
moderncss.dev
5.3. 하나의 label과 여러 개의 input 연결하기
들어가기에 앞서, 나의 html 파일에는 동일한 form - input radio 구조가 반복되고 있다. 따라서 label for와 input id로 연결하기에는 네이밍의 복잡성이 생기기 때문에 label 태그 안에 input을 넣어 for-id를 사용하지 않으려고 했다.
- 초반 연결
<html />
<label>얼만큼 집중했나요?
<ol class="focus-level">
<li>
<input type="radio" name="radio" title="집중도 1점">
<span>1</span>
</li>
<li>
<input type="radio" name="radio" title="집중도 2점">
<span>2</span>
</li>
<li>
<input type="radio" name="radio" title="집중도 3점">
<span>3</span>
</li>
<li>
<input type="radio" name="radio" title="집중도 4점">
<span>4</span>
</li>
<li>
<input type="radio" name="radio" title="집중도 5점">
<span>5</span>
</li>
</ol>
</label>
하나의 label에 여러개의 input을 쓰면 label이 페이지에 차지하고 있는 공간이 커진다. 따라서 label 내에서 input 외의 공간을 클릭했을 때 선택 값이 input의 기본 값으로 변경되는 오류가 발생한다.
이를 해결하고자 fieldset을 사용했다.
- fieldset 사용하기
<html />
<fieldset>
<legend>얼만큼 집중했나요?</legend>
<ol class="focus-level">
<li>
<label>
<input type="radio" name="radio" title="집중도 1점">
<span>1</span>
</label>
</li>
<li>
<label>
<input type="radio" name="radio" title="집중도 2점">
<span>2</span>
</label>
</li>
<li>
<label>
<input type="radio" name="radio" title="집중도 3점">
<span>3</span>
</label>
</li>
<li>
<label>
<input type="radio" name="radio" title="집중도 4점">
<span>4</span>
</label>
</li>
<li>
<label>
<input type="radio" name="radio" title="집중도 5점">
<span>5</span>
</label>
</li>
</ol>
</fieldset>
fieldset을 적용하고 나니 숫자(span)를 클릭하면 radio checked
가 되지않는 오류 발생했다.
이 문제는 span에 pointer-events: none;
을 주면 해결할 수 있다.
pointer-events
HTML 요소들의 마우스/터치 이벤트들(CSS hover/active, JS click/tap, 커서 드래그등)의 응답을 조정할 수 있는 속성
property
— none: HTML 요소에 정의된 클릭, 상태(hover,active등), 커서 옵션들을 비활성화한다
5.4. radio 버튼의 웹 접근성 고려하기
- title 속성
title 속성은 요소에 대한 부가적인 정보를 나타내는 것으로 “툴팁”으로 사용되기에 적합하다
하지만, title 속성이 스크린 리더기에서 읽히지 않는 경우도 고려하였을 때, 모든 사용자를 고려하는 마크업을 하기 위해서는 대체 텍스트(alt 속성)나 숨김 텍스트를 우선으로 사용하는 것이 바람직하다.
- 숨김 텍스트 사용 - a11y
<code />
.a11y-hidden {
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%);
width: 1px;
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
}
<code />
<fieldset>
<legend>얼만큼 집중했나요?</legend>
<ol class="focus-level">
<li>
<label>
<span class="a11y-hidden">집중도: 매우 낮음</span>
<input type="radio" name="radio">
<span>1</span>
</label>
</li>
<li>
<label>
<span class="a11y-hidden">집중도: 낮음</span>
<input type="radio" name="radio">
<span>2</span>
</label>
</li>
<li>
<label>
<span class="a11y-hidden">집중도: 보통</span>
<input type="radio" name="radio">
<span>3</span>
</label>
</li>
<li>
<label>
<span class="a11y-hidden">집중도: 집중</span>
<input type="radio" name="radio">
<span>4</span>
</label>
</li>
<li>
<label>
<span class="a11y-hidden">집중도: 몰입</span>
<input type="radio" name="radio">
<span>5</span>
</label>
</li>
</ol>
</fieldset>
a11y-hidden
을 적용하여 텍스트를 숨김처리 하면서 스크린 리더에는 읽히게 하는 방법을 적용했다.
6. 리팩토링하면서 느낀 점
기존에도 성능과 웹 접근성 등을 고려하여 구조를 짰다고 생각했지만 Lighthouse를 활용하여 시각적인 지표를 확인해보니 많은 것을 놓치고 있었다는 것을 느꼈다.
또한 프론트엔드 개발자라면 사용자가 웹을 이용하는 목적과 문제를 해결해줄 수 있어야 한다는 생각을 가지고 있었는데, 자료들을 찾아보면서 많은 사람들이 성능과 웹 접근성에 대해 고민하고 있다는 것을 깨달았다. 이번 리팩토링을 통해서 나의 부족한 점을 찾을 수 있었고, 조금이나마 프론트엔드 개발자의 모습을 갖출 수 있었던 것 같아 좋았다👍
아직 쌓인 일들이 많아 자바스크립트를 연결하지는 못했지만, 작은 프로젝트로 많은 점을 얻은 것 같다. 자바스크립트 연결시 얼마나 많은 고려할 사항들이 기다리고 있을까 두려우면서 기대된다..!
7. ✨ 프로젝트 코드 및 데모 링크
GitHub - mihyunLee/how-is-my-lion-today: 나의 집중도에 따라 바뀌는 사자🦁
나의 집중도에 따라 바뀌는 사자🦁. Contribute to mihyunLee/how-is-my-lion-today development by creating an account on GitHub.
github.com
오늘의 사자 체크하기 | 회고하기
오늘 스쿨은 어땠나요? 나를 되돌아보고 싶다면, 나의 사자를 체크해보세요✔️ 매 교시 집중도를 체크하며 하루를 회고해요
mihyunlee.github.io
'Retrospective > Project' 카테고리의 다른 글
[AWS] AWS Lambda + S3 연동해서 파일 가져오기 (0) | 2023.11.15 |
---|---|
[Vuepress] Vuepress 설치부터 배포 자동화 적용하기 (0) | 2023.09.19 |
[React] 상품/장바구니 수량 조절 기능 구현하기 (0) | 2023.06.09 |
[JS] <progressbar>를 사용하여 설문지의 진행도를 구현해보자! (0) | 2023.04.29 |
JavaScript로 만드는 계산기 JS calculator (Step by step) (0) | 2022.03.07 |