(특강:종찬님) 보이지 않는 영역 && 코드리뷰
📚 What is TIL?
사소한 구현
종찬님이 첫 특강 때도 말씀해주셨지만 이것저것 기능이 많고 복잡한 것을 구현해낸 것보다
간단하게 구현 해낸 결과물 만으로도 디테일한 부분에 신경을 쓰는 개발자인지 아닌지
구별이 가능하다고 하셨다 즉, 사소한 구현만으로도 평가 항목이 될 수 있다는 얘기인 것 같다
💡 그러니, 기초가 튼튼하고 디테일적인 부분을 신경쓸 줄 알아야한다
💡 그렇다면 다시 한 번… 디테일적인 부분은 무엇일까?
보이지 않는 영역에 대한 대비
예를 들어 디자이너가 일정량의 컨텐츠가 담긴 디자인을 완성해 나에게 구현을 맡겼다고 가정해보자
하지만 여기서 디자이너는 컨텐츠의 양이 많아질 것을 인지하지 못할 수도 있다
만약 내가 디자인에 맞게 보이는대로 구현했다고 한다면
실질적으로 코드의 품질이 떨어지더라도 1차적으로는 괜찮을 것이다
🔎 하지만 문제는 보이지 않는 영역이다
즉, 변수가 생겼을 때를 미리 고려하는 디테일이 없다면 문제가 생길 수 있다
명분을 가지려는 자세
내가 하는 작업스타일에 대해서 명분을 가지려고해야지 더 공부하고 성장할 수 있다
💡 “그냥 이렇게 쓰더라” 와 같은 명분없는 자세로 성장할 수 있을까?
부모레벨에서 자식을 컨트롤할때 padding을 왜 쓰는가?
자식을 컨트롤하기 위해 부모에 padding을 썼다는 것은 공통분모에 패딩이
유지가 될 수 있도록 부모레벨에서 기본적인 공간을 부여하게끔 만들어준 것이다
기본적으로 부모안에 자식이 계속적으로 변화가 일어난다는 가정이 있다면
부모에서 공간을 마련하는 것이 좋다
🔎 컨텐츠에 들어가는 것에 상관없이 패딩부분이 비주얼적으로 보여질 수 있다
🔎 창크기를 변화했을 때 부모입장에서 컨트롤 되게끔 한곳에 넣기위해 padding을 쓴다
section 이라고하는 마크업 내부에 h1이 들어가는 것은 유효한가?
이상적인 문서의 구조에서 h1이 하나만 있는 것이 맞겠지만
html 표준에 입각해서 보자면 section 소속에서 가장 큰 제목이 맞으니까 유효하다
웹은 굉장히 복잡성이 존재하기 때문에 그것들까지 커버되기란 어렵다고 얘기할 수 있다
백그라운드 이미지 VS 마크업
어떤 관점으로 보느냐에 따라 다르지만
웹사이트가 운영되고 관리된다고한다면 대부분 관리자 모드가 따로 존재한다
따라서 컨텐츠들을 관리자모드에서 추가수정하는 경우가 많은데
만약 많은 내용물들이 계속적으로 변화가 빈번하다면 마크업이 더 유리하다
하지만 (sns아이콘)과 같이 고정적인 컨텐츠에는 background-image를 주는 것이 좋다
- css주지 않고 마크업을 주는 이유를 더 알아보자
글자를 background로 대체한 이유
Daum 홈페이지의 경우 글자들을 대신해 이미지를 사용한다고 한다 그렇다면 이유는 무엇일까?
폰트는 여러개의 이미지라고 생각하면 되는데 그렇게 생각하면
용량이 비교적 많은 폰트를 쓰는 것보다 원하는 글자가 새겨진 background를 쓰면
빠르게 렌더링할 수 있고 이미지가 뜨지 않는 오류가 있더라도 대체텍스트를 통해 대체가 가능하다
폰트는 어떻게 적용하는 것이 좋은가?
inherit을 통해 상속받을 수 있게 해주는 것이 좋다
🔎 font-family는 상속이 되기 때문에 전체 선택자가 아닌 root안에 넣는 것이 좋다
⚠️ button의 경우 User-agent stylesheet로 font-family가 개별적으로 들어가는 경우가 있다
button {
font-family: inherit;
}
✔️ 이런경우 inherit으로 상속받을 수 있게 해주어 reset 해줄 수 있다
flex로 글자가 나뉘는 현상
flex 속성을 준 flex-items에 strong 과같이 마크업을 하면 글자가 이상하게 영역이 나눠지는 현상이 있을 수 있다
<p>
<span>Lorem ipsum dolor sit <strong>amet</strong> consectetur</span>
</p>
✔️ 이런 경우 span으로 전체를 감싸서 해결할 수 있다
스크린리더를 고려한 마크업
로그인창 닫기와 같은 버튼이 마크업상 초반에 있다고 가정한다면
스크린리더가 읽는 순서를 고려해봤을 때 컨텐츠의 내용을 먼저 듣는 것이 아니기 때문에
컨텐츠를 파악하는데에 방해요소가 될 수 있다
뿐만아니라 버튼을 누르기 위해서 다시 또 위로 올라가야하기때문에 번거로울 수 있다
되도록이면 로그인 창 닫기와 같은 것들은
마크업 상의 위치가 후반에 있다면 내용을 파악하기에 용이할 것이다
💡 마크업을 제일 뒷부분에 작성하고 position 속성을 이용해서 화면위로 이동시키는 방법이 유용할 것 같다
IR기법
이미지를 볼 수 없는 사용자에게 적절한 대체 텍스트를 제공하는것으로
마크업에 작성하고 CSS로 감추는 기법이다
각 회사마다 쓰는 방법이다르고 방법마다 큰 차이도 있지만
코드가 길어지면 길어질수록 어떻게든 대체 텍스트를 꾸깃꾸깃해서
어떤 상황에서도 보여주지 않기위해서라고 생각하면 될 것 같다
클래스 작명에 대한 다양성
<article class="cont-login">
<h1 class="tit-weniv"><img src="images_login/logo.png" alt="weniv" /></h1>
<p class="txt-login">...</p>
<button type="button" class="btn-login">...</button>
</article>
<section class="cont-form-login">
<h3 class="tit-form">...</h3>
<p class="txt-form">...</p>
<form class="form-login">
<fieldset class="module-inp module-inp-id error">
<label for="inpId" class="txt-hide">...</label>
<input type="text" class="inp-login" id="inpId" placeholder="아이디" />
<strong class="inp-error">...</strong>
</fieldset>
<fieldset class="module-inp error">
<input type="password" id="inpPw" class="inp-login" placeholder="비밀번호" />
<label for="inpPw" class="txt-hide">...</label>
<strong class="inp-error">...</strong>
</fieldset>
<input type="checkbox" id="inpHold" class="inp-hold txt-hide" />
<label for="inpHold" class="labl-hold">...</label>
<button type="submit" class="btn-login">...</button>
</form>
<div class="link-userinfo">
<a href="javascript:void(0);" class="link-signup">...</a>
<a href="javascript:void(0);" class="link-lost">...</a>
</div>
<section class="cont-sns-login">
<h3 class="txt-hide">...</h3>
<ul>
<li><a href="javascript:void(0);" class="icon-google link-sns-login">...</a></li>
<li><a href="javascript:void(0);" class="icon-facebook link-sns-login">...</a></li>
<li><a href="javascript:void(0);" class="icon-naver link-sns-login">...</a></li>
<li><a href="javascript:void(0);" class="icon-kakao link-sns-login">카...</a></li>
</ul>
</section>
<button type="button" class="btn-close"><img src="images_login/close.png" alt="로그인 닫기" /></button>
</section>
💡 형태-의미-상태 로 클래스 명을 짓는 방법이 있다
반면에 다른 방법도 있다
<body>
<header>...</header>
<main>
<section class="section section_purple">
<div class="l_wrapper">
<header class="section-header">...</header>
<div class="section-contents">...</div>
<!-- main이 아닌 div -->
<!-- section 소속의 가장 주된 내용 -->
</div>
</section>
<section class="section section_pink">
<div class="l_wrapper">
<header class="section-header">...</header>
<div class="section-contents">...</div>
<!-- main이 아닌 div -->
<!-- section 소속의 가장 주된 내용 -->
</div>
</section>
</main>
<script src=""></script>
</body>
💡 section
안에 section-header
와 같이 태그가 감싸고 있는 부분끼리 더 구분이 쉽도록 할 수도 있다
💡 태그가 가지고 있는 이름의 의미를 고려해서 클래스이름을 지을 수 있다
💡 재사용성에 용이한 클래스이름을 지을 수 있다
로그인 페이지 구현
재현님의 피드백 예측
우선 나는 로그인 페이지를 header / main / footer 로 각각 나눠서 마크업을 진행했다
재현님이 알려주신 형태-의미-상태 로 클래스명을 한번 지어보니 아직은 어설프지만
감이 조금은 잡히는 느낌이 들었다
그래서 완성한 코드는 이렇다
<div class="container">
<header>
<h1>로그인 또는 회원가입</h1>
<a href="#" class="a-cancel">
<img src="./images/cancel.svg" />
</a>
[label](../_sass/minimal-mistakes/_base.scss)
</header>
<main>
<p class="p-explain">위니브에서 여러분의 궁금증을 해결하세요! :)</p>
<label for="id" class="a11y-hidden">이메일</label>
<input type="email" id="id" class="inpt id" />
<label for="pw" class="a11y-hidden">비밀번호</label>
<input type="password" id="pw" class="inpt pw" />
<p class="warningalt">아이디 혹은 비밀번호과 일치하지 않습니다.</p>
<input type="checkbox" id="loginkeep" class="inp-hold txt-hide" />
<label for="loginkeep" class="labl-hold">로그인 상태유지</label>
<button type="button" class="btn-login">로그인</button>
<div class="join-or-find">
<p>회원가입</p>
<div class="line"></div>
<p>아이디/비밀번호 찾기</p>
</div>
</main>
<footer>
<div class="divid">
<div class="divid-line"></div>
<span>또는</span>
<div class="divid-line"></div>
</div>
<button class="btn-snslogin google">
<img class="snslogo" src="./images/google_logo.svg" alt="" />
<span>구글 계정으로 로그인</span>
</button>
<button class="btn-snslogin facebook">
<img class="snslogo" src="./images/facebook_logo.svg" alt="" />
<span>페이스북 계정으로 로그인</span>
</button>
<button class="btn-snslogin naver">
<img class="snslogo" src="./images/naver_logo.svg" alt="" />
<span>네이버 계정으로 로그인</span>
</button>
<button class="btn-snslogin kakao">
<img class="snslogo" src="./images/kakao_logo.svg" alt="" />
<span>카카오 계정으로 로그인</span>
</button>
</footer>
</div>
제한시간을 두고 코드를 짜보니 마크업을 구조적으로 짜려고하기 보다는
빠르게 마크업을 끝내고 CSS를 작성하기 바빴던 것 같다
다른 분들 피드백 받는 것을 보니 나는 flex를 좀 남용한 감이 있었고,
마크업에 좀 더 디테일적으로 부족한 모습이 많이 보였다
그래서…
만일 재현님이 나의 코드를 리뷰해주셨다면 아래와 같이 지적해주셨을 것 같다
⚠️ input 박스에 required를 주지않은 부분
⚠️ form태그로 감싸지 않은 부분
⚠️ join-or-find 와 같은 클래스 작명에 대한 모호함
⚠️ 버튼 타입(submit)을 지정하지않은 부분
⚠️ 회원가입부분을 <a>
태그로 만들지 않고 <p>
태그를 만든부분
⚠️ 가상요소를 사용하지 않고 <div>
로 구분선을 만든 부분
⚠️ footer부분을 따로 container로 묶지 않은 부분
⚠️ footer에 속하는 버튼에 flex를 준 부분
<!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>로그인 페이지</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="container">
<header>
<h1 class="tit-weniv">로그인 또는 회원가입</h1>
<a href="#" class="a-cancel">
<img src="./images/cancel.svg" />
</a>
</header>
<main>
<p class="p-explain">위니브에서 여러분의 궁금증을 해결하세요! :)</p>
<form>
<label for="id" class="a11y-hidden">이메일</label>
<input type="email" id="id" class="inpt id" required />
<label for="pw" class="a11y-hidden">비밀번호</label>
<input type="password" id="pw" class="inpt pw" required />
<p class="warningalt">아이디 혹은 비밀번호과 일치하지 않습니다.</p>
<input type="checkbox" id="loginkeep" class="inp-hold txt-hide" />
<label for="loginkeep" class="labl-hold">로그인 상태유지</label>
<button type="submit" class="btn-login">로그인</button>
</form>
<div class="login-util">
<p>회원가입</p>
<div class="line"></div>
<p>아이디/비밀번호 찾기</p>
</div>
</main>
<footer>
<div class="divid">
<span>또는</span>
</div>
<div class="cont-sns-login">
<button class="btn-snslogin google">
<img class="snslogo" src="./images/google_logo.svg" alt="" />
<span>구글 계정으로 로그인</span>
</button>
<button class="btn-snslogin facebook">
<img class="snslogo" src="./images/facebook_logo.svg" alt="" />
<span>페이스북 계정으로 로그인</span>
</button>
<button class="btn-snslogin naver">
<img class="snslogo" src="./images/naver_logo.svg" alt="" />
<span>네이버 계정으로 로그인</span>
</button>
<button class="btn-snslogin kakao">
<img class="snslogo" src="./images/kakao_logo.svg" alt="" />
<span>카카오 계정으로 로그인</span>
</button>
</div>
</footer>
</div>
<script src=""></script>
</body>
</html>
* {
font-family: 'Spoqa Han Sans Neo', sans-serif;
font-style: normal;
margin: 0;
padding: 0;
}
.a11y-hidden {
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%);
width: 1px;
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
}
/* header */
.container {
margin: 0 auto;
width: 520px;
/* height: 736px; */
background: #ffffff;
border: 1px solid #c4c4c4;
border-radius: 10px;
}
header {
position: relative;
border-bottom: 1px solid #c4c4c4;
}
.tit-weniv {
text-align: center;
margin-top: 20px;
margin-bottom: 18px;
font-weight: 500;
font-size: 16px;
line-height: 20px;
}
.a-cancel {
position: absolute;
top: 0;
right: 24px;
}
/* main */
main {
padding: 26px 24px 38px 24px;
}
.p-explain {
font-style: normal;
font-weight: 500;
font-size: 20px;
line-height: 24px;
margin-bottom: 24px;
}
.inpt {
box-sizing: border-box;
margin-bottom: 10px;
width: 100%;
height: 50px;
border: 1px solid #c4c4c4;
border-radius: 5px;
padding: 0 16px;
}
.warningalt {
margin-bottom: 10px;
color: #f4492e;
font-style: normal;
font-weight: 400;
font-size: 14px;
}
.txt-hide {
position: absolute;
clip: rect(0 0 0 0);
width: 1px;
height: 1px;
margin: -1px;
overflow: hidden;
}
.labl-hold {
position: relative;
display: inline-block;
font-size: 16px;
color: #767676;
cursor: pointer;
}
.labl-hold::before {
content: '';
display: inline-block;
width: 22px;
height: 22px;
vertical-align: -5px;
margin-right: 10px;
outline: 1px solid #2f80ed;
border-radius: 50%;
transition: outline 0.1s ease;
}
.labl-hold::after {
position: absolute;
left: 0;
top: 0.02em;
content: '';
display: inline-block;
width: 22px;
height: 22px;
opacity: 0;
background: url(./images/check.svg) 0 0 / 22px no-repeat;
transition: 0.3s ease;
}
.inp-hold:checked + .labl-hold::after {
opacity: 1;
}
.inp-hold:hover + .labl-hold::before {
outline: 3px solid #2f80ed;
}
.btn-login {
border: none;
color: #fff;
font-weight: 700;
font-size: 18px;
margin-top: 20px;
margin-bottom: 20px;
width: 100%;
height: 50px;
background: #2f80ed;
border-radius: 5px;
}
.login-util {
display: flex;
align-items: center;
justify-content: center;
color: #767676;
gap: 12px;
}
.line {
width: 1px;
height: 18px;
background: #000;
}
/* footer */
.divid span {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 20px;
gap: 13px;
}
.divid span::before {
content: '';
width: 210px;
height: 1px;
background: #c4c4c4;
}
.divid span::after {
content: '';
width: 210px;
height: 1px;
background: #c4c4c4;
}
.cont-sns-login {
padding: 0 24px 25px 24px;
}
.btn-snslogin {
position: relative;
border: none;
background: #fff;
width: 472px;
height: 50px;
border-radius: 5px;
margin: auto;
}
.snslogo {
position: absolute;
left: 12px;
}
.btn-snslogin:not(:last-child) {
margin-bottom: 10px;
}
.google {
border: 1px solid #767676;
}
.facebook {
border: 1px solid #2d9cdb;
}
.naver {
border: 1px solid #00bf18;
}
.kakao {
border: 1px solid #f2c94c;
}
각 항목에 대해 수정을 해보았다
✔️ input 박스에 required를 주고 form으로 감쌌다
✔️ join-or-find 보다는 login-util 이라고 클래스 이름을 바꿨다
✔️ 버튼 타입(submit)을 지정했다
✔️ 회원가입부분을 <a>
태그로 만들었다
✔️ 구분선을 가상요소로 만들고 flex를 주었따
✔️ footer부분을 묶고 padding을 주어 정렬했다
🔗 TIP
:not(last-child) 의 다른버전
.card + .card {
margin-top: 40px;
}
🔎 카드다음에 카드이라는 의미로 첫번째 카드를 선택하지 않고 margin-top 을 주게된다
해당요소가 ckecked 되었을 때 가상요소에 스타일을 먹이는 select 활용법
.labl-hold:checked + .labl-hold::after {
}
focus-visible
탭으로만 outline을 보이게하는 방법
.inp-hold:focus-visible + .labl-hold::before {
outline: 2px solid #000;
outline-offset: 5px;
}
🔎 포커싱 이벤트가 일어나면 무조건 작동하는 :focus 와는 다르게
키보드 입력이 일어났을 경우만 가상클래스를 적용하게 한다
🔎 접근성을 위해 사용하는 경우 마우스 이벤트로도 작동하는 focus는 시각적인 불편함과 체크를 해제했음에도
포커스되어 있다는 이유로 작동하는 혼란을 줄 수 있기 때문에 focus-visible 이 더 효과적이라고 볼 수 있다
img reset 하는법
img {
max-width: 100%;
height: auto;
vertical-align: top;
}
🔎 이미지 넣은 것이 잘 작동하는지 파악을 위해 임의로 min-height을 쓸 수 있다
clip 과 clip-path 의 차이
clip-path 의 경우 잘라야하는 원본사이즈를 몰라도,
position:absolute를 주지않아도 되는 장점이 있다
반면 clip은 이미지의 크기를 알아야하는 단점이 존재한다
아직 네이버는 clip을 사용 중이라고 한다
아직 clip-path를 지원하지 않는 브라우저 버전이 있기 때문이라고 추측이 된다
a 태그의 href 속성
<a href="#">
🔎 페이지 내에 id=none으로 처리하겠다는 뜻으로 현재 문서의 위치를 말한다
<a href="javascript:void(0)">
🔎 앵커를 클릭했을때 자바스크립트를 동작하도록하겠다는 뜻이다
🔎 void함수를 실행시켜 void안에 0이라는 argument 을 주어 undefined라는 값을 리턴받는다
즉, <a href="#">
과 같은 기능을 한다
fieldset으로 구획나누기
<label for="pw"></label>
<input type="text" id="pw" />
<strong>페스워드를 틀렸습니다</strong>
🔎 <form>요소에서 연관된 요소들이 연속적으로 있는경우 <fieldset>
을 통해 하나의 그룹으로 묶을 수 있다
🔎 여러페이지에서 공통적으로 들어가는 것들은 모듈처럼 빼서 쓸 수 있다
background의 단축속성
background-image: url(./images/checkmark.png);
background-size: 22px;
background-repeat: no-repeat;
background: url(./images/checkmark.png) 0 0 / 22px no-repeat;
아직 헷갈린다…
더보기
코딩 컨벤션 즉 규칙들을 알아야한다
회사에서 쓰는 컨벤션을 인지하지 못한다면 코딩을 잘하고 못하고는 의미가 없다
팀프로젝트를 할때 어느정도의 컨벤션이 갖춰져있어야 일관성이 유지가 될 수 있다
정보의 신뢰성
원하는 정보를 찾으려고 하다보면 뜻하지않게 잘못된 정보를 알고 넘어갈 수 있다
따라서 정보를 온전히 믿지않고 스스로 작동하는지 증명해내는 과정이 필요하다
틀을잡고 커밋하는 버릇해보자
한줄한줄 의미있게 생각하고 단계를 나누자
작업순서를 잘잡자
글씨의 높이를 정렬하기 위해선
글자는 baseline 에 배치되기 때문에 vertical-align으로 해결이 가능하다
border 사용법의 주의점
습관적으로 border을 쓰곤 하는데 다른 요소에 영향을 주지않는 outline이나 box-shadow를 쓰자
button의 type은?
submit 이기때문에 생략하는 것도 괜찮다
가상요소가 적용이 안되는 태그
input같은 열린태그에는 가상요소가 적용이 되지 않는다 하지만 예외도 있다
align-items: normal
초기값 normal은 대부분 stretch로 계산되기 떄문에 center하면 꽉 채워진것이 해제되면서 가운데 정렬
가상요소
인라인이기때문에 자유롭게 크기를 조절해주고 싶다면 inline-block을 줘야함
display flex 속성값을 주면
그 안의 블록이 모두 inline-block처럼 변한다
ul li 를 사용할 떄 주의점
ul li는 최소 3번 이상 반복하는 컨텐츠의 경우 사용하는 것을 권한다
주로 ul이 아닌 li에만 클래스를 주곤 했는데 컨텐츠양 의 변화를 생각해보면 ul에 클래스를 주는 것이 좋다
inset의 활용 🔥
종찬님이 내주신 과제를 완성하면서 레이아웃 구조나
클래스 이름에 대해 조금씩 아쉬움이 있었지만
오늘 강의 중 이미지를 넣는 부분에서 종찬님이 반응형웹을 고려해서
img
가 아닌 h2
에 absolute를 주고
inset 속성으로 h2
를 img
영역 만큼
크기를 채운채로 뜨게 만드셨는데 나도 똑같이 적용했던 거라 사실 좀 뿌듯했다 ㅎ
img
를 onclick 했을 때 img
가 차지하고 있는 이상으로
커지는 효과를 넣으려면 어떻게 해야할까 고민했었는데
결론은 h2
에 background 를 img
와 똑같은 이미지를 배경으로 주고
inset:0
하면 되겠다는 생각이었다
잘 작동되도록 자스도 조금 손을 좀 봤다
.img-title {
user-select: none;
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 30px;
color: #fff;
background: rgba(255, 69, 0, 0.1);
transition: all 1s;
}
이제 자러가야지,,
댓글남기기