(6) Web data storage - 테마 저장, 장바구니 구현하기

반응형

테마 저장 구현하기

사용자가 선택한 색상을 저장하고 다시 불러올 수 있도록 해야하고

사용자는 배경색과 테두리 색상을 변경할 수 있으며,

변경된 색상은 로컬 스토리지 (localStorage) 에 저장되어야한다.

 

▶ html, css 파일

더보기
<!DOCTYPE html>
<html>

<head>
  <title>테마 저장하기</title>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <style>

  </style>
  <link rel="stylesheet" href="style.css">
</head>

<body>
  <h2 class="heading">테마 설정</h2>
  <div class="options">
    <div class="option">
      <span>채우기</span>
      <input type="color" id="box-color" name="box-color">
    </div>
    <div class="option">
      <span>테두리</span>
      <input type="color" id="box-border-color" name="box-border-color">
    </div>
  </div>
  <div id="box"></div>
  <script src="index.js"></script>
</body>

</html>
@font-face {
  font-family: 'NanumSquareNeo-Variable';
  src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_11-01@1.0/NanumSquareNeo-Variable.woff2')
    format('woff2');
  font-weight: normal;
  font-style: normal;
}

:root {
  --red: #e46e80;
  --orange: #f18575;
  --yellow: #fbc85b;
  --green: #32b9c1;
  --blue: #5195ee;
  --purple: #8579ef;
  --n000: #f9fafc;
  --n100: #f0f3f7;
  --n200: #e0e4ec;
  --n300: #cacfd9;
  --n400: #9ba2b0;
  --n500: #7f8594;
  --n600: #616775;
  --n700: #494d59;
  --n800: #3e414d;
  --n900: #343843;
  --n930: #2b2f3a;
  --n960: #262a34;
  --light-red: #ffa8b5;
  --light-orange: #ffbab0;
  --light-yellow: #ffe7b2;
  --light-green: #7fd9df;
  --light-blue: #77b2ff;
  --light-purple: #9689ff;
  --dark-red: #dc596d;
  --dark-orange: #e16a58;
  --dark-yellow: #e4ac31;
  --dark-green: #1d9da6;
  --dark-blue: #3a7fd8;
  --dark-purple: #695dd0;
  --light-brown: #b9968e;
  --brown: #a87c72;
  --dark-brown: #9a695e;
}

* {
  box-sizing: border-box;
}

html {
  font-family: NanumSquareNeo-Variable, sans-serif;
  font-size: 16px;
}

body {
  margin: 0 auto;
  padding: 16px;
  max-width: 720px;
  width: 100%;
}

a {
  color: var(--purple);
}

.heading {
  font-size: 32px;
  margin: 32px 0;
  text-align: center;
}

.options {
  display: flex;
  gap: 32px;
  margin: 32px 0;
}

.option {
  flex: 1 0;
  display: flex;
  flex-direction: row;
  justify-content: center;
  align-items: center;
  gap: 16px;
  padding: 24px;
  background-color: var(--n100);
  border-radius: 8px;
}

#box {
  height: 100px;
  margin: 0 auto;
  border-radius: 4px;
  border-style: solid;
  border-width: 16px;
}

우선 테마 저장 기능을 구현하기위해 무엇을 해야하는지 생각해보자.

사용자가 색상을 선택하면 박스의 색상이 바뀌고

그 값이 저장되어야 한다.

즉, 다음과 같은 기능을 구현 해야야한다.

  1. UI 요소 가져오기 (input type="color", div 등)
  2. 사용자가 색상을 선택하면 박스의 색상을 변경
  3. 변경된 색상을 저장 (localStorage)
  4. 페이지를 새로고침해도 로컬 스토리지에 저장된 색상을 유지

필요한 요소 정리 (HTML 분석)

우선 자바스크립트에서 조작해야 할 요소들을 정리해야 한다.

