올리브영 온라인몰 클론코딩 (적응형 PC, Mobile)

핑구·2023년 8월 1일
0

Portfolio

목록 보기
4/5
post-thumbnail

🫒 올리브영 온라인몰

👉 사이트명 : 올리브영 온라인몰
👉 작업기간 : 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


POINT ✨

✔️ Fetch 비동기 통신을 활용해 JSON 파일의 정보 불러오기
✔️ Filter로 원하는 값의 데이터를 분류
✔️ 3단 카테고리 구성
✔️ 다른상품 추천, 다른 키워드 더보기 버튼 클릭시 새로운 정보 불러오기
✔️ Swiper 커스텀 - 내장 속성 'thumbs' 활용 , 그리드 형태의 슬라이더 구현
✔️ 모바일에서 이미지 height 고정형으로 개선
✔️ Swiper 커스텀 - 실시간 View 랭킹


📍 Fetch 비동기통신 활용

👉 event.json

json 파일을 만들어 불러올 정보들을 담아주었다.

{
    "items":[
        {
            "thumbUrl":"./assets/images/banner01.jpg",
            "title":"팔로우하면 20% 쿠폰 GET",
            "linkUrl":"#banner01"
        },
        {
            "thumbUrl":"./assets/images/banner02.jpg",
            "title":"올리브 멤버스 하반기 혜택",
            "linkUrl":"#banner03"
        }
    ]
}

👉 main.js

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 정보를 불러온다
    })
  
  })

📍 Filter로 원하는 값을 분류

👉 이벤트 영역과 브랜드 영역

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 구성

👉 대분류 > 중분류 > 소분류

전체 카테고리 버튼을 누르면 나오는 영역은
"뷰티 > 스킨케어 > 토너/로션/올인원" 형태의 3단 분류로 되어있다.

category.js

중분류를 기준으로 category.json 파일에 작성하면서
소분류는 submenu라는 key에 배열로 만들어주고,
대분류는 sort라는 key를 만들어서 필터로 분류해주었다.

{ 
  "items":[
  {
    "id":1,
    "name":"스킨케어",
    "sort":"뷰티",
    "submenu":[
      {
        "id":1001,
        "name":"토너/로션/올인원"
      },
      {
        "id":1002,
        "name":"에센스/크림"
      },
      {
        "id":1003,
        "name":"미스트/오일"
      }
    ]
  },
  .
  .
  .

main.js

// 전체 카테고리 불러오기
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 커스텀

👉 탭 메뉴를 슬라이더와 연동

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, 
  }
})

👉 Grid 형태의 슬라이더

main.js

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의 높이) */
}

👉 실시간 View 랭킹


① 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도 따라서 줄어든다.

개선 후👆

화면 너비가 줄어들어도 height는 그대로 유지된다.
이미지에 object-fit:cover를 주어 넘치는 부분은 깔끔하게 잘리도록 했다.


📍 Healthy life CSS 개선


기존 올리브영 사이트에서 Healthy 영역의 텍스트 박스 배경이미지까지 통으로 쓰고있다.
그런데 이 부분이 마우스를 hover 하면 scale이 살짝 커지는 모션이 들어가는데, 그때 배경색과 이미지의 경계부분이 좀 뭉개져서 개선하기로 했다.


클론코딩 작업할 때 필요한 이미지 부분만 잘라서 업로드하고, 색상 배경 부분은 CSS에서 Background로 처리했다.


(+ ISSUE ) 슬라이더는 then()함수 안쪽에 ..💦

함수 바깥쪽에 슬라이더 함수를 쓰게되면 동적으로 생성된 요소를 슬라이더 함수가 감지하지 못해 각 슬라이드마다 인덱스 값이 NaN으로 뜨고 페이징이 제대로 동작하지 않는다.
이거때문에 한참 헤맸다.🤢 오랜 시간을 서칭해본 끝에 해결 완료 💦

profile
배운것은 그날그날 잊지않고 기록하기

0개의 댓글