※ 자바스크립트 프론트엔드 스터디에서 발표했던 내용을 정리한 글이기 때문에

스터디원이 아니시라면 조금 이해가 어려우실 수 있습니다

 

위 이미지와 같은 레이아웃을 제작하는 과정입니다.(desktop_components_02)

Figma 링크 : https://www.figma.com/file/viEujZmbqeYG4FHhsi0H7o/responsive_web_components_mobikdesign_v1.0-(Copy)?node-id=0%3A1

 

Figma

Created with Figma

www.figma.com

 

먼저 HTML에서 반응형 웹 설정을 해주세요.

<meta name="viewport" content="width=device-width" initial-scale=1.0>

 

viewport : viewport란 웹페이지의 전체 영역 중 사용자의 기기 화면에 보여지는 영역을 말합니다. 위의 태그는 '웹페이지가 사용자의 화면에 어떤 크기, 비율로 보여질지'를 설정하는 태그입니다.

width = device-width : 웹페이지의 '가로길이'를 사용자의 기기 화면 가로 길이에 맞춘다는 의미입니다.

initial-scale=1.0 : 웹페이지에 첫 접속했을 때 화면의 비율을 말합니다. 1.0으로 설정해줘서 확대되거나 축소되지 않은 원래 크기로 보여지도록 설정했습니다.

 

<link rel="stylesheet" href="style1.css" media="(min-width: 768px)">
<link rel="stylesheet" href="style2.css" media="(min-width: 360px) and (max-width: 768px)">
<link rel="stylesheet" href="style3.css" media="(max-width: 360px)">

두번째 줄의 태그는 "사용자의 화면 가로길이가 360px이상 768px이하일 때, "style2.css"파일로 연결하라"는 의미의 태그입니다.

사용자의 화면 너비 (가로길이)에 따라 다른 css파일로 연결됩니다.

발표에서는 일단 너비가 768px 이상 일 때 연결되는 "style1.css"파일 하나만 작업해보겠습니다. (하나만 제대로 작업해보면 나머지 파일은 같은 방식으로 쉽게 작업할 수 있습니다)

 

HTML에서 전체적인 구조를 작성해주세요.

※ HTML 전체 소스코드는 페이지 하단에 올려놨습니다(html소스는 구조만 파악한 뒤 그대로 복사붙여넣기 해서 사용하고, css소스는 직접 작성해보시는 것을 추천합니다)

<div id="layout">

<div id="navBar"></div>

<div id="title"></div>

<div id="wrapper"></div>

<div id="footerSections"></div>

<div id="bottomBar"></div>

</div>

레이아웃 전체를 div태그를 이용해 다섯 부분으로 나누고(navBar, title, wrapper, footerSections, bottomBar), "layout"이라는 id값을 가진 태그로 레이아웃 전체를 묶어줬습니다.

 

CSS에서 레이아웃의 기본 틀을 구성합니다. (height or grid)

두 가지 방법으로 해보겠습니다.

첫 번째는,

각각의 <div>태그에 height 값을 설정해서 레이아웃을 구성하는 방법입니다. 우리가 만들 레이아웃은 위에서 아래로, 세로로 쌓아나가는 기본적인 배치이기 때문에

<div>태그로 묶인 각각의 '박스'에 높이만 설정해줘도 레이아웃의 틀이 완성됩니다.

/* height 설정으로 layout 만들기 */



#navBar { height: 64px; }

#title { height: 200px; }

#wrapper { height: 712px; }

#footerSections { height: 184px; }

#bottomBar { height: 48px; }

높이는 Figma에서 보고 그대로 써줬습니다. (반응형 웹이라 가로길이는 화면너비에 따라 유동적으로 만들지만, 세로 길이는 그냥 '픽셀'을 사용해서 고정해줬습니다.)

 

두 번째는,

'grid'속성을 이용하는 방법입니다. grid는 표를 만드는 것처럼 레이아웃을 구성합니다.

우리가 만드는 레이아웃이 5행으로 이뤄진 표라고 생각하고

#layout {

display: grid;

grid-template-rows: 64px 200px 712px 184px 48px;

}

각 행의 높이를 64px, 200px, ...으로 설정했습니다. (rows는 행을 의미합니다)

첫번째 방법과 동일한 틀이 만들어집니다.

단, grid속성은 인터넷익스플로러에서는 지원이 되지 않거나 일부만 지원되니 참고하세요!(https://caniuse.com/#search=grid)

 

Position을 설정합니다 ( 중요!! )

navBar, title, wrapper, footerSections, bottomBar의 position을 relative로 설정해주세요!

#navBar {

position: relative;

}



#title {

position: relative;

}



#wrapper {

position: relative;

}



#footerSections {

position: relative;

}



#bottomBar {

position: relative;

}

 

아래와 같이 한꺼번에 설정할 수도 있습니다.

#layout > * {

position: relatvie;

}

/* layout 태그의 하위 태그들에 한꺼번에 스타일이 적용됩니다. */

 

position을 relative로 설정해야하는 이유는, Figma에 있는 레이아웃의 css속성들을 확인해보시면 알 수 있습니다.

대부분의 요소들이 "position: absolute"로 배치되어 있습니다.

position:absolute로 설정을 하면, position이 static이 아닌(absolute/relative/fixed) 부모 태그를 기준으로 배치가 됩니다. 만약 position이 static이 아닌 부모태그를 찾을 수 없으면, <body>태그를 기준으로 배치가 됩니다.

position 속성들 ↓

더보기

position 속성들

static : position을 따로 설정하지 않았을 경우 기본적으로 부여되는 포지션.

absolute : "position이 static이 아닌 상위 엘리먼트를 기준"으로 배치하는 것.

(position이 static이 아닌 상위 엘리먼트가 없을 경우 body태그를 기준으로 배치됨.)

relative : "원래의 위치 기준"으로 배치하는 것

fixed : "전체화면 기준"으로 배치하는 것

 

예를 들어 Figma에서 "HEADING TITLE" 요소를 보면 ,

position: absolute; left: 5.78%; right: 5.62%; top: 24%; bottom: 48%;

으로 배치가 되어 있습니다.

"HEADING TITLE"의 상위 엘리먼트는 <div id="title">태그인데, title태그의 position은 static입니다.(포지션을 따로 설정하지 않았기 때문에 기본값인 static으로 설정되어있는 상태)

따라서 "HEADING TITLE"은 포지션이 static이 아닌 상위태그가 없어 body태그를 기준으로 배치가 됩니다.

그러니 body태그의 높이 기준으로 아래에서 48%만큼, 위에서 24% 떨어진 위치에 배치가 되는데,

직접 해보면 아시겠지만 제대로 된 위치가 아닌 이상한 위치에 배치가 됩니다.

 

제대로 배치되게 하려면 상위 엘리먼트인 <div id="title">태그 기준으로 배치되도록(title태그의 높이 200px 기준으로 위에서 24%, 아래서 48%떨어진 위치에 배치되어야 합니다)

<div id="title">태그의 position을 absolute, relative, 혹은 fixed 셋 중 하나로 설정해줘야합니다.

이 중 가장 적합한 속성은 relative입니다.

fixed는 스크롤을 올리거나 내려도 계속 같은 자리에 있는 광고배너와 같은 특수한 경우에만 사용하고, absolute를 선택한다면 아예 전체 레이아웃 틀 자체를 absolute로 다시 짜야합니다.(이 방법은 위에서처 height / grid 로 레이아웃을 짜는 것에 비해 복잡합니다.)

반면에 relative를 선택하고 top, left 값 등을 별도로 설정하지 않으면 원래 위치에 그대로 배치되기 때문에 우리가 앞에서 만든 레이아웃 틀을 건드리지 않고 진행할 수 있기 때문입니다.

 

------

여기까지 하셨다면 나머지 부분은 figma에 있는 속성들을 복사+붙여넣기 하시면 됩니다!

 

발표 때 제대로 보여드리지 못한 footerSections 배치하는 법만 추가로 설명해드리겠습니다.

 

footerSections 배치하기

HTML문서의 <div id="footerSections"> 태그 안에 footer section의 요소들을 작성해줍니다.

아래 코드를 복붙하셔도 됩니다.

<div id="footerSection1">
	<div class="sectionName">footer section 01</div>
	<div class="line1"></div>
	<div class="line2"></div>
	<div class="explain">Apparently we had reached a great height in the atmosphere, for the sky was a dead black, and the stars had ceased to twinkle.</div>
