👉 사이트명 : 올리브영 온라인몰
👉 작업기간 : PC버전 4일, 모바일 1.5일
👉 사용언어 : HTML5, CSS3, Jquery, JSON
👉 라이브러리 : Swiper.js
👉 분류 : PC 적응형, Mobile 적응형 웹사이트
👉 URL :
PC > https://seona-cha.github.io/oliveyoung-pc
Mobile > https://seona-cha.github.io/oliveyoung-mobile
✔️ Fetch 비동기 통신을 활용해 JSON 파일의 정보 불러오기
✔️ Filter로 원하는 값의 데이터를 분류
✔️ 3단 카테고리 구성
✔️ 다른상품 추천, 다른 키워드 더보기 버튼 클릭시 새로운 정보 불러오기
✔️ Swiper 커스텀 - 내장 속성 'thumbs' 활용 , 그리드 형태의 슬라이더 구현
✔️ 모바일에서 이미지 height 고정형으로 개선
✔️ Swiper 커스텀 - 실시간 View 랭킹
json 파일을 만들어 불러올 정보들을 담아주었다.
{
"items":[
{
"thumbUrl":"./assets/images/banner01.jpg",
"title":"팔로우하면 20% 쿠폰 GET",
"linkUrl":"#banner01"
},
{
"thumbUrl":"./assets/images/banner02.jpg",
"title":"올리브 멤버스 하반기 혜택",
"linkUrl":"#banner03"
}
]
}
json파일에 담긴 정보를 불러오기!
fetch('./assets/data/event.json')
.then(res=>res.json())
.then(json=>{
data = json.items // 전체 데이터를 data라는 변수에 담기
let html=``; // 데이터 입력을 위한 변수 할당
data.forEach(element=>{
html+=`${element.title}` // json 파일의 items의 title 정보를 불러온다
})
})
product.json 파일에서 각각 EventId, brand 값이 일치하는 상품만 뽑아왔다!
{
"items":[
{
"id":"prd01",
"cate":"1001",
"brand":"에스트라",
"productName":"에스트라 아토베리어365 로션 150ml 기획 ...",
"thumbUrl":"./assets/images/product01.jpg",
"best":true,
"cost":31000,
"sale":23560,
"tag":["세일","쿠폰","증정","오늘드림"],
"eventId":false,
"view":32,
"linkUrl":"#product1"
},
.
.
.
Filter를 함수로 만들어서 매개변수에 원하는 값을 넣어 호출해주는 방식으로 뽑아왔다.
// 브랜드별 상품 리스트 불러오기
fetch('./assets/data/product.json')
.then(res=>res.json())
.then(json=>{
data = json.items;
function sortData(brandId,sortId){
// 브랜드가 일치하는 상품 필터링
brandData = data.filter(function(parm){
return parm.brand === sortId;
})
let html=``;
brandData.forEach(element=>{
.
.
})
$(`.sc-brand .brand${brandId}`).html(html);
}
sortData(1,"빌리프");
sortData(2,"아벤느");
sortData(3,"빌리프");
sortData(4,"아벤느");
})
전체 카테고리 버튼을 누르면 나오는 영역은
"뷰티 > 스킨케어 > 토너/로션/올인원" 형태의 3단 분류로 되어있다.
중분류를 기준으로 category.json 파일에 작성하면서
소분류는 submenu라는 key에 배열로 만들어주고,
대분류는 sort라는 key를 만들어서 필터로 분류해주었다.
{
"items":[
{
"id":1,
"name":"스킨케어",
"sort":"뷰티",
"submenu":[
{
"id":1001,
"name":"토너/로션/올인원"
},
{
"id":1002,
"name":"에센스/크림"
},
{
"id":1003,
"name":"미스트/오일"
}
]
},
.
.
.
// 전체 카테고리 불러오기
fetch('./assets/data/category.json')
.then(res=>res.json())
.then(json=>{
data=json.items; // 전체 데이터
let html=``;
// 카테고리 출력 폼
function cateAll(sortId){
cateList = data.filter(function(parm){
return parm.sort == sortId; // 대분류 필터
})
// 대분류
html+=`
<div class="group-cate">
<h3>${sortId}</h3>
<ul class="cate-list">
`
//중분류
cateList.forEach(element => {
html+=`<li class="cate-item">
<a href="#${element.id}">${element.name}</a>
<ul class="sub-list">`
//소분류
sub = element.submenu;
sub.forEach(element=>{
html+=`
<li class="sub-item"><a href="#${element.id}">${element.name}</a></li>
`
})
html+=`
</ul>
</li>
`
});
html+=`
</ul>
</div>
`
$(".gnb .cate-wrapper").html(html);
}
// 대분류 값 넣어서 카테고리 출력폼 호출
cateAll("뷰티");
cateAll("헬스&푸드");
cateAll("라이프");
})
👉 추천 상품, 키워드 영역의 버튼을 누르면 다른 상품을 호출해야한다.
👉 이 부분 역시 함수를 만들어서, 버튼을 누르면 다른 매개변수값을 넣은 함수를 호출하는 방식으로 만들었다.
current3 = 1; // 현재 값을 저장하기위한 변수
function keywords(i,j){
fetch('./assets/data/keywords.json')
.then(res=>res.json())
.then(json=>{
data=json.items;
html=``;
while(i < j){
element = data[i];
html+=`
<li class="keyword-item">
<a href="${element.linkUrl}">
<figure class="img-box">
<img src="${element.thumbUrl}" alt">
</figure>
<div class="text-box">
<h3>${element.title}</h3>
<p>${element.subTitle}</p>
</div>
</a>
</li>
`
i++;
}
$('.sc-keyword .keyword-list').html(html);
});
}
keywords(0,2); //기본으로 보여줄 것
$('.sc-keyword .other-btn').click(function(){
if(current3 == 1){
keywords(2,4);
current3 = 2;
}else if(current3 == 2){
keywords(4,6);
current3 = 3;
}else{
keywords(0,2);
current3 = 1;
}
$('.sc-keyword .other-btn .current').html(current3);
})
forEach 문법을 활용해서 리스트를 뽑아올 경우 return true/false여부에 관계없이 continue 되기 때문에 원하는 갯수만 뽑아오기 위해 while문법을 활용했는데, 이후 더 찾아보니 try catch를 사용하는 방법도 있었다.
Swiper.js Demo 페이지의 Thumbs gallery를 활용했다.
탭 슬라이더와 내용 슬라이더를 각각 따로 만들어 준 후, 연동해주면 된다.
const brandSlider = new Swiper(".sc-brand .swiper",{
loop:true,
spaceBetween:30,
navigation:{
nextEl:".sc-brand .next",
prevEl:".sc-brand .prev"
},
pagination:{
el:".sc-brand .pagination",
type: "fraction"
},
//이 부분!!
thumbs: {
swiper: brandTab,
}
})
swiper의 grid 속성을 이용해서 만들었다.
$(`.sc-dessert .prd-list`).html(html)
const dessertSlider = new Swiper("#dessertSlider",{
slidesPerView:3,
slidesPerGroup:3,
spaceBetween:8,
grid:{
rows:2
},
pagination:{
el:".sc-dessert .pagination"
},
})
그리드 슬라이더를 만들때 주의할 점은, .swiper-slide의 부모 요소인 .swiper-wrapper에 높이값이 꼭 지정되있어야 정상적으로 작동한다.
위에 상품 리스트의 경우 화면 너비에따라 유동적으로 값이 바뀌기때문에, calc()로 정확한 값을 계산해서 높이값을 넣어주었다 !
/* swiper-wrapper */
.sc-dessert .prd-list{
gap: 0;
min-height: 456px;
height: calc(((100vw - 30px) / 3 + 118px) * 2);
/* ((화면너비 - 양옆 margin) / 3열 +textbox의 높이) * 2행*/
}
/* swiper-slide */
.sc-dessert .prd-item{
width: 100%;
margin-top:0 !important;
min-height: 228px;
height: calc((100vw - 30px) / 3 + 118px);
/* (화면너비 - 양옆 margin) / 3열 +textbox의 높이) */
}
① 1-5위 상품이 첫번째 슬라이드, 6-10위 상품이 두번째 슬라이드에 있다.
② 3초에 한번씩 아이템의 포커스가 이동하고, 포커스가 해당 슬라이드의 끝까지 이동하면 다음 슬라이드로 넘어간다.
페이징 혹은 아이템을 클릭하면 초기화하기 위해 함수에 담아준 뒤에 호출했당
// 3초에 한번씩 자동으로 넘어감
function rankAuto(){
rankFocus++;
if(rankFocus == 11){
rankFocus = 1;
}
// rankCurr 값이 바뀌면
if(rankFocus < 6){
rankSlider.slideTo(0);
$('.ranking-prd a').removeClass('active');
$(`.sc-ranking .rank${rankFocus}`).addClass('active');
}else if(rankFocus < 11){
rankSlider.slideTo(1);
$('.ranking-prd a').removeClass('active');
$(`.sc-ranking .rank${rankFocus}`).addClass('active');
}
}
interval = setInterval(rankAuto, 3000);
③ 아이템 제목을 클릭하면 포커스가 이동한다. (이때 setInterval의 타이머 초기화)
// 아이템 클릭시
$('.sc-ranking .rank-item').click(function(a){
a.preventDefault();
rankFocus = Number($(this).attr('href').charAt(1)*10) + Number($(this).attr('href').charAt(2))
i = rankFocus ;
$('.ranking-prd a').removeClass('active');
$(`.sc-ranking .rank${rankFocus}`).addClass('active');
clearInterval(interval);
interval = setInterval(rankAuto, 3000);
})
④ 페이징 클릭시에 슬라이드가 넘어간다. (setInterval의 타이머 초기화)
let rankFocus = 1; //현재 값을 저장하는 변수
$('.sc-ranking .rank1').addClass('active');//첫번째 아이템에 기본 포커스
// 페이징 클릭시
$('.sc-ranking .swiper-pagination-bullet').click(function(a){
a.preventDefault();
i = $(this).index();
rankSlider.slideTo(i);
if(i == 0){
rankFocus = 1;
}else{
rankFocus = 6;
}
$('.ranking-prd a').removeClass('active');
$(`.sc-ranking .rank${rankFocus}`).addClass('active');
clearInterval(interval); //클릭시 인터벌의 타이머 초기화
interval = setInterval(rankAuto, 3000); // 타이머 다시 시작
})
화면 너비가 줄어들면 배너의 height도 따라서 줄어든다.
화면 너비가 줄어들어도 height는 그대로 유지된다.
이미지에 object-fit:cover
를 주어 넘치는 부분은 깔끔하게 잘리도록 했다.
기존 올리브영 사이트에서 Healthy 영역의 텍스트 박스 배경이미지까지 통으로 쓰고있다.
그런데 이 부분이 마우스를 hover 하면 scale이 살짝 커지는 모션이 들어가는데, 그때 배경색과 이미지의 경계부분이 좀 뭉개져서 개선하기로 했다.
클론코딩 작업할 때 필요한 이미지 부분만 잘라서 업로드하고, 색상 배경 부분은 CSS에서 Background로 처리했다.
함수 바깥쪽에 슬라이더 함수를 쓰게되면 동적으로 생성된 요소를 슬라이더 함수가 감지하지 못해 각 슬라이드마다 인덱스 값이 NaN으로 뜨고 페이징이 제대로 동작하지 않는다.
이거때문에 한참 헤맸다.🤢 오랜 시간을 서칭해본 끝에 해결 완료 💦