1-1) h1 - 로고 리스트형 마크업
1-2) 리스트 - float,flex 활용
1-3) select UI
1-4) wai-aria
1-5) 스킵 네비게이션
2-1) 비동기 통신 데이터 바인딩
2-2) swiper Slide event
2-3) header scroll event
2-4) 스킵 네비게이션 event
모바일 적응형 네이버 웹툰 사이트 상단 로고 화면을 보았을때, 아래와 같이 리스트처럼 나열되어 있는 디자인을 확인할 수 있는데,
N 아이콘 모양만 로고라고 생각할 수 있지만, 하나씩 눌러보면,
N 아이콘 -> 네이버 모바일 사이트 이동
웹툰 -> 네이버웹툰 모바일 적응형 사이트 이동
웹소설 -> 네이버웹소설 모바일 적응형 사이트 이동
시리즈 -> 네이버시리즈 모바일 적응형 사이트 이동
각 해당하는 사이트의 로고인 것을 확인할 수 있으며, header의 공통 디자인 또한 유지되는 것을 확인할 수 있다. (물론 각 사이트의 성격에 따라 버튼의 추가 및 삭제는 있음)
그래서 저 4가지 로고를 h1
에 한꺼번에 묶고, 각 해당 사이트로 이동할 수 있게 a
태그로 마크업을 구성하였다.
<h1 class="logo-gnb">
<a href="#" class="link-logo"><span class="blind">네이버</span></a>
<a href="#" class="link-logo active">웹툰</a>
<a href="#" class="link-logo">웹소설</a>
<a href="#" class="link-logo">시리즈</a>
</h1>
웹툰 리스트 항목들은 float와 flex를 함께 활용하여 디자인을 진행했다.
float를 해지할 때는 부모요소에 가상요소 ::after에 clear:both 를 활용하거나, 리스트 자체가 많지 않다면 부모요소에 overflow:hidden 처리를 하여 float를 해지하였다.
css float
.link-webtoon {
display: block;
&::after {
content: '';
display: block;
clear: both;
}
}
.thumbnail {
position: relative;
float: left;
overflow: hidden;
width: 78px;
height: 55px;
-webkit-border-radius: 2px;
border-radius: 2px;
&::after {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 1px solid rgba(0,0,0,.06);
}
img {
position: absolute;
top: 0;
bottom: 0;
left: -10%;
right: -10%;
width: auto;
height: 100%;
margin: auto;
}
}
.info-box {
display: flex;
overflow: hidden;
height: 100%;
}
css-flex
.webtoon-list {
padding: 0 16px;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.webtoon-item {
width: calc((100% - 10px) / 3);
margin-bottom: 15px;
}
.link-webtoon {
display: block;
}
.thumbnail {
position: relative;
overflow: hidden;
padding-top: 129.7297%;
border-radius: 2px;
&::after {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 1px solid rgba(0,0,0,.06);
}
img {
position: absolute;
top: 0;
bottom: 0;
left: -10%;
right: -10%;
width: auto;
height: 100%;
margin: auto;
}
}
thumbnail에 padding으로 컨트롤하여 height를 주면 유동적으로 thumbnail의 크기를 조절할 수 있다.
<div class="select-box">
<select name="list-category" id="list-category1">
<option value="#" selected>인기순</option>
<option value="#">여성 인기순</option>
<option value="#">남성 인기순</option>
<option value="#">조회순</option>
<option value="#">업데이트순</option>
</select>
</div>
.select-box의 가상요소를 추가하여 arrow를 만들었으나 option을 하나하나 클릭했을 때 디자인이 깨지는 현상이 발생함.
<div class="select-tit">
인기순
</div>
<div class="select-box">
<select name="list-category" id="list-category1">
<option value="#" selected>인기순</option>
<option value="#">여성 인기순</option>
<option value="#">남성 인기순</option>
<option value="#">조회순</option>
<option value="#">업데이트순</option>
</select>
</div>
그래서 텍스트만 따로 담아주는 .select-tit를 마크업해준 뒤 가상요소에 arrow를 추가해주면 어떤 텍스트가 들어가도 깨지지 않고 디자인을 유지할 수 있고, .select-box를 투명하게 만든다음 position을 활용하여 .select-tit와 겹치게끔 위치를 조정해주면 눌렀을 때도 select의 option을 사용할 수 있다.
제이쿼리를 통해 option을 클릭했을 때 해당하는 option의 텍스트를 .select-tit에 뿌려주면 선택한 옵션이 어떤것인지도 시각적으로 확인이 가능하다.
j-query
$('#list-category1').on('change',function(){
const optionText = $('#list-category1 option:checked').text();
console.log(optionText);
$('#weekday .select-tit').text(optionText);
});
button처럼 사용되는 a태그나, tab의 목적으로 사용된 list들, aside의 역할, 선택된 상태 등, 태그의 역할, 속성, 상태 정보를 입력해주었다.
대한민국 최대 포털사이트인 '네이버'의 웹툰 사이트인만큼 웹 접근성의 용이를 위하여 스킵 네비게이션을 마크업하였고, 탭을 통하여 엔터를 눌렀을때 각 해당하는 영역으로 이동할 수 있게 구현하였다.
html
<div id="u_skip">
<a href="#home" data-target="home"><span>홈 본문 내용 바로가기</span></a>
<a href="#weekday" data-target="weekday"><span>요일별 본문 내용 바로가기</span></a>
<a href="#end" data-target="end"><span>완결작 본문 내용 바로가기</span></a>
<a href="#bestchallenge" data-target="bestchallenge"><span>베스트도전 본문 내용 바로가기</span></a>
</div>
이번 포트폴리오의 가장 중요한 포인트이자 강조하고싶었던 부분이다.
json파일에 호출하고 싶은 데이터 값을 입력하여 fetch문을 통해 데이터를 호출하고 싶은 클래스에 삽입하였다.
총 4개의 json data를 제작
섹션별 총 4개의 fetch문으로 데이터를 호출
베스트도전의 데이터를 호출
// #bestchallenge
fetch("./asset/data/bestchallenge.json") //
.then((response) => response.json())
.then((json) => {
const bestWebtoon = json.bestWebtoon;
const bestList = json.bestList;
const upEl = `<i class="ic-up"><span class="blind">up</span></i>`
let html = '';
let html2 = '';
bestWebtoon.forEach(el => {
html+=`<li class="best-item">
<a href="${el.url}" class="link-best">
<div class="thumbnail">
<img src="${el.imgSrc}" alt="${el.alt}">
</div>
<div class="info-box">
<strong class="webtoon-tit">${el.title}</strong>
<span class="writer">${el.writer}</span>
</div>
</a>
</li>`
});
bestList.forEach(el => {
const isUp = el.up ? upEl : ""; // .ic-up이 있으면 해당 요소를 뿌려주고. 없으면 빈칸으로!
html2+=`<li class="webtoon-item">
<a href="${el.url}" class="link-webtoon">
<div class="thumbnail">
<img src="${el.imgSrc}" alt="${el.alt}">
</div>
<div class="info-box">
<strong class="webtoon-tit">
${el.title}
<em class="badge">
${isUp}
</em>
</strong>
<span class="writer">${el.writer}</span>
<p class="desc">${el.desc}</p>
<div class="detail">
<span class="score">
<i class="ic-score">
<span class="blind">별점</span>
</i>
${el.score}
</span>
<span class="time"><span class="blind">업데이트 날짜</span>${el.time}</span>
</div>
</div>
</a>
</li>`
});
const list1 = document.querySelector('#bestchallenge .sc-best .best-list');
const list2 = document.querySelector('#bestchallenge .sc-webtoon .webtoon-list');
list1.innerHTML = html;
list2.innerHTML = html2;
});
결과
이러한 방식으로 홈,요일별,완결작,베스트도전 모든 섹션을 데이터를 호출하였다.
swiper slide의 'slideChange'는 자바스크립트이벤트이며 활성화된 슬라이드가 바뀔때마다 호출하게 된다.
realIndex를 통해 루프 모드에서 복제 된 슬라이드를 고려하는 현재 활성 슬라이드의 인덱스 번호를 구할 수 있다.
swiper1.on('slideChange',function(){
if(this.realIndex > 1){
$('.btn-best').addClass('active').siblings().removeClass('active');
}else{
$('.btn-webtoon').addClass('active').siblings().removeClass('active');
}
});
// 활성화된 슬라이드가 바뀔때 마다, 현재 슬라이드의 인덱스가 1보다 크면 '베스트도전'의 탭이 활성화 되고, 1보다 작으면 '웹툰'의 탭이 활성화 됨.
$('.tab-area a').click(function(e){
e.preventDefault();
if ($(this).attr('aria-selected')=="true") {
swiper1.slideTo(0);
$('.btn-webtoon').addClass('active').siblings().removeClass('active');
} else {
swiper1.slideTo(2);
$('.btn-best').addClass('active').siblings().removeClass('active');
}
});
// 탭 '웹툰','베스트도전' 클릭이벤트. 활성화 되어있는 '웹툰' 탭을 클릭하면 슬라이드 인덱스 0번으로 이동하며, '웹툰'이 활성화되고, '베스트도전' 탭을 클릭하면 슬라이드 인덱스 2번으로 이동하며, '배스트도전' 탭이 활성화 된다.
스크롤 시 헤더의 .logo-area부분이 감춰지는 이벤트.
요일별이 활성화 되어있을 때는 .group-lnb까지 감춰지게 구현
let lastScroll = 0;
$(window).scroll(function(){
const curr = $(this).scrollTop();
const here = $('.container').offset().top;
if(curr > lastScroll){
$('.header').addClass('active');
if($('.group-lnb .lnb-item').eq(1).hasClass('active')){
$('.header').addClass('active2');
}
}
if(curr <= here + 100){
$('.header').removeClass('active');
if($('.group-lnb .lnb-item').eq(1).hasClass('active')){
$('.header').removeClass('active2');
}
}
});
결과
스킵 네비게이션을 탭하여 엔터를 눌렀을 때 해당 영역으로 이동하고 각 해당하는 group-lnb 활성화.
만약 요일별 웹툰에 활성화가 되어있으면 .group-weekday탭 추가 (nav event도 동일)
$('#u_skip a').keydown(function(e){
key = e.keyCode;
if(key === 13){
const target = $(this).data('target');
const href = $(this).attr('href');
$('[data-li='+target+']').addClass('active').siblings().removeClass('active');
$(href).addClass('active').siblings().removeClass('active');
if($('.group-lnb .lnb-item').eq(1).hasClass('active')){
$('.group-weekday').addClass('active');
}else{
$('.group-weekday').removeClass('active');
}
}
})