</div>

<div id="footerSection2">
	<div class="sectionName">footer section 02</div>
	<div class="line1"></div>
	<div class="line2"></div>
	<div class="explain">By the same illusion which lifts the horizon of the sea to the level of the spectator on a hillside, the sable cloud beneath was dished</div>
</div>

<div id="footerSection3">
	<div class="sectionName">footer section 03</div>
	<div class="line1"></div>
	<div class="line2"></div>
	<div class="explain">And the car seemed to float in the middle of an immense dark sphere, whose upper half was strewn with silver.</div>
</div>

<div id="footerSection4">
	<div class="sectionName">footer section 04</div>
	<div class="line1"></div>
	<div class="line2"></div>
	<div class="explain">Looking down into the dark gulf below, I could see a ruddy light streaming through a rift in the clouds.</div>
</div>

 

이제 footerSection1, 2, 3, 4의 자리를 각각 잡아줘야합니다.

Figma에서 Footer_section_v01 , v02, v03, v04의 속성을 각각 복사해와서 붙여넣기 해주시면 돼요.

 

#footerSection1 {
  position: absolute;
  left: 5.62%;
  right: 73.12%;
  top: 20.69%;
  bottom: 31.03%;
}

#footerSection2 {
  position: absolute;
  left: 28.12%;
  right: 50.62%;
  top: 20.69%;
  bottom: 31.03%;
}

#footerSection3 {
  position: absolute;
  left: 50.62%;
  right: 28.12%;
  top: 20.69%;
  bottom: 31.03%;
}

#footerSection4 {
  position: absolute;
  left: 73.12%;
  right: 5.62%;
  top: 20.69%;
  bottom: 31.03%;
}

각 섹션들의 자리배치를 해줬습니다.

 

이제 세부적인 스타일을 적용해야합니다.

.sectionName {
  font-family: Roboto;
  font-style: normal;
  font-weight: normal;
  font-size: 14px;
  line-height: 16px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: rgba(0, 0, 0, 0.8);
  margin-bottom: 15px;
}

.line1 {
  background: rgba(0, 0, 0, 0.08);
  height: 2px;
}

.line2 {
  background: linear-gradient(234.67deg, rgba(255, 255, 255, 0.16) 0.24%, rgba(255, 255, 255, 0) 100.87%), #006BE8;
  height: 2px;
  width: 30%;
  position: relative;
  bottom: 2px;
}

.explain {
  font-family: Roboto;
  font-style: normal;
  font-weight: normal;
  font-size: 12px;
  line-height: 20px;
  letter-spacing: 0.02em;
  color: rgba(0, 0, 0, 0.64);
  margin-top: 15px;
}

각 footer section안에 공통적으로 들어가있는 세부요소들(이름, 라인, 설명 등)은 id값 대신 class값을 지정해줬기 때문에 이렇게 한꺼번에 스타일을 적용할 수 있습니다.

(id값은 하나의 태그에만 지정할 수 있고, class값은 여러개의 태그에 지정할 수 있기 때문에 여러 개의 태그에 한꺼번에 공통된 스타일을 적용하려면 class값을 지정합니다.)

 

Footer Section 제목과 아래 영어로 된 설명을 구분하는 라인이 보이시죠?

라인은 Figma에서 이미지로 export해서 써도 되지만 저는 직접 만들어봤습니다.

  1. line1과 line2에 각각 background color와 height값을 설정해줘서(background 색상코드는 figma참고, height는 2px) 회색과 파란색, 두 개의 라인을 만들어 줍니다.

  2. 떨어져있는 라인 두개가 겹쳐지게끔 아래쪽에 있는 파란색 라인을 position:relative;로 설정한 다음bottom:2px;로 설정해서 위로 2px올라오게 합니다. 라인 두개가 겹쳐졌습니다.

  3. 파란색 라인의 길이를 30%로 설정해주세요.( width: 30%; )

이렇게 하면 라인이 완성됩니다.

 

그리고 추가로

.sectionName에는 margin-bottom:15px;

.explain에는 margin-top:15px;

마진을 각각 설정해줬습니다. (figma에서 footer section을 보시면 섹션이름과 라인, 아래 설명이 각각 15px만큼 떨어져있기 때문이에~)

 

이 외에는 전부 피그마에서 복붙하시면 완성입니다!

 

(참고! 안읽어도 상관없음

  • 사실 Figma의 레이아웃은 footer section의 모든 요소 하나하나에 일일히 다 id값을 주고 absolute로 위치를 잡아주는 방식으로 되어있는데요. 이렇게 하려면 중복된 소스코드가 많아지기 때문에 저는 그냥 footer section1, 2, 3, 4로 각각 묶어준 다음 이렇게 묶어준 네 개의 섹션만 absolute로 배치를 했어요.

  • 그리고 그 안의 세부적인 요소들은 absolute로 배치하는 대신 그냥 마진값과 포지션값을 살짝 조정해서 위치를 잡아줬습니다.

)

 

혹시라도 레이아웃을 만들면서 이해가 안되거나 어려운 부분이 있으시면 언제든지 저한테 질문해주세요~

아니면 아래에 있는 소스코드를 잘 읽어보시면 어느정도 해결이 되실 거에요

그럼 화이팅!! :D

 

※ 전체소스코드

HTML 전체 소스코드입니다

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width" initial-scale=1.0>
  <title>JS Bin</title>
  <link rel="stylesheet" href="style1.css" media="(min-width: 768px)">
  <link rel="stylesheet" href="style2.css" media="(min-width: 360px) and (max-width: 768px)">
  <link rel="stylesheet" href="style3.css" media="(max-width: 360px)">
</head>

<body>
  <div id="layout">
    <div id="navBar">
      <div id="logo">LOGO</div>
      <div id="menu">
        <a class="links">menu link 01</a>
        <a class="links">menu link 02</a>
        <a class="links">menu link 03</a>
        <a class="links">menu link 04</a>
        <a class="links">menu link 05</a>
      </div>
      <input type="button" value="sign in" id="signIn">
    </div>
    <div id="title">
      <div id="heading">heading title</div>
      <div id="subtitle">heading subtitle text</div>
    </div>
    <div id="wrapper">
      <div id="inner">
        <div id="box1"></div>
        <div id="box2"></div>
      </div>
    </div>
    <div id="footerSections">
      <div id="footerSection1">
        <div class="sectionName">footer section 01</div>
        <div class="line1"></div>
        <div class="line2"></div>
        <div class="explain">Apparently we had reached a great height in the atmosphere, for the sky was a dead black, and the stars had ceased to twinkle.</div>
      </div>

      <div id="footerSection2">
        <div class="sectionName">footer section 02</div>
        <div class="line1"></div>
        <div class="line2"></div>
        <div class="explain">By the same illusion which lifts the horizon of the sea to the level of the spectator on a hillside, the sable cloud beneath was dished</div>
      </div>

      <div id="footerSection3">
        <div class="sectionName">footer section 03</div>
        <div class="line1"></div>
        <div class="line2"></div>
        <div class="explain">And the car seemed to float in the middle of an immense dark sphere, whose upper half was strewn with silver.</div>
      </div>

      <div id="footerSection4">
        <div class="sectionName">footer section 04</div>
        <div class="line1"></div>
        <div class="line2"></div>
        <div class="explain">Looking down into the dark gulf below, I could see a ruddy light streaming through a rift in the clouds.</div>
      </div>
    </div>
    <div id="bottomBar">
      <div id="copyright">© Copyright</div>
      <div id="facebook">ㅁ</div>
      <div id="twitter">ㅁ</div>
      <div id="instagram">ㅁ</div>
    </div>
  </div>

</body>

</html>

 

CSS 전체 소스코드입니다

