
앞에서 공부했던 것들을 바탕으로 직접 간단한 쇼핑몰 사이트를 구현해보자
먼저, 큰 틀은 다음과 같다.
html
<nav class="navbar navbar-light bg-black">
<div class="container-fluid">
<span class="navbar-brand text-white">영긔네 장난감 가게</span>
</div>
</nav>
<div class="container" style="padding-top: 25px;">
<input id="search" placeholder="검색어입력">
</div>
<div class="container">
<div class="row product-list">
<!-- <div class="col-md-3">
<img src="">
<h4>상품명 : </h4>
<p>가격 : </p>
</div> -->
</div>
</div>
<div class="container basket-wrap" style="background : #e2e2e2">
<h4>장바구니 (드래그 가능)</h4>
<div class="row basket" ondrop="drop(event)" ondragover="allowDrop(event)">
</div>
</div>
<div class="container my-3">
<h4>최종가격 : <span class="final-price"></span></h4>
<button class="buy" onclick="buyItem()">구매하기</button>
</div>
css
body {
background: #f4f4f4;
}
button {
background: black;
color: white;
border-radius: 3px;
border: none;
padding: 5px 12px 5px 12px;
}
img {
width: 100%;
margin-bottom: 20px;
}
.basket {
min-height: 300px;
width: 100%;
background: black;
padding: 20px;
}
.basket-wrap {
padding: 30px;
margin-top: 30px;
}
.basket div {
padding: 15px;
border-radius: 5px;
}
.item {
width: 300px;
background: white;
padding: 20px;
margin-top: 20px;
}
.modal1, .modal2 {
position: fixed;
margin: auto;
padding: 30px;
top: 0px;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
}
.black-bg {
display: none;
width: 100%;
height: 100%;
position: fixed;
background: rgba(0, 0, 0, 0.5);
z-index: 5;
padding: 30px;
/* visibility: hidden;
opacity: 0;
transition: all 1s; */
}
.white-bg {
background: white;
border-radius: 5px;
padding: 30px;
}
.show-modal {
display: block;
}
.hide{
display: none;
}

해당 단어를 검색하면 노란색으로 표시되게 해보자
json
{
"products": [
{
"id": 0,
"title": "곰돌이",
"brand": "(주)곰돌과 영긔",
"photo": "s1.jpg",
"price": 10000
},
{
"id": 1,
"title": "토끼",
"brand": "(주)토끼와 영긔",
"photo": "s2.jpg",
"price": 20000
},
{
"id": 2,
"title": "우비 세트",
"brand": "(주)우비와 영긔",
"photo": "s3.jpg",
"price": 30000
},
{
"id": 3,
"title": "펭귄 세트",
"brand": "(주)펭귄과 영긔",
"photo": "s4.jpg",
"price": 40000
}
]
}
js
/**********
* 상품진열 *
***********/
//같은 폴더 안에 store.json을 두었다.
fetch("store.json")
.then((response) => response.json())
.then((data) => {
//원본데이터 다른데서 많이 쓰니까 변수에 보관
products = data.products;
//페이지로드시 json 데이터가져와서 메인페이지 내용 만들기
show(data.products);
})
.catch((error) => console.error("Fetch error:", error));
/********************
* 상품 보여주는 함수 *
********************/
function show(data) {//상품을 나열합니다.
data.forEach((a, i) => {//데이터에 대해 반복문을 돕니다.
document.querySelector(".product-list").insertAdjacentHTML(//html 삽입
"beforeend",//앞에 나열한다는 뜻
`
<div class="col-md-3">
<div class="item" draggable="true" id="item-${a.id}">
<img src="${a.photo}" draggable="false">
<h4 class="title">${a.title}</h4>
<h4 class="brand">${a.brand}</h4>
<p class="price">가격 : ${a.price}</p>
<button class="add" data-id="${a.id}">담기</button>
</div>
</div>`//html을 작성합니다.
);//드래그할 수 있도록 draggable을 true로 주고 ongragstart를 주었습니다.
});
}
/**********
* 상품검색 *
***********/
document.getElementById("search").addEventListener("input", function () {
let sText = this.value;//입력된 검색어를 input할때마다 가져와 sText로 할당합니다.
//이전 거 초기화합니다.
document.querySelector(".product-list").innerHTML = "";
let searchData = products.filter((a) => {//filter를 사용하여 걸러줍니다.
//includes를 사용하여 단어가 있는지 검사합니다.
return a.title.includes(sText) || a.brand.includes(sText);
});
//앞에 작성한 상품나열 함수에 데이터를 넣습니다.
show(searchData);
// .product-list 클래스를 가진 요소를 선택합니다.
var productElements = document.querySelectorAll(".product-list h4");
// 각 요소에 대해 반복합니다.
productElements.forEach(function (a) {
var title = a.innerHTML;
// 검색어를 찾아서 강조합니다.
title = title.replace(
sText,
`<span style="background : yellow">${sText}</span>`
);
// 요소의 innerHTML을 변경하여 강조된 검색어가 포함된 제목을 표시합니다.
a.innerHTML = title;
});
});