HTML 파일을 보면 색상을 변경할 두 개의 input 태그와 색상이 적용될 박스가 있다.

  • #box-color : 박스 배경색을 선택하는 색상 입력 필드
  • #box-border-color: 박스 테두리 색상을 선택하는 색상 입력 필드
  • #box: 색상을 적용할 대상

이제 이 요소들을 자바스크립트에서 가져와서 조작해야한다.


▶ 필요한 기능

 - Step 1. 요소를 가져오기 (document.getElementById)
 - Step 2. 색상이 변경되면 change 이벤트 감지
 - Step 3. 변경된 색상을 박스에 적용
 - Step 4. 변경된 색상을 localStorage에 저장
 - Step 5. 저장된 색상을 페이지 로드 시 다시 적용

 

▼ Step 1. 요소 가져오기

getElementById를 사용하여 요소를 가져온다.

// index.js

//  Step 1. 요소를 가져오기 (document.getElementById)
const box = document.getElementById('box');
const boxColorInput = document.getElementById('box-color');
const boxBorderColorInput = document.getElementById('box-border-color');

 

Step 2. 색상이 변경되면 change 이벤트 감지

// Step 2. 색상이 변경되면 이벤트 감지
boxColorInput.addEventListener('change', (event) => {
  console.log('배경색이 변경됨:', event.target.value);
})

boxBorderColorInput.addEventListener('change', (event) => {
  console.log('테두리색이 변경됨:', event.target.value);
})

사용자가 색상을 선택하면 콘솔에 출력 되게 한다. (디버깅)

 

▼ Step 3. 변경된 색상을 박스에 적용

// Step 2. 색상이 변경되면 이벤트 감지
boxColorInput.addEventListener('change', (event) => {
  // console.log('배경색이 변경됨:', event.target.value);
  // Step 3. 변경된 색상을 박스에 적용
  box.style.backgroundColor = event.target.value;
})

boxBorderColorInput.addEventListener('change', (event) => {
  // console.log('테두리색이 변경됨:', event.target.value);
  // Step 3. 변경된 색상을 박스에 적용
  box.style.borderColor = event.target.value;

})

 

▼ Step 4. 변경된 색상을 저장 (localStorage)

// Step 2. 색상이 변경되면 이벤트 감지
boxColorInput.addEventListener('change', (event) => {
  const color = event.target.value;
  // console.log('배경색이 변경됨:', event.target.value);
  // Step 3. 변경된 색상을 박스에 적용
  box.style.backgroundColor = color; 
  //  Step 4. 변경된 색상을 저장 (localStorage)
  localStorage.setItem('boxColor', color);
})

boxBorderColorInput.addEventListener('change', (event) => {
  const borderColor = event.target.value;
  // console.log('테두리색이 변경됨:', event.target.value);
  // Step 3. 변경된 색상을 박스에 적용
  box.style.borderColor = borderColor;
  //  Step 4. 변경된 색상을 저장 (localStorage)
  localStorage.setItem('boxBorderColor', borderColor);
})

이제 변경된 색상이 localStorage 에 저장되지만

새로고침하면 다시 기본값으로 돌아오는 문제가 발생한다.

 

Step 5. 저장된 색상을 페이지 로드 시 다시 적용

새로고침을 하면 기존의 변수 값이 사라지므로 localStorage에서 저장된 값을 다시 불러와야한다.

사용자가 설정한 색상을 유지하려면

페이지가 로드될 때 이전에 저장된 값이 있으면 불러와서 적용해야한다.

 

우선 localStorage에 저장된 boxColor랑 boxBorderColor 값을 가져와야 한다

const savedBoxColor = localStorage.getItem('boxColor');
const savedBoxBorderColor = localStorage.getItem('boxBorderColor');

 

그런 다음 if문을 열어서 null 값이아닌

saveBoxColor와 saveBOxBorderColor 값이 존재한다면

box의 배경색과 테두리색을 저장된 값으로 변경해주고

input type="color' 에도 저장된 값을 적용해줘야한다.

