[클론코딩] 로톡

J.yeon·2024년 1월 20일
post-thumbnail

프로젝트👩‍💻 : 로톡
사용언어🛠️ : HTML5, SCSS, JAVASCRIPT
라이브러리📁 : Swiper, jQuery
타입⚙️ : 반응형
⏱️ : 3일 소요


✅ W3C Markup · CSS3 Pass

데이터 비동기 처리(AJAX)를 활용한 로톡 클론코딩 사이트입니다.
max-width: 959px 기준, 세미 반응형으로 제작했습니다.

✨data처리: section 총 6개 영역, $.get() & fetch문 사용


📌<h>태그 outline📌


✨KEY POINT

  • 검색창 cancel버튼 제어
  • data 불러오기 {더 보기 contents}
  • data 불러오기 {tab contents}
  • Font Awesome icon



검색창 cancel버튼 제어

검색창에 검색어를 입력하면 cancel버튼이 생성되고, 검색어를 지우면 사라집니다.
입력 중에 cancel버튼을 눌러도 입력값과 버튼이 사라져야 합니다.

<form method="get" class="search-form">
  <fieldset>
    <legend class="blind">검색</legend>
    <input type="text" class="input-search" placeholder="어떤 문제가 있으신가요?" />
    <button type="button" class="btn-cancel">
      <span class="blind">검색입력취소</span>
    </button>
  </fieldset>
</form>

input에 입력이 감지되면 .btn-cancel에 클래스 show가 붙도록 코드를 작성했습니다.

change() 이벤트는 값이 입력된 후 input을 벗어나야 감지됨으로,
입력하는 순간마다 변화를 감지하게끔 keyup() 이벤트를 사용했습니다.

// 검색창 input close btn
$('.header-top .input-search').keyup(function () {
    if ($(this).val() != 0) { 👈input값이 비어있지 않다면,
        $('.header-top .btn-cancel').addClass('show');
    } else {
        $('.header-top .btn-cancel').removeClass('show');
    }
});
$('.header-top .btn-cancel').click(function () { 
    $('.header-top .input-search').val(''); 👈input값 비우기
    $('.header-top .btn-cancel').removeClass('show');
});



📌AJAX를 사용하여 JSON파일의 데이터를 불러옵니다.
🔗about AJAX & JSON


data 불러오기 {더 보기 contents}

더 보기 버튼을 클릭하면 글 list가 4개씩 변경되고, 현재페이지가 그에 맞게 변경됩니다.

ul.con-list 안에 li를 불러와야 합니다.

<!-- 최신 상담글 -->
<section class="sc-latest">
  <div class="inner">
    <div class="head--sc">
      <a href="">
        <h2 class="headline">최신 상담글</h2>
        <svg></svg>
      </a>
    </div>
    <ul class="con-list">
      <!-- JSON 파일 불러오기 -->
    </ul>
    <div class="more-area">
      <button class="btn-more">
        <svg></svg>
        <span class="txt">최신 상담글 더 보기</span>
        <div class="fraction">
          <span class="blind">현재페이지</span>
          <em class="latest-page">1</em>/
          <span class="blind">전체페이지</span>4
        </div>
      </button>
    </div>
  </div>
</section>
<!-- //최신 상담글 -->

우선 데이터 json을 작성합니다.

보통 객체 > 배열 > 객체 로 구조를 잡지만,
해당 영역에서만 key없이 배열 > 배열 > 객체 구조로 코드를 짜보았습니다.

틀린 답은 아니나 데이터를 불러오는데 다소 번잡(?)할 수 있을 것 같습니다🤔✍️

배열 안에 4개의 배열이 존재하고 각 배열당 4개의 객체가 존재합니다.
더보기 버튼을 클릭할 때마다 배열index순서대로 4개의 객체 데이터가 전달됩니다.

