프로젝트👩💻 : 로톡
사용언어🛠️ : HTML5, SCSS, JAVASCRIPT
라이브러리📁 : Swiper, jQuery
타입⚙️ : 반응형
⏱️ : 3일 소요
✅ W3C Markup · CSS3 Pass
데이터 비동기 처리(AJAX)를 활용한 로톡 클론코딩 사이트입니다.
max-width: 959px 기준, 세미 반응형으로 제작했습니다.
✨data처리: section 총 6개 영역, $.get() & fetch문 사용
📌<h>태그 outline📌
검색창에 검색어를 입력하면 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
더 보기 버튼을 클릭하면 글 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('데이터 전달 오류');
});
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())
✨각 객체의 keyName과 li.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의 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를 사용했습니다. (나머지는 유료)
solid는 font-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>