또한 UI와 실제 박스 색상이 일치하도록 해줘야한다.

  if (saveBoxColor) {
    box.style.backgroundColor = saveBoxColor;
    boxColorInput.value = saveBoxColor;
  }
  if (saveBoxBorderColor) {
    box.style.borderColor = saveBoxBorderColor;
    boxBorderColorInput.value = saveBoxBorderColor;
  } 
}

 

그리고 페이지가 로드될 때 (window.onload) 실행해야 저장된 색상이 적용되므로

로컬 스토리지에서 가져온 색상을 박스와 색상 선택기에 반영해야한다.

loadSavedColors(); // 페이지 로드 시 실행

 

모든 단계를 합치면 다음과 같이 정상적으로 작동하는 것을 확인할 수 있다.

const box = document.getElementById('box');
const boxColorInput = document.getElementById('box-color');
const boxBorderColorInput = document.getElementById('box-border-color');

function loadSavedColors() {
  const savedBoxColor = localStorage.getItem('boxColor');
  const savedBoxBorderColor = localStorage.getItem('boxBorderColor');

  if (savedBoxColor) {
    box.style.backgroundColor = savedBoxColor;
    boxColorInput.value = savedBoxColor;
  }

  if (savedBoxBorderColor) {
    box.style.borderColor = savedBoxBorderColor;
    boxBorderColorInput.value = savedBoxBorderColor;
  }
}

boxColorInput.addEventListener('change', (event) => {
  const color = event.target.value;
  box.style.backgroundColor = color;
  localStorage.setItem('boxColor', color);
});

boxBorderColorInput.addEventListener('change', (event) => {
  const borderColor = event.target.value;
  box.style.borderColor = borderColor;
  localStorage.setItem('boxBorderColor', borderColor);
});

loadSavedColors();

 


+α 기본값 설정하기
기본값을 미리 지정하면 로컬 스토리지에 값이 없을 때도 색상이 적용된다

우선 기본값을 먼저 설정해준다.

// Step 5. 저장된 색상을 페이지 로드 시 다시 적용
function loadSavedColors() {
  // 색상 기본값 설정
  const defaultBoxColor = '#ff0000'; // 기본 배경색 (빨강)
  const defaultBoxBorderColor = '#0000ff'; // 기본 테두리색 (파랑)

  const savedBoxColor = localStorage.getItem('boxColor');
  const savedBoxBorderColor = localStorage.getItem('boxBorderColor');

  if (savedBoxColor) {
    box.style.backgroundColor = savedBoxColor;
    boxColorInput.value = savedBoxColor;
  }

  if (savedBoxBorderColor) {
    box.style.borderColor = savedBoxBorderColor;
    boxBorderColorInput.value = savedBoxBorderColor;
  }
}

loadSavedColors(); // 페이지 로드 시 실행

 

그런다음 || 연산자로 boxColor가 null이면 기본값 #ff0000이 적용되고

boxBorderColor가 null이면 기본값 #0000ff이 적용되게 해준다.

// Step 5. 저장된 색상을 페이지 로드 시 다시 적용
function loadSavedColors() {
  // 색상 기본값 설정
  const defaultBoxColor = '#ff0000'; // 기본 배경색 (빨강)
  const defaultBoxBorderColor = '#0000ff'; // 기본 테두리색 (파랑)

  const savedBoxColor = localStorage.getItem('boxColor') || defaultBoxColor; // 저장된 값이 없으면 기본값 사용
  const savedBoxBorderColor = localStorage.getItem('boxBorderColor') || defaultBoxBorderColor; // 저장된 값이 없으면 기본값 사용

  if (savedBoxColor) {
    box.style.backgroundColor = savedBoxColor;
    boxColorInput.value = savedBoxColor;
  }

  if (savedBoxBorderColor) {
    box.style.borderColor = savedBoxBorderColor;
    boxBorderColorInput.value = savedBoxBorderColor;
  }
}