#layout > * {
	border: 1px solid gray;
	position: relative;
}
#layout {
	display: grid;
	grid-template-rows: 64px 200px 712px 184px 48px;
	margin-bottom: 60px;
}
#logo {
	position: absolute;
	width: 104px;
	height: 32px;
	left: 5.62%;
	top: 16px;
}
#menu {
	position: absolute;
	left: 15%;
	right: 18%;
	top: 28.57%;
	bottom: 42.86%;
	text-align: center;
	font-family: Roboto;
	font-style: normal;
	font-weight: normal;
	font-size: 14px;
	line-height: 16px;
	/* identical to box height, or 114% */
	
	text-align: center;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	/* Text_blue */
	
	color: rgba(0, 0, 0, 0.88);
}
.links {
	margin-left: 3%;
}
#signIn {
	position: absolute;
	left: 86.88%;
	right: 5.62%;
	top: 12.5%;
	bottom: 12.5%;
}
#title {
	background: linear-gradient(263.53deg, rgba(255, 255, 255, 0.32) 0.24%, rgba(255, 255, 255, 0.08) 100.87%), #006BE8;
}
#heading {
	position: absolute;
	left: 5.78%;
	right: 5.62%;
	top: 24%;
	bottom: 48%;
	font-family: Roboto;
	font-style: normal;
	font-weight: 500;
	font-size: 64px;
	line-height: 56px;
	/* identical to box height, or 87% */
	
	align-items: center;
	text-align: center;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	font-variant: small-caps;
	/* #FFFFFF */
	
	color: #FFFFFF;
}
#subtitle {
	position: absolute;
	left: 5.78%;
	right: 5.62%;
	top: 64%;
	bottom: 24%;
	font-family: Roboto;
	font-style: normal;
	font-weight: 500;
	font-size: 24px;
	line-height: 24px;
	/* identical to box height, or 100% */
	
	align-items: center;
	text-align: center;
	letter-spacing: 0.32em;
	text-transform: uppercase;
	/* #FFFFFF */
	
	color: #FFFFFF;
}
#wrapper {
	background: gray;
}
#inner {
	height: 584px;
	margin-top: 48px;
	margin-bottom: 80px;
	margin-left: 8%;
	margin-right: 8%;
	display: grid;
	grid-template-columns: 300px 1fr;
}
#box1 {
	background-color: white;
	margin-right: 16px;
}
#box2 {
	background-color: white;
}
#footerSection1 {
	position: absolute;
	left: 5.62%;
	right: 73.12%;
	top: 20.69%;
	bottom: 31.03%;
}
#footerSection2 {
	position: absolute;
	left: 28.12%;
	right: 50.62%;
	top: 20.69%;
	bottom: 31.03%;
}
#footerSection3 {
	position: absolute;
	left: 50.62%;
	right: 28.12%;
	top: 20.69%;
	bottom: 31.03%;
}
#footerSection4 {
	position: absolute;
	left: 73.12%;
	right: 5.62%;
	top: 20.69%;
	bottom: 31.03%;
}
.sectionName {
	font-family: Roboto;
	font-style: normal;
	font-weight: normal;
	font-size: 14px;
	line-height: 16px;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: rgba(0, 0, 0, 0.8);
	margin-bottom: 15px;
}
.line1 {
	background: rgba(0, 0, 0, 0.08);
	height: 2px;
}
.line2 {
	background: linear-gradient(234.67deg, rgba(255, 255, 255, 0.16) 0.24%, rgba(255, 255, 255, 0) 100.87%), #006BE8;
	height: 2px;
	width: 30%;
	position: relative;
	bottom: 2px;
}
.explain {
	font-family: Roboto;
	font-style: normal;
	font-weight: normal;
	font-size: 12px;
	line-height: 20px;
	/* or 167% */
	
	letter-spacing: 0.02em;
	color: rgba(0, 0, 0, 0.64);
	margin-top: 15px;
	}
#copyright {
	position: absolute;
	left: 41.25%;
	right: 41.25%;
	top: 33.33%;
	bottom: 33.33%;
	font-family: Roboto;
	font-style: normal;
	font-weight: 300;
	font-size: 12px;
	line-height: 16px;
	text-align: center;
	letter-spacing: 0.04em;
	color: rgba(0, 0, 0, 0.48);
}
#instagram {
	position: absolute;
	left: 93.12%;
	right: 5.62%;
	top: 33.33%;
	bottom: 33.33%;
}
#twitter {
	position: absolute;
	left: 91.25%;
	right: 7.5%;
	top: 33.33%;
	bottom: 33.33%;
}
#facebook {
	position: absolute;
	left: 89.38%;
	right: 9.38%;
	top: 33.33%;
	bottom: 33.33%;
}

 

https://skm1104.tistory.com/4

 

네이버 회원가입 페이지 따라 만들기(클론코딩) - HTML/CSS

HTML, CSS, Javascript 연습용으로 네이버 회원가입 페이지를 따라 만들어봤습니다. 회원가입 페이지 레이아웃을 html과 css로 비슷하게 따라하고, Javascript로 사용자가 데이터를 입력했는지 확인하기 + 사용자..

skm1104.tistory.com

HTML, CSS에 이어서 Javascript로 기능구현하는 과정까지 보여드릴게요.

 

저는 간단한 유효성검사 기능까지만 구현했습니다. 

사용자가 데이터를 입력하지 않았거나, 잘못된 데이터를 입력했을 때(조건/형식에 맞지 않는 데이터를 입력했을 때) 

입력폼 아래에 "필수 정보입니다", "8~16자 영문 대 소문자, 숫자, 특수문자를 사용하세요."와 같은

에러메세지를 출력할 것입니다.

 

기능을 구현하기 전에

먼저 네이버 실제 회원가입 페이지의 유효성 검사 로직을 직접 이것저것 입력해보면서 분석해봤습니다.

입력폼별로 로직을 정리한 내용입니다. 

더보기

1. 아이디 : 

데이터 입력x : 필수 정보입니다.

5자미만/20자초과/영문소문자,숫자,특수기호(-),(_) 외의 문자 입력 : 5~20자의 영문 소문자, 숫자와 특수기호(_),(-)만 사용 가능합니다.

조건에 맞는 데이터 입력 : 멋진 아이디네요!

2. 비밀번호 :

데이터 입력x : 필수 정보입니다.

8자미만/16자초과/영문대소문자, 숫자, 특수문자 외의 문자 입력 :

8~16자 영문 대 소문자, 숫자, 특수문자를 사용하세요.

(네이버에서는 비밀번호를 위험, 보통, 안전, 사용불가 네 가지로 판단하는데 전부 다 구현하기에는 좀 어려워서ㅜㅜ그냥 안전, 사용불가 두가지로만 나눠서 구현했습니다)

3. 비밀번호 재확인 :

데이터 입력x : 필수 정보입니다.

비밀번호와 불일치 : 비밀번호가 일치하지 않습니다.

네이버 비밀번호 재확인 입력폼은

8자미만/16자초과/특정문자 입력불가 등의 조건이 없고 그냥 2번에 입력한 비밀번호와 일치하는지만

판단하더라구요. ("123"이라고 입력해도 위의 비밀번호 입력폼에 입력된 비밀번호와 동일하다면 에러메세지가 출력되지 않았습니다.)

4. 이름 :

데이터 입력x : 필수 정보입니다.

한글, 영문 대소문자 외의 문자 사용(특수기호, 공백 안됨. 자음이나 모음 단독입력도 안됨)x :

한글과 영문 대 소문자를 사용하세요. (특수기호, 공백 사용 불가)

5. 생년월일 :

가장 복잡한 부분이었습니다. 순서도를 그려야할 것 같은데 일단 그냥 써보겠습니다.

1) 연도가 입력되었는지 확인한다(숫자4개이기만 하면 ok)

1-1) 연도가 입력되었다면, 2)로 이동

1-2) 연도가 입력되지 않았다면, "태어난 년도 4자리를 정확하게 입력하세요." 출력

2) 월이 입력되었는지 확인한다.("월"외의 option을 선택하면 ok)

2-1) 월이 입력되었다면, 3)으로 이동

2-2) 월이 입력되지 않았다면, "태어난 월을 선택하세요." 출력

3) 날짜가 입력되었는지 확인한다.(숫자 1개~2개이기만 하면 ok)

3-1) 날짜가 입력되었다면, 4)로 이동

3-2) 날짜가 입력되지 않았다면, "태어난 일(날짜) 2자리를 정확하게 입력하세요." 출력

3-3) 날짜에 숫자1개~2개 외의 다른 데이터(문자, 숫자3개이상 등)가 입력되었다면, "생년월일을 다시 확인해주세요." 출력

4) 입력된 생년월일을 확인한다.

4-1) 나이가 만100세 이상이면 "정말이세요?" 출력.