/***********************
* 장바구니 버튼으로 담기 *
***********************/
//최종가격 초기화
var finalPrice = 0;
//버튼으로 담는 함수. 앞에서 '담기'버튼을 누르면 handleDrop()이 작동하게 작성
function handleDrop(ev) {
ev.preventDefault();
//담기 버튼이 ev이므로 부모 요소를 가져온다.
var target = ev.target.parentElement;
var data = target.id;//식별 아이디
var imgData = target.querySelector("img").src;//이미지
var titleData = target.querySelector(".title").innerText;//상품명
var brandData = target.querySelector(".brand").innerText;//브랜드명
var priceData = target.querySelector(".price").innerText;//가격
//장바구니에서 식별 아이디로 이미 담겨있는 상품들을 가져온다.
const existingItem = document.querySelector(".basket")
.querySelector(`#${data}`);
if (existingItem) {//해당 상품이 장바구니에 이미 있을 경우
//해당 상품의 갯수
let countElement = existingItem.querySelector(".count");
//해당 상품의 갯수를 정수화시켜준다. 더해주기 위해
let number = parseInt(countElement.value);
number += 1;
countElement.value = number;//1더해주고 다시 값을 넣어준다.
//앞에 붙어있는 '가격 : '을 떼준값을 최종가격에 더해준다.
finalPrice += parseInt(priceData.substr(5));
} else {//장바구니에 해당 상품이 없을 경우
let number = 1;
document.querySelector(".basket").insertAdjacentHTML(
"beforeend",
`
<div class="col-md-3 white-bg" id="${data}">
<div>
<img src="${imgData}" alt="Product Image">
<h4 class="title">${titleData}</h4>
<h4 class="brand">${brandData}</h4>
<p class="price">${priceData}</p>
<p>수량: <input type="number" class="count" value="${number}" min="1"></p>
</div>
</div>`
);//장바구니에 해당 상품 넣어준다.
finalPrice += parseInt(priceData.substr(5));//최종가격 업데이트
}
//해당 아이템의 갯수 가져옴
const newItem = document.querySelector(`#${data} .count`);
//blur(포커스를 잃는 순간)일때 update 함수 실행
newItem.addEventListener("blur", update);
//업데이트한 최종가격 화면에 반영
document.querySelector(".final-price").innerText = finalPrice;
}
//최종가격 업데이트 실행
function update() {
finalPrice = 0;//초기화
//장바구니에 있는 아이템 반복돌림
document.querySelectorAll(".basket .col-md-3").forEach((item) => {
const price = parseInt(
item.querySelector(".price").innerText.replace("가격 : ", "")
);
const count = parseInt(item.querySelector(".count").value);
//값, 수량 구해와서 곱한 거 더해줘서 최종가격 구함
finalPrice += price * count;
});
document.querySelector(".final-price").innerText = finalPrice;
}

우비세트를 두번 드래그 하자 수량이 늘어나는 것을 볼 수 있다.
장바구니에 담으면 최종가격이 업데이트 된다.
/****************
* 드래그 앤 드롭 *
****************/
//최종가격 초기화
var finalPrice = 0;
//장바구니에 드롭놓을때 실행
function allowDrop(ev) {
ev.preventDefault();
}
//해당 상품 드래그할 때 실행
function drag(ev) {
//dataTransfer.setData로 드래그할 데이터를 전달한다.
ev.dataTransfer.setData("text", ev.target.id);//id
ev.dataTransfer.setData("img", ev.target.querySelector("img").src);//이미지
ev.dataTransfer.setData("title", ev.target.querySelector(".title").innerText);//상품명
ev.dataTransfer.setData("brand", ev.target.querySelector(".brand").innerText);//브랜드명
ev.dataTransfer.setData("price", ev.target.querySelector(".price").innerText);//가격
}
//장바구니에 드롭할 때 실행
function drop(ev) {
ev.preventDefault();
//dataTransfer.getData로 데이터를 가져온다.
var data = ev.dataTransfer.getData("text");
var imgData = ev.dataTransfer.getData("img");
var titleData = ev.dataTransfer.getData("title");
var brandData = ev.dataTransfer.getData("brand");
var priceData = ev.dataTransfer.getData("price");
//장바구니에 드롭할 때
if (ev.target.classList.contains("basket")) {
//id로 드래그한 상품이 있는지 찾음
const existingItem = ev.target.querySelector(`#${data}`);
//만약 있다면
if (existingItem) {
let countElement = existingItem.querySelector(".count");
let number = parseInt(countElement.value);
//상품 갯수 불러와 1더해주고 다시 값 넣어줌
number += 1;
countElement.value = number;
//최종가격 업데이트
finalPrice += parseInt(priceData.substr(5));
} else {//아닐경우
let number = 1;
ev.target.insertAdjacentHTML(
"beforeend",
`
<div class="col-md-3 white-bg" id="${data}">
<div>
<img src="${imgData}" alt="Product Image">
<h4 class="title">${titleData}</h4>
<h4 class="brand">${brandData}</h4>
<p class="price">${priceData}</p>
<p>수량: <input type="number" class="count" value="${number}" min="1"></p>
</div>
</div>`
);
finalPrice += parseInt(priceData.substr(5));
}
}
document.querySelector(".final-price").innerText = finalPrice;
}
//최종가격 업데이트 실행
function update() {
finalPrice = 0;//초기화
//장바구니에 있는 아이템 반복돌림
document.querySelectorAll(".basket .col-md-3").forEach((item) => {
const price = parseInt(
item.querySelector(".price").innerText.replace("가격 : ", "")
);
const count = parseInt(item.querySelector(".count").value);
//값, 수량 구해와서 곱한 거 더해줘서 최종가격 구함
finalPrice += price * count;
});
document.querySelector(".final-price").innerText = finalPrice;
}