loadSavedColors(); // 페이지 로드 시 실행

 


장바구니 구현하기

파일이 많은 관계로 아예 통으로 업로드함

03.장바구니구현.zip
0.01MB

 


이번 장바구니 구현은 로컬 스토로지가 아닌 세션 스토리지를 사용할 것이다.

로컬 스토리지 vs. 세션 스토리지 차이점

  로컬 스토리지 (localStorage) 세션 스토리지 (sessionStorage)
데이터 유지 기간 브라우저를 닫아도 유지됨 브라우저를 닫으면 삭제됨
용도 장기적으로 저장할 때 일시적인 데이터 저장 (예: 장바구니)
예제 사용자 테마 설정, 로그인 정보 장바구니, 폼 입력값 유지

장바구니는 "세션 동안만" 유지되는 것이 일반적이므로 sessionStorage를 사용한다.

 

우선 시작하기 전 각 js 파일이 어떤 용도로 쓰이는지 파악해보자.

  index.js (메인 페이지) shopping-cart.js (장바구니 페이지)
사용 목적 상품을 장바구니에 추가하고 개수를 업데이트 장바구니에 담긴 상품을 표시
주요 기능 "담기" 버튼 클릭 시 세션 스토리지에 저장 세션 스토리지에서 상품을 가져와 화면에 표시
페이지 이동 "장바구니로 가기" 버튼 클릭 시 페이지 이동 "주문하기" 버튼 클릭 시 장바구니 초기화 후 이동

 

그렇다면 최종적으로 세션 스토리지를 이용하여 장바구니 기능을 구현하기위해

필요한 기능들을 정리해보자

index.js 에서 필요한 기능

 - Step 1. 장바구니 데이터를 세션 스토리지에서 가져오기
 - Step 2. 장바구니 데이터를 세션 스토리지에 저장하기
 - Step 3. 장바구니 총 개수 계산하기
 - Step 4. 페이지 로드 시 장바구니 개수 업데이트
 - Step 5. "담기" 버튼 클릭 시 상품을 장바구니에 추가하는 함수

 - Step 6. "장바구니로 가기" 버튼 클릭 시 페이지 이동

 

shopping-cart.js 에서 필요한기능

 - Step 1. 장바구니 데이터를 세션 스토리지에서 가져오기
 - Step 2. HTML 리스트에 상품 추가하기
 - Step 3. 장바구니 상품 목록을 화면에 표시하기
 - Step 4. "주문하기" 버튼 클릭 시 실행하기
 - Step 5. 페이지 로드 시 장바구니 목록 표시하기

 - Step 6. 이벤트 리스너 설정

 

세션 스토리지는 Web Storage API를 따르기 때문에

로컬 스토리지와 동일한 방식으로 사용할 수 있다고 했으니

테마 저장에서 배운것과 동일하므로 전체 코드로 작성했다.

// index.js

// 장바구니 개수를 표시할 요소 가져오기
const totalItemCountInCartText = document.querySelector('#total-item-count-in-cart');

/**
 * Step 1: 장바구니 데이터를 세션 스토리지에서 가져오기
 * 세션 스토리지에서 'cartItems' 키에 저장된 데이터를 불러오고, 없으면 빈 객체 {}를 반환.
 */
function getCartItems() {
  return JSON.parse(sessionStorage.getItem('cartItems')) || {};
}

/**
 * Step 2: 장바구니 데이터를 세션 스토리지에 저장하기
 * JavaScript 객체를 JSON 문자열로 변환하여 'cartItems' 키로 저장.
 */
function setCartItems(cartItems) {
  sessionStorage.setItem('cartItems', JSON.stringify(cartItems));
}

/**
 * Step 3: 장바구니 총 개수 계산하기
 * 장바구니에 담긴 모든 상품의 총 개수를 계산하여 반환.
 */