(오늘 날짜 2019-12-17 기준, 100년전인 1919-12-17을 입력하면 에러메세지가 출력되고, 1919-12-18이후면 ok)

4-2) 나이가 만14세 미만이면 "만 14세 미만의 어린이는 보호자 동의가 필요합니다." 출력

4-3) 미래의 날짜(?)를 입력하면 "미래에서 오셨군요. ^^" 출력

 

6. 성별

데이터 입력x : 필수 정보입니다.

처음부터 이 에러메세지가 뜨지는 않고, "남자" 혹은 "여자"를 선택했다가 다시 "성별"을 선택하면

그 때 에러메세지가 뜹니다.

 

7. 본인 확인 이메일

데이터 입력x : 선택 입력사항이므로 아무것도 입력하지 않아도 에러메세지가 뜨지 않습니다.

이메일 형식이 아닌 데이터 입력: "이메일 주소를 다시 확인해주세요." 출력

 

8. 휴대전화

데이터 입력x : 필수 정보입니다.

휴대폰 번호 형식이 아닌 데이터 입력 : "형식에 맞지 않는 번호입니다." 출력

분석이랄 것도 없지만 어쨌든 이 분석내용을 바탕으로 자바스크립트 소스를 작성했습니다. 

 

첫 번째로, DOM에서 입력폼들과 에러메세지 출력 부분의 아이디/클래스명을 가져와서 전부 변수에 담았습니다.

/*변수 선언*/


var id = document.querySelector('#id');

var pw1 = document.querySelector('#pswd1');
var pwMsg = document.querySelector('#alertTxt');
var pwImg1 = document.querySelector('#pswd1_img1');

var pw2 = document.querySelector('#pswd2');
var pwImg2 = document.querySelector('#pswd2_img1');
var pwMsgArea = document.querySelector('.int_pass');

var userName = document.querySelector('#name');

var yy = document.querySelector('#yy');
var mm = document.querySelector('#mm');
var dd = document.querySelector('#dd');

var gender = document.querySelector('#gender');

var email = document.querySelector('#email');

var mobile = document.querySelector('#mobile');

var error = document.querySelectorAll('.error_next_box');

이 부분은 특별히 설명하지 않아도 되겠죠? 사실 글로벌 변수 사용은 자제하는 게 좋다고 하던데 다른 방법을 아직 모르겠고 간단한 기능 구현이기 때문에 어쩔 수 없이 이렇게 글로벌 변수를 왕창 선언해줬습니다^^;

 

두 번째로, 변수에 담긴 입력폼 요소에 이벤트 핸들러를 연결해줬습니다. 

id.addEventListener("change", checkId);
pw1.addEventListener("change", checkPw);
pw2.addEventListener("change", comparePw);
userName.addEventListener("change", checkName);
yy.addEventListener("change", isBirthCompleted);
mm.addEventListener("change", isBirthCompleted);
dd.addEventListener("change", isBirthCompleted);
gender.addEventListener("change", function() {
    if(gender.value === "성별") {
        error[5].style.display = "block";
    } else {
        error[5].style.display = "none";
    }
})
email.addEventListener("change", isEmailCorrect);
mobile.addEventListener("change", checkPhoneNum);

gender 부분은 함수가 짧길래 그냥 저렇게 익명함수로 작성해봤고 나머지는 따로 함수를 작성했습니다. 

 

여기서 제가 해결하지 못한 부분은, 

네이버에서는 처음에 내용이 아무것도 입력되지 않은 텍스트 입력 폼에 마우스 커서만 눌렀다가 떼도

이벤트가 발생해서 "필수 정보입니다."가 출력되는데, 

제가 작성한 소스는 "change"이벤트가 발생해야 함수가 실행되기 때문에 텍스트 입력폼 내용에 변경이 있어야만

에러메세지가 출력된다는 점입니다. ("필수 정보입니다."가 출력되게 하려면 뭔가 입력했다가 지워야합니다ㅜ)

 

다른 이벤트 종류가 있는지 찾아봤지만 잘 모르겠더라구요.. 

 

마지막으로, 콜백 함수들을 작성해줬습니다.

이 때 정규식이 필요한 함수의 경우 정규식 패턴을 만들어 함수 내부에서 변수에 담아 선언해줬습니다.

콜백 함수 소스코드 전체보기 ↓

더보기
/*콜백 함수*/


function checkId() {
    var idPattern = /[a-zA-Z0-9_-]{5,20}/;
    if(id.value === "") {
        error[0].innerHTML = "필수 정보입니다.";
        error[0].style.display = "block";
    } else if(!idPattern.test(id.value)) {
        error[0].innerHTML = "5~20자의 영문 소문자, 숫자와 특수기호(_),(-)만 사용 가능합니다.";
        error[0].style.display = "block";
    } else {
        error[0].innerHTML = "멋진 아이디네요!";
        error[0].style.color = "#08A600";
        error[0].style.display = "block";
    }
}

