[portfolio] kakaopage 클론코딩

김연빈·2023년 4월 2일
0

portfolio

목록 보기
6/7
post-thumbnail

✔️카카오페이지 클론코딩

제작기간 : 23.03.20 ~ 23.03.23 (4일)
사용 : HTML, CSS, JSON
분류 : 클론코딩, 데이터 바인딩, tablet / mobile

⭐카카오페이지 json 데이터 파일을 생성하고, JavaScript를 사용해 html에 데이터 바인딩을 했습니다.⭐

파일 구조

  • index.html : 마크업
  • asset
    • css
      • common : 공통 소스
      • fonts : 폰트
      • layout : 레이아웃
      • main : 메인 페이지
      • reset : 리셋
    • data : JSON 데이터
      • bookData
      • eventData
      • mainBanner
      • productData
      • rankData
    • fonts : 폰트파일
    • images : 이미지파일
    • js
      • main : 메인 스크립트

1. 데이터 바인딩

Data Binding : 뷰와 모델을 묶어서 서로 간의 데이터를 동기화하는(일치시키는) 것

  • 뷰(View) : 사용자에게 보이는 부분(화면상에 보여지는 데이터)
  • 모델(Model) : 데이터를 관리하는 영역(브라우저 메모리에 있는 데이터)

2. JSON

JSON (JavaScript Object Notation)은 구조화된 데이터Javascript 객체 문법으로 표현하기 위한 문자 기반의 표준 포맷

JavaScript의 object(객체)와 같이 key(속성)-value(값)가 묶여진(property) 표기법을 사용

  • 객체(Object) : 속성 값 쌍의 집합 {중괄호} --- 프로퍼티 집합
  • 배열(Array) : 리스트 형식 [대괄호, , ] --- 데이터값 집합
  • 복수의 data는 ,로 구분
{
    "items":[
        {
            "name":"lala",
            "char":["fat","cute"],
            "age":3
        },
        {
            "name":"lulu",
            "char":"lazy",
            "age":5
        }
    ]
}

3. json을 활용한 데이더 바인딩

🔷fetch API

  • HTTP 요청 전송 기능을 제공하는 Web API
  • 웹 애플리케이션 개발에서 다른 서비스에 요청을 보내고 응답을 받기 위한 것

*API (Application Programming Interface) : 응용 프로그램에서 사용할 수 있도록, 운영체제나 프로그래밍 언어가 제공하는 기능을 제어할 수 있게 만든 인터페이스

🔷filter 함수

  • 주어진 함수의 테스트를 통과하는 모든 요소를 모아 새로운 배열로 반환

🔷indexOf 함수

  • 문자열에서 특정 문자열을 찾고, 검색된 문자열의 시작위치에 해당하는 index를 반환
  • 또는 배열에서 원하는 특정 배열값의 존재여부를 알려줌

📌html

<body>
	<ul class="list1">
	 	<!-- content -->
	</ul>
	<ul class="list2">
  	 	<!-- content -->
	</ul>
	<ul class="list3">
  	 	<!-- content -->
	</ul>
	<ul class="list4">
  	 	<!-- content -->
	</ul>

	<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</body>

📌json

{
    "items":[
        {
            "name":"철수",
            "age":20,
            "sort":"돈이많음"
        },
        {
            "name":"영수",
            "age":10,
            "sort":"가난"
        },
        {
            "name":"만수",
            "age":40,
            "sort":"돈이많음,게으름"
        },
        {
            "name":"정수",
            "age":120,
            "sort":"천재,게으름,돈이많음"
        }
    ]
}

📌js

<script>  
    // 값을 일일이 넣지 않고 함수를 쓰면 편하다!
    function humanList(sortData,frame){
        fetch('./human.json') // 통신할 데이터파일의 경로 넣기
        .then(res=>res.json()) // response로 json을 받아오기
        .then(json=>{ // 받아온 json을 실행 -> 데이터에 접근 가능!
            allData = json.items; // json 뒤에 .items를 붙이면 바로 array에 접근 가능
            // resultData = allData.filter(function(data){
            //     return data.age >= 20;
            // })
            resultData = allData.filter(function(data){ // 필터링 하기
                return data.sort.indexOf(sortData) >= 0
              	// indexOf() : 원하는 문자열이 있으면 0 이상을, 없으면 -1을 반환
              	// 즉 반환값이 0 이상이면 원하는 문자열을 포함한 문자열인 것!
              	// 이 조건에 해당하는 값만 반환
            });
            let html = '';
            resultData.forEach(element => { 
            // array를 반복 돌리기. element로 배열 하나하나에 접근 가능
                age = element.age < 20 ? '미성년자':'성인';
              	// 조건 연산자 `?`를 이용해 간단히 썼음.
                // 의미는 아래 조건문과 동일
                // if(element.age < 20){
              	// 		age = "미성년자";
            	// }else{
              	// 		age = "성인";
            	// }
                html+=`<li>
                    ${element.name}
                    나이:${age}
                    특징:${element.sort}
                    </li>`;
            });
          	// 만들어 둔 빈 html에 `+=` 으로 더하기 할당.
            // element 뒤에 .sort를 붙여서 sort 속성의 값을 불러옴
            $(frame).html(html);
            // .html() : 선택한 요소 안의 내용을 가져오거나, 다른 내용으로 바꿈. 
            // 불러온 json 데이터를 화면에 뿌려줌
        });
    };
	/*
     * @param : sortData
     * @param : frame
     */
    humanList('게으름','.list1'); // list1에 게으름 데이터를 나오게 해라
    humanList('천재','.list2');
    humanList('돈이많음','.list3');
    humanList('가난','.list4');