function getTotalItemCount() {
  const cartItems = getCartItems();
  let totalItemCount = 0;
  
  for (const itemCount of Object.values(cartItems)) {
    totalItemCount += itemCount;
  }
  
  return totalItemCount;
}

/**
 * Step 4: 페이지 로드 시 장바구니 개수 업데이트
 * 장바구니 개수를 세션 스토리지에서 가져와 화면에 표시.
 */
function setInitialValues() {
  totalItemCountInCartText.innerHTML = getTotalItemCount();
}

/**
 * Step 5: "담기" 버튼 클릭 시 상품을 장바구니에 추가하는 함수
 */
function handleAddClick(button) {
  const itemName = button.dataset['itemName']; // 버튼에서 상품 이름 가져오기
  const cartItems = getCartItems(); // 현재 장바구니 데이터 불러오기

  // 해당 상품의 개수 증가
  cartItems[itemName] = (cartItems[itemName] || 0) + 1;

  // 업데이트된 장바구니 데이터를 세션 스토리지에 저장
  setCartItems(cartItems);

  // 장바구니 개수 업데이트
  totalItemCountInCartText.innerHTML = getTotalItemCount();
}

/**
 *  Step 6: "장바구니로 가기" 버튼 클릭 시 페이지 이동
 */
function handleCartClick(e) {
  e.preventDefault();
  const totalItemCount = getTotalItemCount();

  // 장바구니가 비어 있으면 empty-shopping-cart.html로 이동, 아니면 shopping-cart.html로 이동
  window.location.href = totalItemCount > 0 ? './shopping-cart.html' : './empty-shopping-cart.html';
}

/**
 * Step 7: 이벤트 리스너 설정
 */
function setEventListeners() {
  // "담기" 버튼 클릭 이벤트 등록
  document.querySelectorAll('.add-to-cart-btn').forEach((button) =>
    button.addEventListener('click', () => handleAddClick(button))
  );

  // "장바구니로 가기" 버튼 클릭 이벤트 등록
  document.querySelector('#go-to-shopping-cart').addEventListener('click', handleCartClick);
}

// 페이지 로드 시 실행
setInitialValues();
setEventListeners();

 

//shopping-cart.js

// 상품 이름을 변환하기 위한 사전(Dictionary)
const dictionary = {
  apple: '사과',
  orange: '오렌지',
  banana: '바나나',
};

/**
 * Step 1: 장바구니 데이터를 세션 스토리지에서 가져오기
 */
function getCartItems() {
  return JSON.parse(sessionStorage.getItem('cartItems')) || {};
}

/**
 * Step 2: HTML 리스트에 상품 추가하는 함수
 */
function addItemToList(name, count) {
  const cartItemList = document.getElementById('cart-item-list'); // 리스트 가져오기
  const item = document.createElement('li'); // 새로운 리스트 아이템 생성
  item.textContent = `${dictionary[name]}: ${count}개`; // 상품명 + 개수 표시
  cartItemList.appendChild(item); // 리스트에 추가
}

/**
 * Step 3: 장바구니 상품 목록을 화면에 표시하는 함수
 */
function createCartItemList() {
  const cartItems = getCartItems();
  for (const [key, value] of Object.entries(cartItems)) {
    addItemToList(key, value);
  }
}

/**
 * Step 4: "주문하기" 버튼 클릭 시 실행되는 함수
 */
function handleOrderButtonClick() {
  alert('주문이 완료되었습니다!');
  sessionStorage.removeItem('cartItems'); // 장바구니 데이터 삭제
  window.location.href = './index.html'; // 메인 페이지로 이동
}

/**
 * Step 5: 페이지 로드 시 장바구니 목록 표시 (초기화 함수)
 */
function setInitialValues() {
  createCartItemList();
}

/**
 * Step 6: 이벤트 리스너 설정
 */
function setEventListeners() {
  document.querySelector('#order-btn').addEventListener('click', handleOrderButtonClick);
}

// 페이지 로드 시 실행
setInitialValues();
setEventListeners();

 

반응형