function checkPw() {
    var pwPattern = /[a-zA-Z0-9~!@#$%^&*()_+|<>?:{}]{8,16}/;
    if(pw1.value === "") {
        error[1].innerHTML = "필수 정보입니다.";
        pwMsg.style.display = "block";
        pwMsgArea.style.paddingRight = "40px";
        pwImg1.src = "m_icon_pass.png";
        error[1].style.display = "block";
    } else if(!pwPattern.test(pw1.value)) {
        error[1].innerHTML = "8~16자 영문 대 소문자, 숫자, 특수문자를 사용하세요.";
        pwMsg.innerHTML = "사용불가";
        pwMsgArea.style.paddingRight = "93px";
        error[1].style.display = "block";
        pwMsg.style.color = "red";
        pwMsg.style.display = "block";
        pwImg1.src = "m_icon_not_use.png";
    } else {
        error[1].style.display = "none";
        pwMsg.innerHTML = "안전";
        pwMsgArea.style.paddingRight = "93px";
        pwMsg.style.color = "#03c75a";
        pwMsg.style.display = "block";
        pwImg1.src = "m_icon_safe.png";
    }
}

function comparePw() {
    if(pw2.value === pw1.value) {
        pwImg2.src = "m_icon_check_enable.png";
        error[2].style.display = "none";
    } else if(pw2.value !== pw1.value) {
        pwImg2.src = "m_icon_check_disable.png";
        error[2].innerHTML = "비밀번호가 일치하지 않습니다.";
        error[2].style.display = "block";
    } 

    if(pw2.value === "") {
        error[2].innerHTML = "필수 정보입니다.";
        error[2].style.display = "block";
    }
}

function checkName() {
    var namePattern = /[a-zA-Z가-힣]/;
    if(userName.value === "") {
        error[3].innerHTML = "필수 정보입니다.";
        error[3].style.display = "block";
    } else if(!namePattern.test(userName.value) || userName.value.indexOf(" ") > -1) {
        error[3].innerHTML = "한글과 영문 대 소문자를 사용하세요. (특수기호, 공백 사용 불가)";
        error[3].style.display = "block";
    } else {
        error[3].style.display = "none";
    }
}


function isBirthCompleted() {
    var yearPattern = /[0-9]{4}/;

    if(!yearPattern.test(yy.value)) {
        error[4].innerHTML = "태어난 년도 4자리를 정확하게 입력하세요.";
        error[4].style.display = "block";
    } else {
        isMonthSelected();
    }


    function isMonthSelected() {
        if(mm.value === "월") {
            error[4].innerHTML = "태어난 월을 선택하세요.";
        } else {
            isDateCompleted();
        }
    }

    function isDateCompleted() {
        if(dd.value === "") {
            error[4].innerHTML = "태어난 일(날짜) 2자리를 정확하게 입력하세요.";
        } else {
            isBirthRight();
        }
    }
}



function isBirthRight() {
    var datePattern = /\d{1,2}/;
    if(!datePattern.test(dd.value) || Number(dd.value)<1 || Number(dd.value)>31) {
        error[4].innerHTML = "생년월일을 다시 확인해주세요.";
    } else {
        checkAge();
    }
}

function checkAge() {
    if(Number(yy.value) < 1920) {
        error[4].innerHTML = "정말이세요?";
    } else if(Number(yy.value) > 2019) {
        error[4].innerHTML = "미래에서 오셨군요. ^^";
    } else if(Number(yy.value) > 2005) {
        error[4].innerHTML = "만 14세 미만의 어린이는 보호자 동의가 필요합니다.";
    } else {
        error[4].style.display = "none";
    }
}


function isEmailCorrect() {
    var emailPattern = /[a-z0-9]{2,}@[a-z0-9-]{2,}\.[a-z0-9]{2,}/;

    if(email.value === ""){ 
        error[6].style.display = "none"; 
    } else if(!emailPattern.test(email.value)) {
        error[6].style.display = "block";
    } else {
        error[6].style.display = "none"; 
    }

}

function checkPhoneNum() {
    var isPhoneNum = /([01]{2,})([01679]{1,})([0-9]{3,4})([0-9]{4})/;

    if(mobile.value === "") {
        error[7].innerHTML = "필수 정보입니다.";
        error[7].style.display = "block";
    } else if(!isPhoneNum.test(mobile.value)) {
        error[7].innerHTML = "형식에 맞지 않는 번호입니다.";
        error[7].style.display = "block";
    } else {
        error[7].style.display = "none";
    }

    
}

 

위 소스코드에서 제가 사용한 정규표현식 패턴들만 모아보면 다음과 같습니다. 

var idPattern = /[a-zA-Z0-9_-]{5,20}/;
var pwPattern = /[a-zA-Z0-9~!@#$%^&*()_+|<>?:{}]{8,16}/;
var namePattern = /[a-zA-Z가-힣]/;
var yearPattern = /[0-9]{4}/;
var datePattern = /\d{1,2}/;
var emailPattern = /[a-z0-9]{2,}@[a-z0-9-]{2,}\.[a-z0-9]{2,}/;
var isPhoneNum = /([01]{2,})([01679]{1,})([0-9]{3,4})([0-9]{4})/;

저는 이 회원가입 페이지를 만들면서 정규식이라는 걸 거의 처음 접했기 때문에

제가 이해할 수 있는 수준으로 아주아주 간단하게 작성했습니다. 사실 이게 맞는지 잘 모르겠지만 테스트 해봤을 때 작동은 잘 되더라구요. 

따로 설명을 안해도 읽어보시면 이해가 될 정도이긴 하지만 그래도 몇 가지 설명하자면, 

pwPattern은 [ ] 안에 있는 문자(영어 소문자(a-z), 대문자(A-Z), 숫자(0-9), 그외특수문자들)가 아닌 문자를 포함하거나 8자미만/16자초과하는 문자열을 테스트하면 false가 반환되는 식입니다.

namePattern에서 '가-힣'은 완성형 한글(자음+모음/자음+모음+자음)의 첫글자-마지막글자를 나타냅니다. 그래서 'ㄱ', 'ㅏ'와 같은 단독 한글 자음/모음을 입력하고 테스트했을 때 false가 뜹니다.

datePattern의 '\d'는 숫자를 의미합니다. [0-9]와 같다고 보시면 됩니다.

emailPattern의 '[a-z0-9]{2,}@' 부분은 @앞에 오는 문자열이 영어소문자나 숫자로 이루어져있고, 2글자 이상이라는 의미입니다. 

생활코딩의 javascript강의 정규표현식 파트와 정규식 시각화 사이트(https://regexper.com/), 정규식 테스트 사이트(https://regexr.com/)를 참고했습니다. 

 

다음은 함수 중에서 checkPw() 함수만 따로 가져왔습니다. 

function checkPw() {
    var pwPattern = /[a-zA-Z0-9~!@#$%^&*()_+|<>?:{}]{8,16}/;
    if(pw1.value === "") {
        error[1].innerHTML = "필수 정보입니다.";
        pwMsg.style.display = "block";
        pwMsgArea.style.paddingRight = "40px";
        pwImg1.src = "m_icon_pass.png";
        error[1].style.display = "block";
    } else if(!pwPattern.test(pw1.value)) {
        error[1].innerHTML = "8~16자 영문 대 소문자, 숫자, 특수문자를 사용하세요.";
        pwMsg.innerHTML = "사용불가";
        pwMsgArea.style.paddingRight = "93px";
        error[1].style.display = "block";
        pwMsg.style.color = "red";
        pwMsg.style.display = "block";
        pwImg1.src = "m_icon_not_use.png";
    } else {
        error[1].style.display = "none";
        pwMsg.innerHTML = "안전";
        pwMsgArea.style.paddingRight = "93px";
        pwMsg.style.color = "#03c75a";
        pwMsg.style.display = "block";
        pwImg1.src = "m_icon_safe.png";
    }
}

checkPw() 함수는

네이버 실제 회원가입 페이지에 있는 자물쇠 이미지를 다운받아서 썼습니다. (근데 며칠 후에 네이버가 자물쇠 이미지를 바꿨어요) 입력값에 따라 이미지가 바껴요. 

비밀번호를 입력하면 입력폼 오른쪽에 사용불가 혹은 안전 이라는 메세지가 뜨는데, 이 때 메세지가 나타날 공간을 확보해주기 위해서 입력폼 오른쪽 padding값을 93px로 변경해줍니다. (pwMsgArea.style.paddingRight = "93px";) (원래 padding-right 값은 40px입니다.)

그리고 "사용불가" / "안전"이라는 메세지를 html 해당태그 안에 삽입하고, 컬러를 설정한 뒤(레드/그린),

display: none -> diplay: block으로 바꿔 메세지가 나타나게 합니다. 

비밀번호 입력폼 아래쪽에 나타나는 에러메세지는 다른 폼들과 똑같이 처리했습니다. (에러메세지를 태그 안에 삽입하고, display:block;으로 스타일을 바꿔 메세지가 보이게 했습니다. 제대로 입력했을 때는 display 속성을 다시 none으로 바꿔줍니다. )

 

이 외에 다른 함수들 설명은 생략하고 넘어가겠습니다. 

코드에 개선할 부분이 있으면 알려주시고 이해되지 않는 부분이 있다면 질문해주세요~

 

 

HTML, CSS, Javascript 연습용으로 네이버 회원가입 페이지를 따라 만들어봤습니다. 

회원가입 페이지 레이아웃을 html과 css로 비슷하게 따라하고,

Javascript로 사용자가 데이터를 입력했는지 확인하기 + 사용자가 입력한 데이터가 조건에 맞는지 확인하기(유효성 검사) -> (데이터가 입력되지 않았거나 조건에 맞지 않다면) 에러메세지 출력하기

이렇게 간단한 기능을 구현해봤습니다. 

 

자바스크립트로 기능구현한 부분은 따로 글을 작성할 예정이고

이 글에서는 결과물과 html, css 소스코드를 간단한 설명과 함께 보여드리려고 합니다. 

 

결과물부터 보여드릴게요

 

결과물

네이버 실제 회원가입 페이지

클릭하면 크게 볼 수 있어요

 

따라 만든 페이지

클릭하면 크게 볼 수 있어요

(휴대폰 인증, Footer 부분 등은 생략하고 만들었습니다. )

하필이면 제가 이거 만들고 나서 며칠 안돼서 네이버에서 회원가입페이지 로고랑 자물쇠 모양이랑 색상이랑 이것저것 수정했더라구요. 제가 만든 건 약간 구버전이 되어버렸네요ㅜ

* 글 하단에 html, css 전체 소스코드를 올려놨습니다. html, css, js 파일은 github에 전부 올려놨어요. github링크도 하단에 같이 첨부해놓았습니다.

 

1. HTML 코드 

많은 기능을 구현할 것이 아니기 때문에 실제 네이버 회원가입페이지 html 소스코드에 비해

간단하게 작성했습니다.

전체 페이지 구성

<div id="header"> 
	<div id="wrapper">
    	<div id="content">
        </div>
    </div>
</div>

먼저 전체 페이지 구성은 div태그를 이용해 header, wrapper, wrapper 안의 content 이렇게 세 부분으로 작성했습니다.

content 안에 아이디, 비밀번호, 이름 등 입력 폼들을 각각 <div> 태그로 묶어서 넣어주고, 버튼도 content 안에 포함시켰습니다. 

 

id 입력폼 부분

<div id="content">
	<div>
		<h3>
			<label for="id">아이디</label>
		</h3>
		<span class="box int_id">
			<input type="text" id="id" class="int" maxlength="20">
			<span class="step_url">@naver.com</span>
		</span>
		<span class="error_next_box"></span>
	</div>
</div>

content 안에 작성되어있는 id 입력폼 부분 소스코드입니다.

 

코드를 보시면 "아이디"라는 입력폼의 타이틀을 <label>, <h3> 태그로 감싸 줬는데

h3 태그는 의미상 구분 단위로 쓰기 위해서(입력폼의 제목이라는 뜻) + h3 태그를 모아서 한 번에 스타일을 적용하기 위해서 사용했고,

label 태그"아이디"라는 글자를 클릭하면 자동으로 아이디 입력폼에 포커스가 되는 기능을 주기 위해서 사용했습니다.

지금 보니까 h3 태그는 생략해도 큰 문제가 없을 것 같네요..?

*label태그에는 <label>태그를 클릭하면, 동일한 아이디값을 가진 <input>태그에 자동으로 포커스가 되는 기능이 포함되어 있습니다. 저는 이 기능을 사용하기 위해서 <input>태그의 아이디값과 <label>태그의 아이디값을 "id"로 동일하게 설정했습니다.

 

입력폼은 span태그로 묶어주고, 그 안에 텍스트를 입력받을 <input type="text"> 태그와 메일주소("@naver.com")를 표시해줄 또다른 sapn태그를 넣어줬습니다. 

입력폼 전체를 묶은 span 태그에는 box라는 class값을 설정해주고, input 태그에도 int라는 class값을 설정해줬습니다. 아이디 입력폼 뿐 아니라 비밀번호, 이름 등 다른 입력폼에도 같은 클래스 값을 설정해줬습니다. 아이디, 비밀번호, 이름 등 모든 입력폼에 공통적인 스타일을 적용할 것이기 때문입니다. (단, 아이디 입력폼은 다른 입력폼들과 오른쪽 padding 값이 다르기 때문에 나중에 css코드에서 padding-right 스타일만 따로 적용해주기 위해서 int_id라는 class값도 추가로 설정해줬습니다. )

input 태그의 max-length 속성은 입력할 수 있는 글자수를 20자로 제한하는 속성입니다. 네이버에서 20자로 제한하고 있길래 그냥 저도 그렇게 했어요

<span class="error_next_box"> 태그아이디값을 잘못 입력했을 때, 입력 폼 아래에 에러 메세지가 표시될 부분을 나타내기 위해 사용했습니다. 

각 태그들의 id명, class명은 대부분 실제 네이버 회원가입 페이지에서 그대로 가져왔습니다. (error_next_box, int_id 등)

 

비밀번호, 이름, 이메일, 휴대전화 등 나머지 입력폼들은 전부 id 입력폼과 비슷한 구성으로 작성했기 때문에 생략하고 넘어가겠습니다. 

태그 구성이 조금 다른 생년월일 입력폼만 추가로 볼게요

 

생년월일 입력폼

		<!-- BIRTH -->
                <div>
                    <h3><label for="yy">생년월일</label></h3>

                    <div id="bir_wrap">
                    
                        <!-- BIRTH_YY -->
                        <div id="bir_yy">
                            <span class="box">
                                <input type="text" id="yy" class="int" maxlength="4" placeholder="년(4자)">
                            </span>
                        </div>

                        <!-- BIRTH_MM -->
                        <div id="bir_mm">
                            <span class="box">
                                <select id="mm">
                                    <option>월</option>
                                    <option value="01">1</option>
                                    <option value="02">2</option>
                                    <option value="03">3</option>
                                    <option value="04">4</option>
                                    <option value="05">5</option>
                                    <option value="06">6</option>
                                    <option value="07">7</option>
                                    <option value="08">8</option>
                                    <option value="09">9</option>                                    
                                    <option value="10">10</option>
                                    <option value="11">11</option>
                                    <option value="12">12</option>
                                </select>
                            </span>
                        </div>

                        <!-- BIRTH_DD -->
                        <div id="bir_dd">
                            <span class="box">
                                <input type="text" id="dd" class="int" maxlength="2" placeholder="일">
                            </span>
                        </div>

                    </div>
                    <span class="error_next_box"></span>    
                </div>

생년월일 입력폼 부분의 소스코드입니다. 

크게 보면 (입력폼 전체를 묶는)div태그 > bir_wrap > bir_yy, bir_mm, bir_dd 로 구성되어 있습니다.

bir_wrap은 bir_yy, bir_mm, bir_dd 세 요소를 감싸주는 목적으로, css로 세 요소를 배치할 때 필요하기 때문에 추가해줬습니다. 

이 외에는 아이디입력폼과 크게 다르지 않기 때문에 특별히 설명할 부분이 없네요

 

가입하기 버튼 

<!-- JOIN BTN-->
<div class="btn_area">
	<button type="button">
		<span>가입하기</span>
	</button>
 </div>

버튼 부분은 코드가 간단하죠?

css레이아웃 배치를 염두에 두고 버튼영역(btn_area) > 버튼(button) > 텍스트(span) 이렇게 구분해뒀습니다. 

버튼에는 딱히 기능을 추가할 생각이 없었고, 기능을 추가하더라도 어차피 html 소스 전체에 button태그가 하나밖에 없기 때문에 그냥 태그명으로 접근이 가능하니 굳이 id값을 따로 설정하지는 않았습니다. 

 

HTML 코드 작성은 크게 어려운 부분은 아니니까 여기서 마무리할게요!

 

2. CSS 코드

전체적인 틀 잡기

/* 레이아웃 틀 */
html {
    height: 100%;
}

body {
    margin: 0;
    height: 100%;
    background: #f5f6f7;
    font-family: Dotum,'돋움',Helvetica,sans-serif;
}
#logo {
    width: 240px;
    height: 44px;
    cursor: pointer;
}

#header {
    padding-top: 62px;
    padding-bottom: 20px;
    text-align: center;
}
#wrapper {
    position: relative;
    height: 100%;
}

#content {
    position: absolute;
    left: 50%;
    transform: translate(-50%);
    width: 460px;
}