</script>

4. 데이터 바인딩으로 비슷한 레이아웃 한 번에 꾸미기

  • 틀 영역의 고유한 번호 sortNum으로 각 영역을 filtering하고, forEach문으로 배열 하나하나에 접근해서 json에서 받아왔던 데이터를 각각의 조건에 따라 변수에 담고, 틀 영역인 frame에 html 변수의 값을 담음.

📌json

{
    "items":[
        {
            "id":1,
            "thumb":"./assets/images/wait3-webtoon-1.png",
            "title":{
                "name":"호랑낭자뎐",
                "img":"./assets/images/wait3-webtoon-1-2.png"
            },
            "snippet":{
                "wait3":true,
                "wait":false,
                "event":false,
                "new":true,
                "free":false,
                "up":false,
                "age":0,
                "info":"10.2만"
            },
            "sort":"1,5" // 두 영역에 동시에 존재함
        },
		...
    ]
}

📌js

/**
 * @작품리스트
 */
function productList(sortNum,frame,type){
    fetch('./assets/data/productData.json')
    .then(res=>res.json())
    .then(json=>{
        allData=json.items;
        result = allData.filter(function(data){
            return data.sort.indexOf(sortNum) >= 0;
        });
        let html='';
        result.forEach(element => {

            titleEl = (type === 1)?`<img src="${element.title.img}" alt="${element.title.name}">`:element.title.name

            wait3El=(element.snippet.wait3)?`<img src="./assets/images/badge-wait3.svg" alt="3시간마다 무료">`:'';
          	// wait3가 존재하면 파일이 담긴 img 태그를, 존재하지 않으면 빈값을 변수에 담아라
            waitEl=(element.snippet.wait)?`<img src="./assets/images/badge-wait.svg" alt="기다리면 무료">`:'';
            eventEl=(element.snippet.event)?`<img src="./assets/images/badge-event.svg" alt="이벤트">`:'';
            newEl=(element.snippet.new)?`<img src="./assets/images/badge-new.svg" alt="신작">`:'';
            freeEl=(element.snippet.free)?`<img src="./assets/images/badge-free.svg" alt="연재무료">`:'';
            upEl=(element.snippet.up)?`<img src="./assets/images/badge-up.svg" alt="새회차 업로드">`:'';
            switch (element.snippet.age) {
            // switch 문 : 변수의 값과 동일한 값을 갖는 case로 가서 실행문을 실행
                case 19:
                    ageEl=`<img src="./assets/images/badge-19.svg" alt="19세 이용가">`;
                    break;
                case 15:
                    ageEl=`<img src="./assets/images/badge-15.svg" alt="15세 이용가">`;
                    break;
                default:
                    ageEl='';
                    break;
            }
            switch (element.snippet.theme) {
                case 1:
                    themeEl='웹툰';
                    break;
                case 2:
                    themeEl='웹소설';
                    break;
                case 3:
                    themeEl='채팅소설';
                    break;
                default:
                    themeEl='책';
                    break;
            }
            descEl = (type === 1)?element.snippet.info:themeEl

            html+=`<li class="swiper-slide">
            <a href="">
                <div class="img-area">
                    <div class="badge-box">
                        <div class="left">
                            ${wait3El} ${waitEl} ${eventEl} ${newEl} ${freeEl} ${upEl}
                        </div>
                        <div class="right">
                            ${ageEl}
                        </div>
                    </div>
                    <img src="${element.thumb}" alt>
                </div>
                <div class="text-area">
                    <h3 class="title">${titleEl}</h3>
                    <div class="desc">${descEl}</div>
                </div>
            </a>
            </li>`;
        });
        $(frame).html(html); // 지정한 frame 아이디값에 가서 html 변수 텍스트를 추가
    });
};
/**
 * @param sortnum - 틀 영역의 고유한 번호
 * @param frame - 틀 영역
 * @param designtype - 디자인 모양의 차이
 * 
 * @sort1 = 3다무-3시간마다무료 웹툰
 * @sort2 = 3다무-3시간마다무료 웹소설
 * @sort3 = 밀리언 페이지
 * @sort4 = #영상화까지! 3다무 웹툰
 * @sort5 = 신작 웹툰 베스트
 */
productList(1,'#list1',1);
productList(2,'#list2',1);
productList(3,'#list3',2);
productList(4,'#list4',1);
productList(5,'#list5',1);

const prdSlide = new Swiper('.prd-slide',{
    slidesPerView: 'auto', // css로 이미지에 일정한 크기를 줬으므로, 
  						   // 슬라이드 뷰 개수를 자동으로 했음
    spaceBetween:3,
    freeMode: true,
});

