프로젝트👩💻 : ANDAR
사용언어🛠️ : HTML5, CSS3, JAVASCRIPT
라이브러리📁 : jQuery, Swiper
타입⚙️ : 적응형 Mobile
⏱️ : 3일 소요
✅ W3C Markup · CSS3 Pass
📌<h>태그 outline📌
gnb가 열리면 body에 hidden클래스가 붙도록 했습니다.
✨이때 클래스가 붙으면 가상선택자에 dimmed가 형성되게끔 하여 불필요한 코드를 줄였습니다.
body.hidden {
overflow: hidden;
}
/* dimmed */
body.hidden::after {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 55;
}
하지만 이럴 경우, dimmed를 클릭하여 gnb를 닫을 때 조금 까다로울 수 있습니다.
가상선택자는 스크립트로 조절이 어렵기때문에 그 부모인 body를 클릭하여 gnb를 닫아야하는데,
body자체에 클릭이벤트를 주게되면 gnb자체가 열리기도 전에 닫히는 상태가 되어버립니다.
// 잘못된 코드❌
$('body').click(function () {
$('.gnb').removeClass('on');
$('body').removeClass('hidden');
});
✨이같은 문제에선 click
이벤트의 e.target
을 이용하여 해결했습니다.
// 해결 코드⭕
$(document).click(function (e) {
if ($('.wrapper').has(e.target).length === 0) {
$('.gnb').removeClass('on');
$('body').removeClass('hidden');
}
});
gnb가 열린 상태에서 gnb안쪽을 클릭하면 e.target은 gnb 또는 gnb내부 요소
지만,
dimmed를 클릭하면 e.target은 body
가 됩니다.
해당 조건을 사용하여 wrapper 안에 body가 존재하지 않는다면 gnb가 닫히도록 구현했습니다.
안다르 클론코딩을 하면서 총 6개의 영역에 swiper를 사용하였습니다.
✨최상단 검정배너와 메인 비주얼 슬라이드에는 swiper effect
인 fade
를 넣었습니다.
// 최상단 배너
const MainTopSwiper = new Swiper('.top-banner', {
effect: 'fade', 👈
loop: true,
autoplay: {
delay: 4000,
disableOnInteraction: false,
},
});
// 메인 비주얼 슬라이드
const MainVisual = new Swiper('.sc-visual .swiper', {
effect: 'fade', 👈
loop: true,
pagination: {
el: '.pagination',
},
autoplay: {
delay: 4000,
disableOnInteraction: false,
},
});
✨마지막 swiper인 레깅스가 포함된 section.sc-cate
에는 동일한 구조의 swiper 3개가 있기때문에,
클래스 group-cate
로 동일한 스타일을 주고 swiper도 동일하게 적용했습니다.
동일한 클래스에 동일한 swiper 구조이기때문에 반복문없이 사용 가능했습니다.
<section class="sc-cate">
<div class="group-cate leggings">
<!-- 동일한 swiper 구조 -->
</div>
<div class="group-cate bratop">
<!-- 동일한 swiper 구조 -->
</div>
<div class="group-cate set">
<!-- 동일한 swiper 구조 -->
</div>
</section>
// 카테고리별 상품 슬라이드
const cateSwiper = new Swiper('.sc-cate .swiper', {
slidesPerView: 2.1,
spaceBetween: 10,
freeMode: true,
});
section.sc-best
는 탭 영역입니다.
✨먼저 role
속성으로 탭역할을 알려줘 접근성을 올려주었습니다.
이후 코드를 좀더 간결하고 쉽게 사용하기위해 dataset
을 사용했습니다.
<section class="sc-best">
//탭 메뉴
<ul class="tab-list" role="tablist">
<li class="tab-item" data-tab=".tabcon1" role="tab">메뉴1</li>
<li class="tab-item" data-tab=".tabcon2" role="tab">메뉴2</li>
</ul>
//탭 내용(패널)
<ul class="tabcon-list">
<li class="tabcon-item tabcon1" role="tabpanel">내용1</li>
<li class="tabcon-item tabcon2" role="tabpanel">내용2</li>
</ul>
</section>
✨탭메뉴에 data-tab
으로 패널의 클래스네임과 동일한 값을 넣어주어
클릭한 메뉴의 data값을 불러와 변수로 담고, 그 변수를 선택자로 사용하였습니다.
$('.sc-best .tab-item').click(function (e) {
e.preventDefault();
tabName = $(this).data('tab'); 👈
$(this).addClass('on').siblings().removeClass('on');
$(tabName).addClass('on').siblings().removeClass('on'); 👈
});
안다르 클론코딩을 하면서 Counter
라는 css 기능을 알게되어 활용해보았습니다.
Counter란?
문서 내에서 특정 요소의 순서를 추적하고 그 값을 스타일에 적용하는 CSS 기능으로,
counter-reset 및 counter-increment 속성을 사용하여 카운터를 초기화하고 증가시킬 수 있다.
이를 활용하여 목록 아이템에 번호를 자동으로 할당하거나 특정 요소에 고유한 식별자를 부여할 수 있다.
🍏모든 브라우저에서 사용 가능
🔗Counter
안다르 콘텐츠 중 BEST 영역에는 제품마다 번호가 쓰여져 있습니다.
before로 표현해도 되겠다싶어 처음에는 각 li마다 하나씩 입력해주는 방식으로 코드를 짰습니다.
ul > li:nth-child(1)::before {
content: '1';
}
ul > li:nth-child(2)::before {
content: '2';
}
ul > li:nth-child(3)::before {
content: '3';
}
ul > li:nth-child(4)::before {
content: '4';
}
/* --- 이하 생략 */
하지만 위처럼 코드가 너무 길어지고, 데이터 변환시에는 list의 순서도 바뀌는 문제가 있다고 판단해 좀더 효율적인 방법을 찾아 해결했습니다.
✨Counter 사용으로 li 순서에 따라 번호가 자동으로 할당되게 했습니다.
또한 li의 데이터가 사라지거나 위치가 변경되어도 그에 따라 번호가 다시 순서대로 재할당됩니다.
ul {
/* 초기 카운터 값 설정(기본 0) */
counter-reset: name;
}
ul > li::before {
/* 카운터 값 증가 */
counter-increment: name;
/* 번호 표시 */
content: counter(name);
}
안다르 클론코딩을 하면서 비슷한 구조와 스타일이 많이 중복되는 것을 알 수 있었습니다.
특히, 제품 list의 구성은 이미지와 텍스트로 이루어져있어 공통 스타일로 묶기 용이했습니다.
✨.img-box
와 .txt-box
로 공통 클래스를 구분하여 작업해 코드를 줄이고 제작 시간을 단축시켰습니다.
/* ---상품 정보 공통ul */
.img-box {
position: relative;
}
.img-box img {
width: 100%;
}
.img-box .ico {
position: absolute;
top: 0;
right: 0;
width: 16%;
min-width: 38px;
}
.txt-box {
display: flex;
flex-direction: column;
align-items: start;
gap: 10px;
padding: 15px 5% 0;
}
.txt-box .review {
font-size: 13px;
font-weight: 600;
line-height: 1;
color: #8e1f29;
}
.txt-box .title {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
text-overflow: ellipsis;
overflow: hidden;
font-size: 14px;
line-height: 17px;
letter-spacing: -0.05em;
}
/* 이하 생략 */
/* ---//상품 정보 공통ul */
다른 콘텐츠와 마찬가지로 영상이 포함된 콘텐츠도 클릭시 링크이동이 되어야 합니다.
✨iframe
을 a
태그로 감싸는 형태는 웹표준에 적합한 코드가 아니기때문에 해당 부분만 따로 수정해주었습니다.
//수정 전 [❌]
<section class="group-recomm">
<a href="#">
<div class="img-box">
<iframe src="영상링크"></iframe>
</div>
</a>
</section>
//수정 후 [⭕]
<section class="group-recomm">
<div class="relative">
<a href="#" class="link-cover"></a>
<div class="img-box">
<iframe src="영상링크"></iframe>
</div>
</div>
</section>
.link-cover {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
💡
a
태그 내부에iframe
태그를 직접 포함하는 것은 일반적으로 허용되지 않습니다.
이러한 제한은 보안 및 웹페이지 구조의 논리성과 일관성을 유지하기 위한 것입니다. 만약 하이퍼링크를 통해 외부 페이지를 로딩하고 동시에 이 페이지 안에 다른 외부 페이지를iframe
으로 불러올 수 있다면, 사용자 경험이 혼란스러워질 수 있고, 보안상의 이슈도 발생할 수 있습니다.