[
    [
        {
            "subtitle": "임대차",
            "title": "지급명령 소요 기간에 대한 정보",
            "desc": "이전에도 전셉증금 미반환 문제때문에 상담글을 올린적이 있었습니다. 지급명령과 반환소송 둘 중 고민하다가 반환소송은 비용적인 문제도 있고 소요기간이 길다는 점이 있어 고민 끝에 먼저...",
            "img": ["./assets/images/lawyer01.webp", "./assets/images/lawyer02.webp", "./assets/images/lawyer03.webp", "./assets/images/lawyer04.webp"],
            "linkUrl": ""
        },
        {
            "subtitle": "임대차",
            "title": "지급명령 소요 기간에 대한 정보",
            "desc": "이전에도 전셉증금 미반환 문제때문에 상담글을 올린적이 있었습니다. 지급명령과 반환소송 둘 중 고민하다가 반환소송은 비용적인 문제도 있고 소요기간이 길다는 점이 있어 고민 끝에 먼저...",
            "img": ["./assets/images/lawyer01.webp", "./assets/images/lawyer02.webp", "./assets/images/lawyer03.webp", "./assets/images/lawyer04.webp"],
            "linkUrl": ""
        },
        {
            "subtitle": "임대차",
            "title": "지급명령 소요 기간에 대한 정보",
            "desc": "이전에도 전셉증금 미반환 문제때문에 상담글을 올린적이 있었습니다. 지급명령과 반환소송 둘 중 고민하다가 반환소송은 비용적인 문제도 있고 소요기간이 길다는 점이 있어 고민 끝에 먼저...",
            "img": ["./assets/images/lawyer01.webp", "./assets/images/lawyer02.webp", "./assets/images/lawyer03.webp", "./assets/images/lawyer04.webp"],
            "linkUrl": ""
        },
        {
            "subtitle": "임대차",
            "title": "지급명령 소요 기간에 대한 정보",
            "desc": "이전에도 전셉증금 미반환 문제때문에 상담글을 올린적이 있었습니다. 지급명령과 반환소송 둘 중 고민하다가 반환소송은 비용적인 문제도 있고 소요기간이 길다는 점이 있어 고민 끝에 먼저...",
            "img": ["./assets/images/lawyer01.webp", "./assets/images/lawyer02.webp", "./assets/images/lawyer03.webp", "./assets/images/lawyer04.webp"],
            "linkUrl": ""
        }
    ],
    [
        {
            "subtitle": "손해배상",
            "title": "회사에서 손해배상으로 고소를 받을 경우, 어떻게 대응해야 할까요?",
            "desc": "22.12.28년 입사, 퇴사는 23.12.31입니다. 입사 후 기본적인 마무리안내, 셀렉,출고 등 간단한 일을 3개월 수습기간동안 배우면서 익혔습니다. 익힌 후 4개월이 될때 사수...",
            "img": ["./assets/images/lawyer01.webp", "./assets/images/lawyer02.webp"],
            "linkUrl": ""
        },
        {
            "subtitle": "손해배상",
            "title": "회사에서 손해배상으로 고소를 받을 경우, 어떻게 대응해야 할까요?",
            "desc": "22.12.28년 입사, 퇴사는 23.12.31입니다. 입사 후 기본적인 마무리안내, 셀렉,출고 등 간단한 일을 3개월 수습기간동안 배우면서 익혔습니다. 익힌 후 4개월이 될때 사수...",
            "img": ["./assets/images/lawyer01.webp", "./assets/images/lawyer02.webp"],
            "linkUrl": ""
        },
        {
            "subtitle": "손해배상",
            "title": "회사에서 손해배상으로 고소를 받을 경우, 어떻게 대응해야 할까요?",
            "desc": "22.12.28년 입사, 퇴사는 23.12.31입니다. 입사 후 기본적인 마무리안내, 셀렉,출고 등 간단한 일을 3개월 수습기간동안 배우면서 익혔습니다. 익힌 후 4개월이 될때 사수...",
            "img": ["./assets/images/lawyer01.webp", "./assets/images/lawyer02.webp"],
            "linkUrl": ""
        },
        {
            "subtitle": "손해배상",
            "title": "회사에서 손해배상으로 고소를 받을 경우, 어떻게 대응해야 할까요?",
            "desc": "22.12.28년 입사, 퇴사는 23.12.31입니다. 입사 후 기본적인 마무리안내, 셀렉,출고 등 간단한 일을 3개월 수습기간동안 배우면서 익혔습니다. 익힌 후 4개월이 될때 사수...",
            "img": ["./assets/images/lawyer01.webp", "./assets/images/lawyer02.webp"],
            "linkUrl": ""
        }
    ],
  
  ---이하 생략---
  각 객체는 필요 데이터로 수정할 수 있다. (시간절약을 위해 동일한 게시글 사용함)

제이쿼리$.get() 을 사용하여 작성한 json을 경로로 불러옵니다.
.done() 은 데이터를 가져오는데 성공할시, .fail()실패시 실행됩니다.

배열 > 배열 > 객체 구조이기때문에 데이터를 불러올 때 data[] 배열식으로 불러와야 합니다. 배열 안에 있는 객체의 데이터가 필요함으로 반복문을 돌려줍니다.

이때, 객체 안의 img 또한 배열로 되어있어 따로 또 반복문을 돌려줘야 합니다.

👇상세설명👇

// sc-latest JSON
$.get('../../assets/json/latest.json')
    .done(function (data) {
        function latestData(arrayNum) {
            let itemTag = ''; 👈li 데이터 담을 변수(추가 할당을 위해 전역변수로 빼둡니다.)
          
            data[arrayNum].forEach((a, i) => { 👈배열 반복문 (data[n]의 각 객체 => a)
                let imgList = ''; 👈 이미지 수만큼 태그 생성하여 담아둘 변수
                a.img.forEach(el => { 👈data[n]의 각 객체 중 key값이 img인 데이터
                    imgList += `<img src="${el}" alt />`;
                });

                👇data[n]의 각 객체 값을 넣은 li를 객체수만큼 생성하여 변수에 추가합니다.
                객체값을 가져오려면 data[n]의 각 객체 뒤에 .keyName을 붙이면 됩니다.
                
                itemTag += `<li class="con-item"> 
                                    <a href="${a.linkUrl}" class="link-cover"></a>
                                    <div class="con-head">
                                        <span class="subtitle">${a.subtitle}</span>
                                        <strong class="title">${a.title}</strong>
                                    </div>
                                    <p class="con-desc">${a.desc}</p>
                                    <div class="con-foot">
                                        <div class="prof-img">
                                            ${imgList}
                                        </div>
                                        <div class="ans-num">변호사 답변 <em>${a.img.length}</em>개</div>
                                    </div>
                                </li>`;
                // console.log(itemTag);
            });
            $('.sc-latest .con-list').html(itemTag); 👈추가된 모든 li를 ul에 넣습니다.
        }
        latestData(0); 
  		👆함수에 담아 첫 번째 객체4개를 불러옵니다. (로드시 생성되어있어야할 콘텐츠)

        let clickCount = 0;
        $('.sc-latest .btn-more').click(function () {
            clickCount++;
            page = clickCount % 4; 
            👆0, 1, 2, 3값이 반복되게끔 % 연산자를 사용했습니다.
             % 연산자는 나머지값을 반환합니다.
            
            $('.sc-latest .con-list').html(''); 👈클릭할때마다 비워준 뒤,
            $('.sc-latest .btn-more .latest-page').text(page + 1); 👈현재 페이지 표시
            latestData(page); 
          	👆나머지값 넣어주면 1,2,3,0,1,2,3... 순으로 반복되며 함수가 실행됩니다.
        });
    })
    .fail(function () {
        console.log('데이터 전달 오류');
    });



data 불러오기 {tab contents}

tab을 클릭하면 각 tab에 맞는 콘텐츠가 활성화됩니다.

dataset을 활용했습니다.

마찬가지로 ul.tab-con-list 안에 li를 불러와야 합니다.

<!-- 빠른 상담 -->
<section class="sc-quick">
  <div class="inner">
    <div class="addtxt-area">
      <span>AD</span>
      <p class="addtxt">분야별 변호사 광고 영역입니다.</p>
    </div>
    <div class="head--sc">
      <a href="">
        <h2 class="headline">빠른 상담 가능한 변호사</h2>
      </a>
    </div>
    <div class="content">
      <div class="tab-area--btn">
        <ul class="tab-list" role="tablist">
          <li class="tab-item on" role="tab" data-category="성범죄"><button type="button">성범죄</button></li>
          <li class="tab-item" role="tab" data-category="재산범죄"><button type="button">재산범죄</button></li>
          <li class="tab-item" role="tab" data-category="가족"><button type="button">가족</button></li>
          <li class="tab-item" role="tab" data-category="형사범죄"><button type="button">기타 형사범죄</button></li>
          <li class="tab-item" role="tab" data-category="금전계약"><button type="button">금전/계약 문제</button></li>
          <li class="tab-item" role="tab" data-category="부동산"><button type="button">부동산/임대차</button></li>
          <li class="tab-item" role="tab" data-category="폭행협박"><button type="button">폭행/협박</button></li>
          <li class="tab-item" role="tab" data-category="명예훼손"><button type="button">명예훼손/모욕</button></li>
          <li class="tab-item" role="tab" data-category="교통사고"><button type="button">교통사고/범죄</button></li>
          <li class="tab-item" role="tab" data-category="형사절차"><button type="button">형사절차</button></li>
          <li class="tab-item" role="tab" data-category="회사"><button type="button">회사</button></li>
          <li class="tab-item" role="tab" data-category="IT"><button type="button">IT/지식재산/금융</button></li>
          <li class="tab-item" role="tab" data-category="세금"><button type="button">의료/세금/행정</button></li>
          <li class="tab-item" role="tab" data-category="민사절차"><button type="button">민사절차</button></li>
          <li class="tab-item" role="tab" data-category="민사문제"><button type="button">기타 민사문제</button></li>
        </ul>
        <div class="btn-area">
          <button type="button" class="btn-prev">
            <span class="blind">탭메뉴왼쪽끝으로</span>
            <svg></svg>
          </button>
          <button type="button" class="btn-next show">
            <span class="blind">탭메뉴오른쪽끝으로</span>
            <svg></svg>
          </button>
        </div>
      </div>
      <div class="tab-area--con">
        <ul class="tab-con-list">
          <!-- JSON -->
        </ul>
      </div>
    </div>
  </div>
</section>
<!-- //빠른 상담 -->

객체배열을 사용하여 json파일을 작성합니다.
이번엔 일반적인 객체 > 배열 > 객체 구조로 작성했습니다.

객체마다 keyName이 존재하고 그 값으로는 객체가 배열로 되어있는 구조입니다.

{
    "성범죄": [
        {
            "img": "./assets/images/sc-quick_1-01.webp",
            "name": "하선호 변호사",
            "before": "법률사무소 도약",
            "type": ["성폭력/강제추행 등", "미성년 대상 성범죄", "디지털 성범죄"],
            "time": ["오후 9:00", "오후 9:30", "오후 10:00", "오후 10:30"],
            "linkUrl": ""
        },
        {
            "img": "./assets/images/sc-quick_1-01.webp",
            "name": "하선호 변호사",
            "before": "법률사무소 도약",
            "type": ["성폭력/강제추행 등", "미성년 대상 성범죄", "디지털 성범죄"],
            "time": ["오후 9:00", "오후 9:30", "오후 10:00", "오후 10:30"],
            "linkUrl": ""
        },
        {
            "img": "./assets/images/sc-quick_1-01.webp",
            "name": "하선호 변호사",
            "before": "법률사무소 도약",
            "type": ["성폭력/강제추행 등", "미성년 대상 성범죄", "디지털 성범죄"],
            "time": ["오후 9:00", "오후 9:30", "오후 10:00", "오후 10:30"],
            "linkUrl": ""
        },
        {
            "img": "./assets/images/sc-quick_1-01.webp",
            "name": "하선호 변호사",
            "before": "법률사무소 도약",
            "type": ["성폭력/강제추행 등", "미성년 대상 성범죄", "디지털 성범죄"],
            "time": ["오후 9:00", "오후 9:30", "오후 10:00", "오후 10:30"],
            "linkUrl": ""
        }
    ],
    "재산범죄": [
        {
            "img": "./assets/images/sc-quick_1-02.webp",
            "name": "손우석 변호사",
            "before": "법무법인 대환",
            "type": ["사기/공갈"],
            "time": ["오후 9:00", "오후 9:30", "오후 10:00", "오후 10:30"],
            "linkUrl": ""
        },
        {
            "img": "./assets/images/sc-quick_1-02.webp",
            "name": "손우석 변호사",
            "before": "법무법인 대환",
            "type": ["사기/공갈"],
            "time": ["오후 9:00", "오후 9:30", "오후 10:00", "오후 10:30"],
            "linkUrl": ""
        },
        {
            "img": "./assets/images/sc-quick_1-02.webp",
            "name": "손우석 변호사",
            "before": "법무법인 대환",
            "type": ["사기/공갈"],
            "time": ["오후 9:00", "오후 9:30", "오후 10:00", "오후 10:30"],
            "linkUrl": ""
        },
        {
            "img": "./assets/images/sc-quick_1-02.webp",
            "name": "손우석 변호사",
            "before": "법무법인 대환",
            "type": ["사기/공갈"],
            "time": ["오후 9:00", "오후 9:30", "오후 10:00", "오후 10:30"],
            "linkUrl": ""
        }
    ],
  
    ---이하 생략---
  각 객체는 필요 데이터로 수정할 수 있다. (시간절약을 위해 동일한 게시글 사용함)

자바스크립트fetch문 을 사용하여 작성한 json을 불러옵니다.

⚠️데이터자료가 도착하면 자동으로 array/object 자료로 바꿔주는 제이쿼리와 달리 자바스크립트는 따로 json 데이터 형식 변환이 필요합니다. .then(res => res.json())

✨각 객체의 keyNameli.tab이 가진 data-category 값동일한 객체의 데이터를 사용해야함으로 함수로 만들어 매개변수를 빼둡니다.

매개변수에는 li.tab이 가진 data-category 값 == keyName 이 들어가게 됩니다.


👇상세설명👇

// sc-quick JSON
function quickContent(category) {
    fetch('../../assets/json/quick.json')
        .then(res => res.json())
        .then(json => {
            data = json[category]; 👈모든 데이터 중 [객체의 keyName] 자료

            let content = ``; 👈li 담을 변수 (전역변수로 빼둡니다.)
            data.forEach(el => {
                let allType = ``; 👈배열 반복문값 담을 변수
                let allTime = ``; 
                el.type.forEach(type => {
                    allType += `<span>${type}</span>`; 👈반복만큼 추가 할당
                });
                el.time.forEach(time => {
                    allTime += `<div class="time">${time}</div>`;
                });

                content += `<li class="tab-con-item" role="tabpanel">
                            <a href="${el.linkUrl}" class="link-cover"></a>
                            <div class="img-box">
                                <img src="${el.img}" alt="${el.name} 이미지" />
                            </div>
                            <div class="info-box">
                                <strong class="name">
                                    ${el.name}
                                    <span>${el.before}</span>
                                </strong>
                                <div class="info-field">
                                    ${allType} 👈
                                </div>
                                <div class="info-time">
                                    ${allTime} 👈
                                </div>
                            </div>
                        </li>`;
            });
      
            $('.sc-quick .tab-area--con .tab-con-list').html(content); 👈li 넣기
        });
}

👇첫 카테고리는 로드시에도 content가 나와있어야합니다.
const firstCateVal = $('.sc-quick .tab-area--btn .tab-item').eq(0).data('category');
quickContent(firstCateVal);

👇tab클릭시 클릭된 자신의 *data값을 가져와 함수(li데이터생성)*인자로 넣어줍니다.
이렇게되면 어떤 tab을 클릭하던 자신의 콘텐츠가 나타나게 됩니다.
$('.sc-quick .tab-area--btn .tab-item').click(function () {
    const list = $(this).data('category');
    quickContent(list);
});



Font Awesome icon

Font Awesome icon의 HTML코드 & 유니코드를 활용했습니다.

유니코드는 로톡 클론코딩을 하면서 처음 접해본 방식입니다🤔


unicode 사용법은 간단합니다👇

1) Font Awesome cdn을 가져와 html에 붙입니다.
(버전은 4, 5 필요한 버전을 사용하면 됩니다. 저는 5버전을 사용했습니다.)

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.8.2/css/all.min.css" />

2) Font Awesome icon에서 원하는 icon을 찾아 상단 오른쪽에 있는 유니코드를 복사합니다.


