랜딩 페이지 코드리뷰
간단한 랜딩페이지 제작에 이어 반응형까지 구현해보았다
전체코드
<!DOCTYPE html>
<html lang="ko-KR">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HODU</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="l-wrapper">
<div class="header-bg">
<header class="header">
<div class="top-header">
<h1 class="header-logo">
<span class="a11y-hidden">HODU 로고</span>
<a href="#none">
<img src="./images/logo.svg" alt="">
</a>
</h1>
<nav class="header-nav">
<ul>
<li>
<a href="#none">Home</a>
</li>
<li>
<a href="#none">About</a>
</li>
<li>
<a href="#none">Support</a>
</li>
<li>
<a class="header-btn" href="#none">
Download
</a>
</li>
</ul>
</nav>
<img class="toogle-btn" src="./images/toggle.svg" alt="">
</div>
<div class="about">
<div class="about-explain">
<h2 class="about-headings">
Lorem Ipsum is simply<br>
dummy text of the printing and
</h2>
<p class="about-contxt">Lorem Ipsum is simply dummy text of the printing and<br>
typesetting industry.<br>
Lorem Ipsum has been the industry's standard dummy text<br>
ever since the 1500s, when an unknown</p>
<button class="about-btn">Download</button>
</div>
<img class="about-img" src="./images/cat.png" alt="박스를 머리에 뒤집어쓴 고양이 이미지">
</div>
</header>
</div>
<main class="main">
<section class="section-body">
<figure class="body-intro">
<img class="intro-img" src="./images/img_4.png" alt="옆으로 눕고 고개를 옆으로 슥하고 덩그러니 무엇인가를 바라보는 고양이 이미지">
<figcaption>
<h3 class="intro-headings">
Lorem Ipsum is simply<br>
dummy text of the printing<br>
and dummy text</h3>
<p class="intro-contxt">
Lorem Ipsum is simply dummy text of the printing and typesetting<br> industry.
</p>
</figcaption>
</figure>
<div class="body-info">
<h3 class="info-headings">
dummy text of the printing and dummy
</h3>
<p class="info-contxt">Lorem Ipsum is simply <strong>dummy text</strong> of the printing and typesetting
industry.<br>
Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown</p>
</div>
<div class="body-gallery">
<ul class="gallery-list">
<li>
<button>
<img src="./images/img_1.png" alt="캣타워에서 얼굴만 내밀고 무언가를 지켜보는 고양이 이미지">
</button>
</li>
<li>
<button>
<img src="./images/img_2.png" alt="캣타워에서 얼굴만 내밀고 무언가를 지켜보는 고양이 이미지">
</button>
</li>
<li>
<button>
<img src="./images/img_3.png" alt="나와 눈을 마주치고있는 고양이 이미지">
</button>
</li>
</ul>
<div class="gallery-caption">
<p class="caption-contxt">Lorem Ipsum is simply dummy text of the printing and typesetting industry.<br>
Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown<br>
Ipsum is simply dummy text of the printing and typesetting industry. </p>
<button class="caption-btn">Learn More</button>
</div>
</div>
</section>
<article class="banner-subscribe">
<div class="subscribe-title">
<h4 class="subscribe-headings">
Subscribe to our Blog post
</h4>
<p class="subscribe-contxt">
Lorem Ipsum is simply dummy text of the printing and typesetting industry.<br>
Lorem Ipsum has been the industry's standard dummy text
</p>
</div>
<form class="subscribe-form">
<div class="subscribe-inpt">
<img src="./images/msg.svg" alt="">
<label for="email" class="a11y-hidden"></label>
<input id="email" type="email" placeholder="Enter your e-mail address">
</div>
<button type="submit" class="subscribe-btn">Subscribe</button>
</form>
</article>
</main>
<footer class="footer">
<img src="./images/img_5.png" alt="" class="footer-img">
<div class="section-share">
<h2 class="footer-logo">
<span class="a11y-hidden">HODU 로고</span>
<a href="#none">
<img src="./images/logo_footer.svg" alt="HODU 로고 이미지">
</a>
</h2>
<ul class="share-link">
<li>
<a href="#none">
<img src="./images/sns1.svg" alt="bing 로고 이미지">
</a>
</li>
<li>
<a href="#none">
<img src="./images/sns2.svg" alt="instagram 로고 이미지">
</a>
</li>
<li>
<a href="#none">
<img src="./images/sns3.svg" alt="facebook 로고 이미지">
</a>
</li>
<li>
<a href="#none">
<img src="./images/sns4.svg" alt="youtube 로고 이미지">
</a>
</li>
</ul>
<nav class="footer-nav">
<ul>
<li><a href="#none">About</a></li>
<li><a href="#none">Blog</a></li>
<li><a href="#none">Support</a></li>
<li><a href="#none">Terms of Use</a></li>
</ul>
</nav>
</div>
</footer>
</div>
</body>
</html>
body,
p,
ul,
figure,
button {
margin: initial;
padding: initial;
}
ul {
list-style-type: none;
}
a {
text-decoration: none;
color: inherit;
}
h1,
h2,
h3,
h4 {
font-size: inherit;
font-weight: initial;
margin: initial;
}
:root {
font-family: 'Spoqa Han Sans Neo';
font-weight: 400;
--font-size-lg: clamp(24px, 3.5vw, 48px);
--font-size-md: clamp(24px, 2.5vw, 36px);
--font-size-sm: clamp(14px, 2vw, 16px);
}
img {
/* max-width: 100%; */
height: auto;
vertical-align: top;
}
.a11y-hidden {
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%);
width: 1px;
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
}
body {
background: #fff;
}
.l-wrapper {
margin: 0 auto;
width: 1920px;
}
.header-bg {
background-color: #f2e9d8;
}
.header {
position: relative;
width: 66%;
margin: 0 auto;
padding-top: 31px;
padding-bottom: 262px;
}
.top-header {
margin: auto;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 231px;
}
.header-logo a img {
width: clamp(92px, 18vw, 130px);
}
.nav {
display: flex;
align-items: center;
justify-content: space-between;
}
.header-nav ul {
display: flex;
align-items: center;
list-style-type: none;
font-size: var(--font-size-sm);
}
.header-nav ul li + li {
margin-left: 30px;
}
.header-btn {
padding: 15px 30px;
border: none;
background: #d97652;
font-size: var(--font-size-sm);
font-weight: 700;
color: #fff;
border-radius: 40px;
}
.toogle-btn {
display: none;
}
.about-headings {
font-weight: 700;
font-size: var(--font-size-lg);
}
.about-contxt {
padding: 40px 0;
font-size: var(--font-size-sm);
}
.about-btn {
border: none;
background: #d97652;
padding: 15px 30px;
border-radius: 40px;
font-weight: 700;
font-size: var(--font-size-sm);
color: #fff;
}
.about-img {
position: absolute;
width: clamp(197px, 50%, 415px);
margin-bottom: -120px;
bottom: 0;
right: 0;
}
/* main */
.main {
width: 66%;
margin: auto;
}
.section-body {
padding: 80px 0 90px 0;
}
.intro-img {
box-shadow: 10px 10px 30px rgba(0, 0, 0, 0.25);
border-radius: 30px;
width: clamp(326px, 50%, 660px);
}
.body-intro {
display: flex;
gap: 110px;
align-items: center;
}
.intro-headings {
font-weight: 700;
font-size: var(--font-size-md);
padding-bottom: 30px;
}
.intro-contxt {
font-size: var(--font-size-sm);
}
.info-headings {
font-size: var(--font-size-lg);
font-weight: 700;
color: #d97652;
}
.body-info {
margin-top: 120px;
margin-bottom: 88px;
text-align: center;
}
.info-contxt {
font-size: var(--font-size-sm);
padding-top: 40px;
}
.info-contxt strong {
color: #d97652;
}
.gallery-list {
display: flex;
justify-content: space-between;
overflow-x: hidden;
overflow-x: scroll;
margin-bottom: 90px;
}
.gallery-caption {
display: flex;
align-items: center;
justify-content: center;
}
.gallery-list li ~ li {
padding-left: clamp(20px, 2vw, 72px);
}
.gallery-list li button {
border: none;
box-shadow: 10px 10px 30px rgba(0, 0, 0, 0.25);
border-radius: 30px;
overflow: hidden;
}
.gallery-list li button,
.gallery-list li button img {
width: clamp(260px, 20vw, 378px);
}
.caption-contxt {
font-size: var(--font-size-sm);
padding-right: 52px;
}
.caption-btn {
padding: 15px 30px;
font-weight: 700;
font-size: var(--font-size-sm);
border: none;
background: #d97652;
border-radius: 40px;
color: #fff;
}
.banner-subscribe {
position: relative;
margin-bottom: -100px;
width: 100%;
min-width: 326px;
padding: 50px 58px;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
background: #263140;
border-radius: 30px;
color: #fff;
}
.subscribe-headings {
font-weight: 700;
font-size: var(--font-size-md);
padding-bottom: 18px;
}
.subscribe-contxt {
font-size: 14px;
color: #f2e9d8;
}
.subscribe-form {
width: 530px;
height: 60px;
display: flex;
align-items: center;
justify-content: space-between;
background-color: #fff;
padding-right: 5px;
border-radius: 60px;
}
.subscribe-inpt {
display: flex;
align-items: center;
}
.subscribe-inpt img {
padding-left: 30px;
padding-right: 18px;
}
.subscribe-inpt input {
font-size: var(--font-size-sm);
border: none;
background-color: none;
}
.subscribe-inpt input:focus {
outline: none;
}
.subscribe-btn {
border: none;
padding: 15px 30px;
background: #d97652;
border-radius: 40px;
font-weight: 700;
font-size: var(--font-size-sm);
color: inherit;
}
/* footer */
.footer-img {
width: 100%;
height: 340px;
object-fit: cover;
}
.footer-logo a img {
width: clamp(112px, 20vw, 226px);
}
.section-share {
width: 66%;
margin: auto;
height: 160px;
display: flex;
align-items: center;
justify-content: space-between;
}
.share-link {
display: flex;
}
.share-link li ~ li {
padding-left: 20px;
}
.footer-nav {
color: #767676;
text-align: right;
font-size: 14px;
display: none;
}
.footer-nav ul li + li {
padding-top: 16px;
}
@media (max-width: 1900px) {
.l-wrapper {
width: 100%;
}
}
@media (max-width: 1730px) {
.top-header {
margin-bottom: 40px;
}
.about-img {
display: block;
position: static;
margin: auto;
margin-top: 30px;
}
.header {
padding-bottom: 0px;
}
.banner-subscribe {
flex-direction: column;
gap: 30px;
}
}
@media (max-width: 1410px) {
.gallery-caption {
flex-direction: column;
gap: 41px;
align-items: end;
}
.caption-contxt {
margin: auto;
padding: 0px;
}
}
@media (max-width: 1250px) {
.about-explain {
display: flex;
text-align: center;
flex-direction: column;
justify-content: center;
align-items: center;
}
}
@media (max-width: 860px) {
.body-info {
margin-top: 40px;
margin-bottom: 60px;
}
.gallery-list {
margin-bottom: 60px;
}
.section-body {
padding: 32px 0 50px 0;
}
.header {
width: 85%;
}
.toogle-btn {
display: block;
}
.main {
width: 85%;
}
.section-share {
width: 85%;
}
.header-nav {
display: none;
}
.body-intro {
flex-direction: column;
gap: 40px;
}
.banner-subscribe {
padding: 36px 32px 100px 32px;
}
.subscribe-btn {
position: absolute;
right: 0;
bottom: -68px;
}
.subscribe-form {
position: relative;
width: 100%;
height: 40px;
}
.subscribe-inpt img {
padding-left: 17px;
padding-right: 14px;
}
}
@media (max-width: 450px) {
.section-share {
flex-direction: column;
align-items: start;
justify-content: space-evenly;
}
.footer-nav {
display: block;
position: absolute;
right: 20px;
}
.share-link li ~ li {
padding-left: 16px;
}
}
💡 코드에서 부족한 점이 무엇이있을까? 살펴보고 고민해보자
피드백받기
랜딩 페이지를 구현하면서 고민했던 부분과 그 부분에 대해 답변 주신 내용은 이렇다
1) header 부분만 div로 감싸서 별개로 background-color 주었지만 이것이 올바른 방법일까요? 더 좋은 방법이 없을까요?
<div class="header-bg">
<header class="header">
</header>
</div>
✔️ header 에 background-color 를 주고 그 안에 div를 사용해 감싸주는 방법이 더 나아보입니다.
✔️ 형제요소들로 main 과 footer 가 있기 때문에
header가 이들의 형제요소로 사용되는게 좀 더 의미가 분명하고 통일성 있어 보입니다.
2) 클래스 명을 짓는 부분에서 요소를 감싸고 컨테이너 역할을 하고있는 것을
조금 더 명시적으로 표현하기 위해 클래스 명을 지었는데
이것이, 컨테이너에 담긴 컨텐츠를 재사용할 수 있는 가능성과
문서를 더 구조적으로 생각할 수있다는 점을 고려 했을때 장점으로 느껴졌지만,
코드가 늘어나는 것을 고려해서 더 간결하게 구조 짜는 연습이 필요할까요?
<section class="section-body">
<figure class="body-intro">
</figure>
<div class="body-info">
</div>
<div class="body-gallery">
</div>
</section>
✔️ 접두사로 section이나 body 같은 이름을 넣어서 지으면
실제 존재하는 요소의 이름과 겹치기 때문에 혼란스럽지 않을까 생각합니다.
✔️ 보통 많이 사용하는 컨테이너의 클래스 네이밍은 wrapper, box, cont 이런것들이 있으니 참고바랍니다 🙂
3) 반응형을 위해 컨텐츠들이 최대한 .l-wrapper 의 너비에 맞게 요소가 늘어나고 줄어들 수 있도록 구현했는데,
header부터 footer까지 전체적인 컨텐츠의 너비가 동일하게 존재하기 때문에
.l-wrapper 너비 기준으로 컨텐츠의 너비를 %로 지정 하는 방법이 올바른 것일까요?
.header{
width: 66%;
}
.main {
width: 66%;
}
.section-share{
width: 66%;
}
✔️ 반응형을 위해 %를 많이 사용하면 생각보다 비율을 디자인 처럼 맞추기 쉽지 않습니다.
그래서 보통 반응형에서는 max-width 같은 속성을 많이 사용하는데요.
디자인 시안의 컨텐츠 넓이가 최대 크기라고 감안하고 작업하기 때문입니다.
✔️ 만약 l-wapper 기준으로 안에 들어있는 컨텐츠의 넓이를 %로 줘버리면
l-wapper 커지면 커질 수록 컨텐츠의 넓이도 계속 커지게 됩니다.
디자이너는 그렇게 계속 커지기 보다는 일정 크기까지 커지면 커지기를 멈추는 디자인을 많이 사용합니다.
그래서 max-width 속성을 많이 사용합니다.
그 외에 피드백 받은 내용
1. 디자인 시안에서 컨텐츠의 최대 넓이는 1280px 입니다. 이 넓이 이상 넘어가지 않게 작업해주세요 🙂
2. header-logo 에서 a11y-hidden은 필요 없을것 같습니다. Img 태그의 alt 값에 숨김 텍스트를 넣어도 충분합니다 🙂
3. .header-nav ul 의 list-style-type속성은 이미 ul 요소 전체적으로 리셋스타일로 넣어주셨습니다.
이렇게 중복되는 속성은 모두 제거해주세요 🙂
4. about 과 about-explain 두 가지로 컨테이너를 나눠야 할 필요가 있을까 하는 생각이 듭니다.
필요없는 요소는 과감히 제거해주세요!
5. about-btn에 type 이 명시되어있지 않습니다. 데이터 전달을 위한 submit 버튼이 아니라면 타입을 설정해주세요 🙂
6. .gallery-list li ~ li 에서 요소와 요소 사이의 간격은 마진으로 처리 부탁드립니다.
그리고 여기서는 flex를 사용하셨기 때문에 gap 으로 간격을 주시는것도 좋아보입니다.
7. gallery-list 안의 버튼들에도 타입이 없습니다!
8. subscribe-inpt 요소안의 label에 텍스트가 없습니다. 무엇을 위한 input 요소인지 설명을 넣어주시기바랍니다.
9. subscribe-inpt 요소안의 input 의 넓이가 조금 긴 이메일을 담기에는 너무 좁아보입니다.
사용자 편의를 위해 넓혀주세요 🙂 옆에있는 버튼 근처까지 가면 좋을것 같습니다.
10. share-link 안의 img 태그 alt 값에는 앵커 태그를 누르면 어디로 향하는지에 대한 설명 역할도 포함됩니다.
‘bing 로고 이미지’ 보다는 ‘bing 으로 이동하기’ 정도의 대체 텍스트가 적당해보입니다.
11. l-wrapper 마진때문에 화면이 넓어지면 배경색이 끊어집니다. 재배치가 필요해보입니다.
수정해보기
우선 피드백 받은 내용들은 사소하면서도 기본적인 부분들이 많았다
나름 꼼꼼하게 구현했다고 생각했지만 디테일이 부족했다
특히 반응형 구현을 위해 wrapper의 자식요소에 %를 주는 것이 wrapper의 크기에 맞춰 조절이 돼서
더 동적으로 보일 것이라고 생각했지만,
픽셀퍼펙션을 위해선, %보다는 디자인 시안의 최대 너비를 고려해서
너비의 값을 지정해주는 것이 좋았을텐데 그 부분을 간과했던 것 같다
✔️ label에 텍스트 추가
✔️ button type 추가
✔️ margin 대신 gap처리
✔️ 중복되는 list-style-type 제거
✔️ a 태그롤 감싸진 img에 대한 alt 값 수정
✔️ 로고이미지에 대한 설명은 IR기법 대신 img태그에 alt 값추가
✔️ 뷰포트 너비에 맞게 배경적용
✔️ 불필요한 컨테이너 제거
✔️ wrapper의 최대크기 지정
기능추가
대화 상자 요소 <dialog>
사용하기
<article class="modal">
<img src="..." alt="">
<h5 class="...">Thank you</h5>
<p class="...">Lorem ipsum dolor sit amet.</p>
<form method="dialog">
<button type="submit">confirm</button>
</form>
</article>
const emailForm = document.querySelector('.subscribe-form');
const dialog = document.querySelector('dialog');
emailForm.addEventListener('submit', e => {
e.preventDefault();
dialog.showModal();
})
🔎 dialog
요소는 닫을 수 있는 경고, 검사기, 창 등 대화 상자 같이
상호작용이 가능한 요소로 웹페이지에서 손쉽게 팝업 대화 상자를 만들 수 있다
🔎 open
, close
, show
와 같은 속성을 가질 수 있는데
js에서 showModal()
메서드를 통해 대화 상자를 열 수 있다
🔎 기본적으로 dialog 에는 close()
메서드가 내장되어있어서 js코드 없이도 모달을 닫을 수 있다
🔎 ::backdrop
가상요소를 사용해서 dialog 요소의 뒤에 스타일을 적용할 수 있다
JS로 토글메뉴 구현하기
const nav = document.querySelector('.header-nav')
const open_btn = document.querySelector('.toggle-btn')
const close_btn = document.querySelector('.close-btn')
open_btn.addEventListener('click', () => {
nav.style.transform = "translate(0)";
})
close_btn.addEventListener('click', () => {
console.log('we');
nav.style.transform = "translate(100%)";
})
💡 transform으로 간단하게 토글 메뉴 구현은 쉬웠는데,
💡 뷰포트 크기가 줄어들었을 때 토글버튼이 활성화되는데 이것을 활성화시키고 다시 비활성화했을 때,
transform으로 인해 wrapper너비를 침범해버리는 문제가 있었다
window.addEventListener('resize', () =>{
if(window.innerWidth > 860){
nav.style.transform = "translate(0)";
} else if(window.innerWidth < 860) {
nav.style.transform = "translate(100%)"
}
})
✔️ 따라서, 뷰포트가 토글버튼이 활성화되는 너비보다 커질 경우,
자동으로 translate(0)이 되도록해서 해결했다
완성❗
보완할 점
💡 테마 변경해보기
💡 조금 더 간략한 문서구조짜기
💡 화면이 작아졌을 때 더 매끄럽게 요소들이 작아지고 커질수 있도록 하기
댓글남기기