1) body, wrapper의 height값을 100%로 설정해줍니다.

height를 %값으로 주게 되면, 상위태그(부모태그)의 높이를 기준으로 높이가 설정됩니다.

(예를 들어 body { height : 1000px } #wrapper { height : 50% } 라면,

wrapper의 height 값은 부모태그인 body태그의 height값 기준 50%인 500px로 설정이 됩니다.)

그래서 height를 %로 설정할 때는 부모태그의 height를 먼저 확인하는 것이 좋습니다.

저는 처음에 wrapper만 height: 100%;로 설정했더니

wrapper의 실제 높이가 0px이 되더라구요(body, html태그의 높이가 설정되어있지 않았기 때문). 

wrapper가 content를 감싸줘야 하는데 따로 노는 상황이 되어서 시행착오 끝에 

html과 body태그에도 height를 설정해줬더니 제대로 구현이 되었습니다.

 

2) header에는 text-align: center; 속성을 설정해서 네이버 로고(#logo)가 수평으로 가운데 정렬되도록 해줬습니다. 

로고는 이미지 태그인데 text-align으로도 수평 정렬이 되길래 그냥 이렇게 해줬어요.

header의 위 아래 padding값은 고정된 값이기 때문에 px로 설정했고, (px값은 실제 네이버 페이지의 css코드에서 그대로 가져왔어요)

네이버 로고 위에 마우스 커서를 올리면 커서 모양이 포인터로 바뀌는 디테일을 구현하기 위해 cursor: pointer; 속성도 넣어줬습니다. 

 

3) wrapper의 position을 relative로 설정해주고, content는 position: absolute;로 설정했습니다.

이렇게 설정하면 wrapper를 기준점으로 삼아서 content를 배치할 수 있습니다.

wrapper 너비를 기준으로 왼쪽에서 50%만큼 떨어지게 한 다음(left: 50%), transform 속성을 이용해서 content의 너비에서 50%만큼 다시 왼쪽으로 가게 했습니다(transform: translate(-50%)). 이렇게 하면 content를 wrapper안에서 수평으로 중앙정렬 할 수 있습니다.

너비가 고정되어있지 않은 컨텐츠를 중앙정렬할 때 사용하기 좋은 방법인 것 같습니다.

 

입력 폼 스타일

h3 {
    margin: 19px 0 8px;
    font-size: 14px;
    font-weight: 700;
}