5. 클릭하면 동일한 값에 연결하기

📌html

<section class="sc-ranking pb16">
    <div class="flex-area">
        <h2 class="headline">실시간 랭킹</h2>
        <a href="" class="btn-all" aria-label="실시간 랭킹 전체보기">
            <svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'> <path fill-rule='evenodd' clip-rule='evenodd' d='M8.5 18.0694L14.6166 12L8.5 5.93058L9.9417 4.5L17.5 12L9.9417 19.5L8.5 18.0694Z' fill='#222222'/> </svg>
        </a>
    </div>
    <div class="flex-area2">
        <nav class="group-nav">
            <div class="nav">
              	<!-- data-prd 속성값 #prd1, #prd2로 동일한 아이디값을 가진 영역에 연결 -->
                <a href="" data-prd="#prd1" class="active">웹툰</a>
                <a href="" data-prd="#prd2">웹소설</a>
            </div>
        </nav>
        <span class="time">2023.00.00 00:00</span>
    </div>
    <div id="prd1" class="prd active">
        <ul class="prd-list" id="rank1">
            <!-- content -->
        </ul>
    </div>
    <div id="prd2" class="prd">
        <ul class="prd-list" id="rank2">
            <!-- content -->
        </ul>
    </div>
</section>

📌css

.sc-ranking .group-nav a{ /* 처음엔 텍스트만 있음 */
    padding: 5px 13px; 
    font-size: 13px; 
    color: #222; 
}
.sc-ranking .group-nav a.active{ /* active 클래스 추가되면 보더 테두리 넣기 */
    border: 1px solid #222; 
    border-radius: 100px; 
}
.sc-ranking .prd{ /* prd 영역은 숨겨져 있음 */
    display: none; 
}
.sc-ranking .prd.active{ /* active 클래스 추가되면 prd 영역이 나타남 */ 
    display: block; 
}

📌js

$('.sc-ranking .group-nav a').click(function(e){
    e.preventDefault();
  	// 이벤트의 기본 동작을 중단. 
    // a태그의 기본 동작인 링크 이동을 중단하고, 해당 코드 블록 내의 추가적인 동작을 실행

    target=$(this).data('prd');
  	// 클릭한 요소의 data-prd 속성값을 가져와서 target 변수에 담음

    $(this).addClass('active').siblings().removeClass('active');
  	// 클릭한 요소에 active 클래스 추가, 그 형제는 active 클래스 제거
    $(target).addClass('active').siblings().removeClass('active');
  	// target 변수의 값을 가진 요소에 active 클래스 추가, 그 형제는 active 클래스 제거
});

6. Swiper 반응형

🔷breakpoints

먼저 초기값을 모바일로 둔 뒤, 사이즈를 늘려가면서 조절해야 함.

const mainSlide = new Swiper('.main-slide',{
    slidesPerView: 1, // 브라우저 너비가 767px 이하 / 슬라이드 뷰 개수 1개
    loop:true,
    autoplay: {
            delay: 1700,
            disableOnInteraction: false
    },
    pagination:{
        el:'.fraction',
        type:'fraction'
    },
    breakpoints:{
        767:{ // 브라우저 너비가 767px 초과 / 슬라이드 뷰 개수 1.5개
            slidesPerView: 1.5,
            spaceBetween:3,
            centeredSlides:true, // true일 때 활성 슬라이드가 항상 중앙에 배치 됨
        }
    }
});

🔷브라우저 너비 변화에 따른 Swiper 작동 유무

const bookSlide = new Swiper('.book-slide',{
    loop:true,
    slidesPerView: 1, // 브라우저 너비 767px 이하 / 슬라이드 뷰 개수 1개
    autoplay: {
            delay: 1700,
            disableOnInteraction: false
        },
    pagination:{
        el:'.fraction',
        type:'fraction'
    },
    breakpoints:{
        767:{ // 브라우저 너비가 767px 초과 / 슬라이드 뷰 개수 2개
            slidesPerView: 2,
            spaceBetween:10,
        },
    }
});

$(function(){ // 1) 윈도우 창을 켰을 때 초기 상태
    if(window.innerWidth >= 768) { // 윈도우 창 크기가 768px 이상이면,
        bookSlide.disable(); // bookSlide 비활성화
        bookSlide.autoplay.disable(); // bookSlide 자동재생 비활성화
    }else{ // 768px 미만이면,
        bookSlide.enable(); // bookSlide 활성화
        bookSlide.autoplay.start(); // bookSlide 자동재생 활성화
    }
});
$(window).resize(function(){ // 2) 윈도우 창 크기가 변화할 때
    if(window.innerWidth >= 768){
        bookSlide.disable();
        bookSlide.autoplay.disable();
    }else{
        bookSlide.enable();
        bookSlide.autoplay.start();
    }
});

* 웹 표준 검사 통과

The W3C Markup Validation Service의 Nu Html Checker를 통해 index.html의 마크업이 웹 표준에 적합함을 확인했습니다.

profile
web publisher

0개의 댓글