3) 가상선택자content에 복사한 유니코드를 붙입니다.
이때, font-weight, font-family 속성을 붙여줘야만 유니코드를 사용할 수 있습니다.

👉 font-weight
유니코드는 각각 Solid, Regular, Light, Duotone, Thin 타입이 제공되고 있습니다.

저는 무료로 제공되고 있는 Solid를 사용했습니다. (나머지는 유료)

solidfont-weight: 600 입니다.

👉 font-family
사용하는 버전마다 쓰이는 font-family가 다르니 유의하셔야 합니다.
저는 5버전이기때문에 Font Awesome 5 Free 를 사용했습니다.

<div class="addtxt-area">
  <span>AD</span>
  <p class="addtxt">분야별 변호사 광고 영역입니다.</p>
</div>
.addtxt-area {
    position: absolute;
    right: 0;
    top: 9px;

    font-size: 11px;
    font-weight: 500;
    color: #c2c2c2;

    span {
        &::after {
            content: '\f05a';
            display: inline-block;
            margin-left: 2px;
            font-family: 'Font Awesome 5 Free';
            font-size: 10px;
            font-weight: 600;
        }
    }

HTML code를 가져와 사용할 수도 있습니다.👇

1) 마찬가지로 cdn을 불러오기까지는 동일합니다.

2) HTML code 를 복사해 html에 붙여줍니다.

⚠️이때 주의할 것이 있습니다.

그냥 코드만 복사해서 붙이면 아이콘이 나타나지 않는 문제가 생깁니다.

👉버전5 이상부터는 다소 사용법이 복잡해져 추가적인 코드가 필요하다고 합니다.

스타일이 5가지로 분리되어 일부를 제외한 나머지는 유료로 제공되며,
그 중 무료로 사용되는 스타일 타입은 Solid, Brands 2가지 입니다.

✨각각 스타일별 class 추가 코드
Solid : fas, Brands : fab

저는 Brands 스타일을 사용했기때문에 fab 클래스를 추가로 붙여주니 잘 나타났습니다.

<li class="sns-item ytb">
  <a href="" title="새창열림">
    <i class="fab fa-brands fa-youtube"></i> 👈
    <span class="blind">유튜브 이동</span>
  </a>
</li>
profile
어느덧 3년차 퍼블... 왜 벌써 2026년이냐

0개의 댓글