.box {
    display: block;
    width: 100%;
    height: 51px;
    border: solid 1px #dadada;
    padding: 10px 14px 10px 14px;
    box-sizing: border-box;
    background: #fff;
    position: relative;
}

.int {
    display: block;
    position: relative;
    width: 100%;
    height: 29px;
    border: none;
    background: #fff;
    font-size: 15px;
}

입력 폼 스타일에서 가장 기본이 되는 세 부분입니다!

그림을 좀 못그렸는데 그래도 보면 대충 이해는 가시죠? 아닌가요?ㅎㅎ..

입력폼 부분의 레이아웃은 이렇게 구성되어있습니다. 

 

1) h3은 위로 19px, 아래로 8px 마진을 줘서 자리를 잡아줬구요.

2) box와 int의 높이(height)는 각각 51px, 29px로 고정해주고 

3) box와 int의 너비(width)는 100%로 설정했는데, 부모태그인 content의 너비(460px) 기준 100%가 되기 때문에 실제 값은 각각 460px이 되겠습니다.

 

4) 그리고 box에 padding 값을 줘서 int가 box로부터 위아래로 10px, 양쪽으로 14px만큼 떨어지게(작아지게) 했어요.

 

단, 아이디 입력폼의 경우 다른 폼과 다르게 오른쪽에 "@naver.com"이 들어갈 공간이 필요하기 때문에

.box.int_id {
    padding-right: 110px;
}

이렇게 오른쪽 padding 값만 따로 110px로 설정해줬어요.

 

.box.int_pass {
    padding-right: 40px;
}

.box.int_pass_check {
    padding-right: 40px;
}

오른쪽에 자물쇠 이미지가 들어가야하는 비밀번호 입력 폼과 비밀번호 재확인 입력 폼도 padding-right를 따로 설정해줬습니다. 

 

5) box의 box-sizing속성box의 크기를 어떤 기준으로 계산할지 정하는 속성입니다.

테두리를 포함해서 계산할지(border-box), 테두리를 제외하고 계산할지(content-box) 등등..

기본값은 content-box입니다. content-box로 설정되어 있는 경우 border가 없다면 괜찮지만 border가 있다면 border size만큼인 1px~2px정도가  내가 정한 box 크기에 +돼서 전체적인 레이아웃이 아주 약간씩 어긋나게 돼요.

그러니 애초에 크기를 정할 때 border크기를 포함시켜서 정하거나(460px + border 1px = 461px로 설정),

아니면 border-box로 설정해주는 것이 좋습니다. 

 

6) 아이디 입력 폼의 "@naver.com"

.step_url {
    /*@naver.com*/
    position: absolute;
    top: 16px;
    right: 13px;
    font-size: 15px;
    color: #8e8e8e;
}

 

 

position: absolute, top: 16px, right: 13px -> position이 static이 아닌 부모태그인 box를 기준으로 위에서 16px, 오른쪽에서 13px 떨어진 위치에 배치했습니다.

 

7) 비밀번호 입력 폼의 자물쇠 이미지

.pswdImg {
    width: 18px;
    height: 20px;
    display: inline-block;
    position: absolute;
    top: 50%;
    right: 16px;
    margin-top: -10px;
    cursor: pointer;
}

마찬가지로 postion: absolute, top: 50%, right: 16px -> position이 static이 아닌 부모태그 box를 기준으로 위에서 50%, 오른쪽에서 16px만큼 떨어지게 배치했어요.

그리고 margin-top: -10px로 설정해서 위로 다시 10px만큼 올라가게 했어요.

이렇게 한 이유는 세로로 중앙 정렬을 하기 위해서입니다. box높이의 50%만큼 아래로 내려갔다가 -> 자물쇠이미지 높이의 절반(20px/2 = 10px)만큼 다시 위로 올라가면 정확히 중앙에 오게 되니까요.

wrapper에서 content를 가로로 중앙정렬했던 것과 같은 원리입니다. 

 

생년월일 입력 폼 배치

#bir_wrap {
    display: table;
    width: 100%;
}

#bir_yy {
    display: table-cell;
    width: 147px;
}

#bir_mm {
    display: table-cell;
    width: 147px;
    vertical-align: middle;
}

#bir_dd {
    display: table-cell;
    width: 147px;
}

#bir_mm, #bir_dd {
    padding-left: 10px;
}

생년월일 입력 폼은 table 속성을 이용해서 배치해줬어요.

*참고 : table 속성은 원래 표를 만들 때 사용하는 속성인데요. grid 속성과 비슷하지만, grid가 레이아웃 배치하라고 나온 속성이라면 table 속성은 말그대로 표를 만들라고 나온 속성이기 때문에 레이아웃 배치용으로는 grid를 사용하는 게 바람직하다고 해요. 그런데 grid속성은 비교적 최근에 나온 속성이라 호환되지 않는 브라우저(특히 IE)가 있는 반면, table 속성은 좀 더 많은 브라우저에서 지원이 되기 때문에 table 속성을 이용해서 레이아웃 배치 작업을 하는 경우가 꽤 있는 것 같더라구요. 실제로 네이버 회원가입 페이지의 생년월일 입력 폼 부분은 table 속성을 이용해 배치가 되어있었습니다. 그래서 저도 table을 이용해서 한 번 배치를 해봤는데요.

bir_yy, bir_mm, bir_dd를 각각 table을 구성하는 cell로 설정하고, 세 요소를 감싸고 있는 bir_wrapdisplay속성table로 설정했습니다. 

bir_yy, bir_mm, bir_dd(테이블의 각 셀들)의 너비는 각각 147px로 설정하고,

그 중 bir_mm, bir_dd는 padding-left: 10px로 설정해서 셀들 사이에 10px만큼 간격이 생기게 했어요.

 

bir_mm에 vertical-align: middle;으로 설정한 이유는 나머지 두 셀과 높이를 맞춰주기 위해서 입니다.

bir_yy, bir_dd가 box안에 int(height:31px)가 들어가 있는 구조라면 

bir_mm은 box안에 select(height:29px)가 들어가 있는 구조라 나머지 두 셀과 박스 높이가 약간 달랐어요.

 

에러메세지 배치

이렇게 에러메세지가 뜨도록

.error_next_box {
    margin-top: 9px;
    font-size: 12px;
    color: red;    
    display: none;
}

#alertTxt {
    position: absolute;
    top: 19px;
    right: 38px;
    font-size: 12px;
    color: red;
    display: none;
}

위와 같이 css 스타일을 적용해줬습니다. 

error_next_box가 "8~16자 영문 대 소문자, 숫자, 특수문자를 사용하세요."와 같은 에러메세지가 출력될 부분이고,

alertTxt는 비밀번호 입력폼에서 "사용불가"와 같은 경고메세지를 출력할 부분입니다.

error_next_box는 margin 값을 이용해서 위에있는 box와 약간 떨어뜨려주면서 자리를 잡아줬습니다. 

alertTxt는 "@naver.com"이나 자물쇠 이미지처럼 position: absoulte 속성을 이용해서 자리를 잡아줬어요. 

둘 다 display 속성none으로 해서 보이지 않게 설정해두었습니다. 

이벤트가 발생하면(사용자가 잘못된 데이터를 입력하면) display: block으로 바뀌면서 에러메세지가 나타날거에요. (이 부분은 자바스크립트로 구현)

 

버튼 스타일

마지막으로 별 거 없는 버튼 스타일입니다.

.btn_area {
    margin: 30px 0 91px;
}

#btnJoin {
    width: 100%;
    padding: 21px 0 17px;

    border: 0;
    cursor: pointer;
    color: #fff;
    background-color: #08a600;
    font-size: 20px;
    font-weight: 400;
    font-family: Dotum,'돋움',Helvetica,sans-serif;
}

버튼 영역은 위 30px(위에 있는 입력폼과 간격 띄우기), 아래 91px(원래 버튼 아래에 들어가는 footer영역 높이 정도) 마진을 줬어요.

버튼 크기는 width와 padding으로 잡아줬어요. 버튼 너비(width)를 100%로 설정해서 부모 태그인 content의 너비인 460px로 만들고, 위 아래 padding 값을 줘서 버튼 크기를 위아래로 늘려줬어요. 

그리고 버튼 태그에 기본적으로 들어가있는 border(테두리)를 없애주고, 실제로 동작하는 버튼은 아니지만 그래도 커서는 포인터로 설정했어요. 