직접 수량을 입력하면 수량이 변경되고, 최종가격이 업데이트된다.
//최종가격 업데이트 실행
function update() {
finalPrice = 0;//초기화
//장바구니에 있는 아이템 반복돌림
document.querySelectorAll(".basket .col-md-3").forEach((item) => {
const price = parseInt(
item.querySelector(".price").innerText.replace("가격 : ", "")
);
const count = parseInt(item.querySelector(".count").value);
//값, 수량 구해와서 곱한 거 더해줘서 최종가격 구함
finalPrice += price * count;
});
//이 부분이 수량 직접 입력하면 업데이트해주는 코드
document.querySelector(".final-price").innerText = finalPrice;
}

구매하기를 누르면 모달창이 표시되도록 한다.
html
<div class="modal1" style="display: none;">
<div class="white-bg">
<h4>성함</h4>
<input type="text" id="name">
<h4>연락처</h4>
<input type="text" id="phone">
<button class="show-receipt">입력완료</button>
<div>
<button class="close">닫기</button>
</div>
</div>
</div>
js
/**********
* 상품구매 *
***********/
//입력폼
function buyItem() {
document.querySelector(".modal1").style.display = "block";
}
document.querySelector(".modal1 .close").addEventListener("click", function () {
document.querySelector(".modal1").style.display = "none";
});

입력완료 버튼을 누를 시 영수증 이미지를 보여준다. 이때 영수증 이미지에는 구매한 상품들과 최종가격을 나열해준다.
html
<div class="modal2" style="display: none;">
<div class="white-bg">
<h4>영수증</h4>
<canvas id="canvas" width="350" height="350"></canvas>
<div>
<button class="close">닫기</button>
</div>
</div>
</div>
js
//구매 영수증 이미지 표시
document.querySelector(".show-receipt").addEventListener("click", function () {
//이전 모달창 닫기
document.querySelector(".modal1").style.display = "none";
//영수증 모달창 보이기
document.querySelector(".modal2").style.display = "block";
//캔버스 만들어줌
var canvas = document.getElementById("canvas");
var c = canvas.getContext("2d");
c.font = "20px dotum";
//최종가격 초기화
finalPrice = 0;
//칸 띄워줄때 쓸 변수
var i = 1;
//장바구니에 있는 상품 반복문 돌리기
document.querySelectorAll(".basket .col-md-3").forEach((item) => {
const title = item.querySelector(".title").innerText;
const price = parseInt(
item.querySelector(".price").innerText.replace("가격 : ", "")
);
const count = parseInt(item.querySelector(".count").value);
//최종가격 구해줌
finalPrice += price * count;
c.fillText(title, 30, i * 70 - 20);//상품명 표시.두 세번째 파라미터는 위치
c.fillText("가격 : " + price, 30, i * 70 + 5);//가격 표시
c.fillText("수량 : " + count, 30, i * 70 + 30);//수량 표시
i++;
});
document.querySelector(".final-price").innerText = finalPrice;
//영수증 마지막에 최종가격 표시
c.fillText("최종 가격 : " + finalPrice, 30, i * 70 - 20);
});
//영수증 닫기
document.querySelector(".modal2 .close").addEventListener("click", function () {
document.querySelector(".modal2").style.display = "none";
var canvas = document.getElementById("canvas");
var c = canvas.getContext("2d");
//영수증 초기화. 초기화해주지 않으면 닫고 다시 상품추가할때 엉망으로 나옴
c.clearRect(0, 0, canvas.width, canvas.height);
});
0.5x로 설정하는 것을 추천한다.(모바일은 0.25x)
짱이에요