그 외에 색상이나 폰트는 그냥 네이버 css를 복붙했습니다. 

 

이렇게 하면 레이아웃은 완성입니다! 설명이 생략된 부분은 아래에 있는 html, css 전체 소스코드나 github에 올려둔 파일을 참고해주시면 됩니다.

 


Github 링크

https://github.com/from97/naver_join

 

 


HTML, CSS 전체 소스코드

 

HTML 소스코드

더보기
<!DOCTYPE html>
<html lnag="ko">
    <head>
        <meta charset="UTF-8">
        <title>네이버 : 회원가입</title>
        <link rel="stylesheet" href="new_main.css">
        
    </head>
    <body>
        <!-- header -->
        <div id="header">
            <a href="https://nid.naver.com/user2/V2Join.nhn?m=agree#agreeBottom" target="_blank" title="네이버 회원가입 페이지 보러가기"><img src="NAVER_CI_Green.png" id="logo"></a>
        </div>


        <!-- wrapper -->
        <div id="wrapper">

            <!-- content-->
            <div id="content">

                <!-- ID -->
                <div>
                    <h3 class="join_title">
                        <label for="id">아이디</label>
                    </h3>
                    <span class="box int_id">
                        <input type="text" id="id" class="int" maxlength="20">
                        <span class="step_url">@naver.com</span>
                    </span>
                    <span class="error_next_box"></span>
                </div>

                <!-- PW1 -->
                <div>
                    <h3 class="join_title"><label for="pswd1">비밀번호</label></h3>
                    <span class="box int_pass">
                        <input type="text" id="pswd1" class="int" maxlength="20">
                        <span id="alertTxt">사용불가</span>
                        <img src="m_icon_pass.png" id="pswd1_img1" class="pswdImg">
                    </span>
                    <span class="error_next_box"></span>
                </div>

                <!-- PW2 -->
                <div>
                    <h3 class="join_title"><label for="pswd2">비밀번호 재확인</label></h3>
                    <span class="box int_pass_check">
                        <input type="text" id="pswd2" class="int" maxlength="20">
                        <img src="m_icon_check_disable.png" id="pswd2_img1" class="pswdImg">
                    </span>
                    <span class="error_next_box"></span>
                </div>

                <!-- NAME -->
                <div>
                    <h3 class="join_title"><label for="name">이름</label></h3>
                    <span class="box int_name">
                        <input type="text" id="name" class="int" maxlength="20">
                    </span>
                    <span class="error_next_box"></span>
                </div>

                <!-- BIRTH -->
                <div>
                    <h3 class="join_title"><label for="yy">생년월일</label></h3>

                    <div id="bir_wrap">
                        <!-- BIRTH_YY -->
                        <div id="bir_yy">
                            <span class="box">
                                <input type="text" id="yy" class="int" maxlength="4" placeholder="년(4자)">
                            </span>
                        </div>

                        <!-- BIRTH_MM -->
                        <div id="bir_mm">
                            <span class="box">
                                <select id="mm" class="sel">
                                    <option>월</option>
                                    <option value="01">1</option>
                                    <option value="02">2</option>
                                    <option value="03">3</option>
                                    <option value="04">4</option>
                                    <option value="05">5</option>
                                    <option value="06">6</option>
                                    <option value="07">7</option>
                                    <option value="08">8</option>
                                    <option value="09">9</option>                                    
                                    <option value="10">10</option>
                                    <option value="11">11</option>
                                    <option value="12">12</option>
                                </select>
                            </span>
                        </div>

                        <!-- BIRTH_DD -->
                        <div id="bir_dd">
                            <span class="box">
                                <input type="text" id="dd" class="int" maxlength="2" placeholder="일">
                            </span>
                        </div>

                    </div>
                    <span class="error_next_box"></span>    
                </div>

                <!-- GENDER -->
                <div>
                    <h3 class="join_title"><label for="gender">성별</label></h3>
                    <span class="box gender_code">
                        <select id="gender" class="sel">
                            <option>성별</option>
                            <option value="M">남자</option>
                            <option value="F">여자</option>
                        </select>                            
                    </span>
                    <span class="error_next_box">필수 정보입니다.</span>
                </div>

                <!-- EMAIL -->
                <div>
                    <h3 class="join_title"><label for="email">본인확인 이메일<span class="optional">(선택)</span></label></h3>
                    <span class="box int_email">
                        <input type="text" id="email" class="int" maxlength="100" placeholder="선택입력">
                    </span>
                    <span class="error_next_box">이메일 주소를 다시 확인해주세요.</span>    
                </div>

                <!-- MOBILE -->
                <div>
                    <h3 class="join_title"><label for="phoneNo">휴대전화</label></h3>
                    <span class="box int_mobile">
                        <input type="tel" id="mobile" class="int" maxlength="16" placeholder="전화번호 입력">
                    </span>
                    <span class="error_next_box"></span>    
                </div>


                <!-- JOIN BTN-->
                <div class="btn_area">
                    <button type="button" id="btnJoin">
                        <span>가입하기</span>
                    </button>
                </div>

                

            </div> 
            <!-- content-->

        </div> 
        <!-- wrapper -->
    <script src="main.js"></script>
    </body>
</html>

 

CSS 소스코드

더보기
/* 레이아웃 틀 */
html {
    height: 100%;
}

body {
    margin: 0;
    height: 100%;
    background: #f5f6f7;
    font-family: Dotum,'돋움',Helvetica,sans-serif;
}
#logo {
    width: 240px;
    height: 44px;
    cursor: pointer;
}

#header {
    padding-top: 62px;
    padding-bottom: 20px;
    text-align: center;
}
#wrapper {
    position: relative;
    height: 100%;
}

#content {
    position: absolute;
    left: 50%;
    transform: translate(-50%);
    width: 460px;
}




/* 입력폼 */


h3 {
    margin: 19px 0 8px;
    font-size: 14px;
    font-weight: 700;
}


.box {
    display: block;
    width: 100%;
    height: 51px;
    border: solid 1px #dadada;
    padding: 10px 14px 10px 14px;
    box-sizing: border-box;
    background: #fff;
    position: relative;
}

.int {
    display: block;
    position: relative;
    width: 100%;
    height: 29px;
    border: none;
    background: #fff;
    font-size: 15px;
}

input {
    font-family: Dotum,'돋움',Helvetica,sans-serif;    
}

.box.int_id {
    padding-right: 110px;
}

.box.int_pass {
    padding-right: 40px;
}

.box.int_pass_check {
    padding-right: 40px;
}

.step_url {
    /*@naver.com*/
    position: absolute;
    top: 16px;
    right: 13px;
    font-size: 15px;
    color: #8e8e8e;
}

.pswdImg {
    width: 18px;
    height: 20px;
    display: inline-block;
    position: absolute;
    top: 50%;
    right: 16px;
    margin-top: -10px;
    cursor: pointer;
}

#bir_wrap {
    display: table;
    width: 100%;
}

#bir_yy {
    display: table-cell;
    width: 147px;
    
}

#bir_mm {
    display: table-cell;
    width: 147px;
    vertical-align: middle;
}

#bir_dd {
    display: table-cell;
    width: 147px;
}

#bir_mm, #bir_dd {
    padding-left: 10px;
}

select {
    width: 100%;
    height: 29px;
    font-size: 15px;
    background: #fff url(https://static.nid.naver.com/images/join/pc/sel_arr_2x.gif) 100% 50% no-repeat;
    background-size: 20px 8px;
    -webkit-appearance: none;
    display: inline-block;
    text-align: start;
    border: none;
    cursor: default;
    font-family: Dotum,'돋움',Helvetica,sans-serif;
}

/* 에러메세지 */

.error_next_box {
    margin-top: 9px;
    font-size: 12px;
    color: red;    
    display: none;
}

#alertTxt {
    position: absolute;
    top: 19px;
    right: 38px;
    font-size: 12px;
    color: red;
    display: none;
}

/* 버튼 */

.btn_area {
    margin: 30px 0 91px;
}

#btnJoin {
    width: 100%;
    padding: 21px 0 17px;
    border: 0;
    cursor: pointer;
    color: #fff;
    background-color: #08a600;
    font-size: 20px;
    font-weight: 400;
    font-family: Dotum,'돋움',Helvetica,sans-serif;
}

+ Recent posts