📝

002. 설문페이지 만들기

1. 화면 레이아웃 구성하기

1.1 우리가 만들 화면

💡
실제 테스트는 10개의 문항으로 설문지가 구성되어 있지만, 프론트엔드 강의에서는 3개의 문항으로 실습을 진행합니다. 이후에 진행될 백엔드 파트에서는 모든 문항을 불러오는 실습이 진행됩니다.
 
notion imagenotion image
notion imagenotion image
notion imagenotion image
 

1.2 화면 나누기

화면은 아래와 같이 나누어집니다. 모든 문항들을 설문지페이지에 뿌려주고 실제 화면에는 한 문항만을 보여주기 위해 각 문항들은 전체화면으로 설정해 주어야 합니다.
notion imagenotion image
 

2. HTML

2.1 기본 구조

설문지페이지를 만들기 위해 form.html 파일을 생성합니다. 기본 구조는 메인페이지의 index.html 과 동일합니다.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>나의 개발 유형찾기</title> </head> <body> </body> </html>
 

2.2 태그 입력하기

section 과 콘텐츠들을 감쌀 div 하나, 사용자가 설문 데이터를 전송하기 위한 form , 그리고 그 안에 문항들을 넣을 div 3개로 구성됩니다.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>나의 개발 유형찾기</title> </head> <body> <section> <div> <form> <div></div> <div></div> <div></div> </form> </div> </section> </body> </html>
 
 

2.3 id, class 입력하기

CSS 적용을 위해 id와 class명을 각 태그에 입력합니다.
<section id="survey"> <div class="wrapper"> <form id="form"> <div class="test"></div> <div class="test"></div> <div class="test"></div> </form> </div> </section>
 

2.4 콘텐츠 넣기

2.4.1 문제

문항 번호와 문제는 h3 태그를 사용하여 <div class="question_container"></div> 안에 넣습니다. class명은 각각 numberquestion 으로 입력합니다.
<div class="question_container"> <h3 class="number">1/3</h3> <h3 class="question">당신이 가장 재밌었던 수업은?</h3> </div>
 

2.4.2 설문 항목

설문 항목은 <div class="answer"></div> 에 들어갑니다. 설문지에서 사용자는 한 개의 답안을 선택해야 합니다. 그렇게 때문에 우리는 input 태그의 타입 속성인 라디오 버튼(radio button)을 사용할 것입니다. 또한 항목 설명을 위해 label 태그를 사용하며, input 태그와 label 태그는 div 태그로 감싸줍니다.
<div class="answer"> <div> <input type="radio" name="answer_1" /> <label>게임 개발(유니티, 언리얼, Pygame)</label> </div> <div> <input type="radio" name="answer_1" /> <label>백엔드 또는 인프라(C계열-닷넷, JAVA-Spring, Python-Django, Network, Ubuntu, CentOS, 부하분산, etc)</label> </div> <div> <input type="radio" name="answer_1" /> <label>정보보안(해킹과 방어)</label> </div> <div> <input type="radio" name="answer_1" /> <label>프론트엔드(HTML, CSS, Javascript, etc)</label> </div> <div> <input type="radio" name="answer_1" /> <label>데이터 분석과 인공지능(전처리, 분석, 시각화, 머신러닝, 딥러닝)</label> </div> <div> <input type="radio" name="answer_1" /> <label>이 길은 내 길이 아닌 것 같다... 고로 재미있는지 모르겠다. </label> </div> </div>
 
input 태그의 name 속성은 "제출" 버튼을 클릭했을 때 서버로 전달되는 이름을 지정할 수 있는 속성입니다. 여기서 문항(문제)별 name을 동일하게 지정해 주어야 같은 그룹으로 설정할 수 있습니다.
 
💡
input 태그의 id 속성label 태그의 for 속성을 동일한 값으로 입력하면 텍스트(항목)를 클릭하여도 라디오 버튼을 선택할 수 있습니다. input 태그의 id, value 등의 값과 label 태그의 for 속성값을 설정하는 작업은 백엔드 파트에서 진행됩니다.
 
2.4.3 버튼
다음 문항으로 넘어가기 위한 "다음" 버튼을 만들어 줍니다. <div class="btn_wrap"></div> 안에 button 태그를 만들어 주고 class명은 next_btn으로 입력합니다.
<div class="btn_wrap"> <button class="next_btn">다 음</button> </div>
 

2.4.3 그 외 문항

아래는 2번 문항입니다. 기본 구조는 앞서 설명한 구조와 같기 때문에 그대로 복사하여 붙여넣기하겠습니다. 안에 들어갈 문제와 설문 항목 등의 내용은 아래와 같이 변경합니다.
1번 문항과 2번 문항은 별도의 그룹이기 때문에 input 태그의 name 속성은 answer_2로 입력합니다. 또한 2번 문항부터는 이전으로 돌아갈 수 있도록 "이전" 버튼을 추가합니다.
<div class="question_container"> <div class="number">2/3</div> <div class="question">여러분의 서비스가 성공하였다면, 서비스를 성공시킨 요소는?</div> </div> <div class="answer"> <div> <input type="radio" name="answer_2" /> <label>데이터 분석을 통한 효과적인 개인 맞춤형 서비스</label> </div> <div> <input type="radio" name="answer_2" /> <label>화려하거나 품격있는 디자인, 흥미로운 콘텐츠 제작</label> </div> <div> <input type="radio" name="answer_2" /> <label>게임적인(Gamification, 게이미피케이션) 요소 도입</label> </div> <div> <input type="radio" name="answer_2" /> <label>안정적인 서비스 운용</label> </div> <div> <input type="radio" name="answer_2" /> <label>믿을 수 있는 정보 관리</label> </div> </div> <div class="btn_wrap"> <button class="prev_btn">이 전</button> <button class="next_btn">다 음</button> </div>
 
아래는 3번 문항입니다. 앞에서 해본 것과 마찬가지로 input 태그의 name 속성과 문제 및 설문 항목 등의 내용 외에는 구조가 동일합니다.
하지만 3번 문항은 마지막 문항이기 때문에 "다음" 버튼이 아닌 "제출" 버튼이 필요합니다. 여기서 제출 버튼은 button 태그가 아닌 input 태그를 사용합니다. type 속성을 submit으로 입력하고 value 속성에는 "제출"이라고 입력하여 제출 버튼을 만들어 줍니다. class 명은 submit_btn이라고 하겠습니다.
<div class="question_container"> <div class="number">3/3</div> <div class="question">당신은 친구의 집들이에 가려고 합니다. 당신이 선택한 선물은?</div> </div> <div class="answer"> <div> <input type="radio" name="answer_3" /> <label>집 꾸미기에 도움이 되는 예쁜 인테리어 소품을 골라볼까?</label> </div> <div> <input type="radio" name="answer_3" /> <label>실용성이 갑이지! 화장지랑 수건 세트랑...</label> </div> <div> <input type="radio" name="answer_3" /> <label>음... 우선 센스있는 집들이 선물 먼저 검색해봐야지!</label> </div> <div> <input type="radio" name="answer_3" /> <label>집들이 가서 뭐하겠어! 보드 게임이나 사 가자!</label> </div> <div> <input type="radio" name="answer_3" /> <label>역시 집은 안전이 최고!! 현관문 보안장치 사줘야겠다.</label> </div> </div> <div class="btn_wrap"> <button class="prev_btn">이 전</button> <input type="submit" value="제 출" class="submit_btn"/> </div>
 
 

2.5 실행화면

notion imagenotion image
 

3. CSS 적용하기

3.1 CSS 파일 만들기

설문지페이지를 꾸며주기 위한 form.css 파일을 css 폴더 내에 만듭니다. 앞서 만들었던 reset.cssform.css 파일을 form.html 에 아래와 같이 적용시킵니다.
<link rel="stylesheet" type="text/css" href="css/reset.css"> <link rel="stylesheet" type="text/css" href="css/form.css">
 

3.2 스타일 적용하기

3.2.1 기본 설정

메인페이지에서 기본 스타일 설정을 했던 것과 같이 @font-face 를 통해 폰트를 적용합니다.
@font-face { font-family: "GmarketSansBold"; src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2001@1.1/GmarketSansBold.woff") format('woff'); font-weight: normal; font-style: normal; } @font-face { font-family: "NEXON Lv1 Gothic OTF"; src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_20-04@2.1/NEXON Lv1 Gothic OTF.woff") format("woff"); font-weight: normal; font-style: normal; }
 
설문페이지의 배경색, 글자색, 그리고 기본 폰트 설정을 아래와 같이 합니다.
body { background-color: #FAF1BE; color: #000; font-family: "NEXON Lv1 Gothic OTF"; }
 
모든 콘텐츠들을 담고 있는 #survey 의 스타일을 설정합니다. 우선 display 를 flex로 설정하고, justify-content 를 center로 설정하여 콘텐츠들을 중앙으로 배치합니다.
widthheight는 각각 100vw, 100vh로 뷰포트의 너비값, 높이값을 갖도록 합니다. 그리고 margin-top 을 통해 상단의 50px만큼의 여백을 만들어 줍니다.
#survey{ display: flex; justify-content: center; width: 100vw; height: 100vh; margin-top: 50px; }
 
문제와 각 답안들이 들어가는 .testmax-width 를 500px로 설정하여 내부 콘텐츠들의 너비가 600px을 넘지 못하도록 합니다. 또한 화면에는 한 개의 문제만 보여져야 하므로 height 는 뷰포트의 높이값을 갖도록 100vh로 설정합니다.
.test { max-width: 600px; height: 100vh; }
 

3.2.2 문제

display 를 flex로 설정하고 flex-direction 을 column으로 설정하여 콘텐츠를 수직으로 배치합니다. 그리고 align-items 를 center로 입력하여 문제를 중앙으로 배치합니다.
font-size 는 30px로 설정하고 행간을 주기 위해 line-height 를 1.3으로 입력합니다. 또한 하단의 설문 항목과의 간격을 주기 위해 padding-bottom 을 30px로 설정합니다.
.question_container { display: flex; flex-direction: column; align-items: center; font-size: 30px; line-height: 1.3; padding-bottom: 30px; }
 
문제 번호와 문제의 간격을 주기위해 padding-bottm 을 30px로 설정하여 .number 의 안쪽 아래에 여백을 줍니다.
.question_container .number { padding-bottom: 30px; }
 
문제의 폰트는 'GmarketSansBold' 로 지정하여 강조합니다.
.question_container .question { font-family: "GmarketSansBold"; }
 

3.2.3 설문 항목

설문 항목의 font-size 는 17px로 합니다. 또한 line-height 는 1.3으로, padding-bottom 은 30px로 동일하게 설정합니다.
.answer { font-size: 17px; line-height: 1.3; padding-bottom: 30px; }
 
각 항목들이 서로 간격을 갖도록 padding-bottom 을 15px로 설정합니다.
.answer div { padding-bottom: 15px; }
 
input 태그의 마우스 커서를 가져가면 커서의 모양이 손가락 모양이 되도록 cursor 를 pointer로 지정합니다.
.answer input { cursor: pointer; }
 

3.2.4 버튼

1번 문항의 버튼의 경우는 중앙으로 배치하고, 그 외 다른 문항들은 두 개의 버튼이 있기 때문에 양쪽으로 배치합니다.
중앙 정렬은 justify-content 를 center로 설정합니다. 두 개의 버튼을 배치할 때는 space-between으로 설정하여 요소 사이의 공간을 두게 합니다.
.btn_wrap { display: flex; justify-content: center; } .btn_sort{ justify-content: space-between; }
 
💡
justify-content 의 space-between은 요소들 사이에 동일한 간격을 두고 배치됩니다.
 
2개의 버튼을 감싸고 있던 div 의 class에 btn_sort를 추가로 입력합니다.
<div class="btn_wrap btn_sort"> <button class="prev_btn">이 전</button> <button class="next_btn">다 음</button> </div>
 
아래와 같이 "제출" 버튼을 포함한 모든 버튼들의 스타일을 설정합니다.
button, .submit_btn { width: 100px; height: 40px; padding: 5px; border-style: none; border-radius: 10px; font-family: "NEXON Lv1 Gothic OTF"; font-size: 20px; background-color: #7F47DD; color: #fff; cursor: pointer; }
 
버튼에 마우스 커서를 가져갔을 때의 스타일도 함께 설정합니다. 여기서는 글자색과 배경색을 변경시켜 줍니다.
button:hover, .submit_btn:hover{ color: #7F47DD; background-color: #fff; }
 

3.2.5 추가 작업

설문지페이지에서는 마우스 스크롤이 아닌 "이전", "다음" 버튼으로만 화면이 이동되도록 할 것입니다. bodyoverflow: hidden 을 추가로 입력하여 넘친 부분을 잘라내어 스크롤이 보이지 않도록 합니다.
body { background-color: #FAF1BE; color: #000; overflow: hidden; font-family: "NEXON Lv1 Gothic OTF"; }
 

3.3 실행화면

현재 실행화면입니다. 이제 "다음" 버튼을 클릭하면 다음 문항으로 넘어가도록 기능을 추가해야 합니다. 기능 추가는 Javascript를 활용하는 다음 파트인 "기능 추가하기"에서 진행하겠습니다.
notion imagenotion image
 

4. 기능 추가하기

4.1 js 파일 만들기

여러가지 기능을 추가하기 위해 js 폴더를 만들고 그 안에 form.js 파일을 생성합니다. 그리고 form.html 에 적용합니다. 아래 코드는 body 태그 내 제일 하단에 입력합니다.
<script type="text/javascript" src="js/form.js"></script>
 
그리고 jQuery를 사용할 것이기 때문에 form.html 에 아래와 같은 코드를 추가합니다. 이 코드는 <head></head> 안에 title 태그 위에 입력합니다.
<script src="https://code.jquery.com/jquery-3.5.1.js"></script>
💡
jQuery를 사용하는 방법은 대표적으로 API를 직접 다운로드 받아 사용하는 방법과 CDN을 이용하는 방법이 있습니다. 본 실습에서는 CDN을 이용하여 진행합니다.
 

4.2 슬라이드 효과

4.2.1 scrollDown()

"다음" 버튼을 클릭하였을 때 다음 문항으로 넘어가도록 기능을 추가하기 위해 scrollDown() 함수를 정의합니다.
function scrollDown() { }
 
우리는 animate 메소드의 scrollTop 속성을 사용하여 스크롤의 위치를 이동시켜 다음 문항으로 넘어가도록 할 것입니다.
스크롤은 .test 의 height만큼 이동되어야 하기 때문에 현재 .test 의 height 값을 변수 vheight 에 저장합니다.
function scrollDown() { const vheight = $('.test').height(); }
 
scrollTop 속성에는 스크롤이 이동될 값을 입력해야 하는데 각 문항의 스크롤 위치는 모두 다릅니다. 이때 각 문항의 스크롤 위치의 변화를 생각하면 스크롤 위치값을 지정할 수 있습니다.
예를 들어 .test 의 높이가 700px일 때, 첫번째 문항에서 다음 문항으로 넘어가기 위해서는 700px만큼 스크롤을 아래로 이동시키면 됩니다. 그리고 그 다음 문항도 마찬가지입니다.
처음 스크롤 위치는 0이고 그 다음은 700px, 1400px, ... , 이렇게 스크롤은 .test 의 높이의 배수로 이동하게 됩니다.
vheight 의 배수로 이동시키기 위해 $(window).scrollTop()vheight 로 나누고, 그 나눈값을 Math.floor() 함수를 사용하여 내림합니다. 그 결과값에 1을 더하고 마지막으로 vheight를 곱합니다.
function scrollDown() { const vheight = $('.test').height(); $('html, body').animate({ scrollTop: ((Math.floor($(window).scrollTop() / vheight)+1) * vheight) }, 500); }
 
html과 body의 스크롤의 위치를 animate 로 이동시켜 전체화면이 이동하도록 합니다. animate 의 속성인 지속 시간을 500으로 설정하여 기본 설정(400)보다 느리게 움직이게 합니다.
 
이제 "다음" 버튼을 클릭하면 scrollDown() 함수를 호출해서 스크롤이 아래로 이동될 수 있도록 합시다. $(function()){} 안에서 함수를 호출하여 페이지가 로드되면 이벤트들이 실행될 수 있도록 합니다.
$(function(){ });
 
"다음" 버튼에 클릭 이벤트를 부여하기 위해 버튼의 클래스명인 .next_btn 을 선택합니다. 우선 preventDefault() 함수를 사용하여 버튼 클릭 시 발생할 수 있는 기본 동작이 중단되도록 하고, scrollDown() 함수를 호출합니다.
$(function(){ $('.next_btn').click(function(e){ e.preventDefault(); scrollDown(); }); });
 

4.2.2 scrollUp()

"이전" 버튼을 클릭하면 scrollDown() 함수와는 반대로 스크롤의 위치를 .test 의 높이만큼 위로 이동시켜야 합니다.
위로 이동시키기 위해 $(window).scrollTop()vheight 로 나누고, 그 나눈값을 Math.ceil() 함수를 사용하여 올림합니다. 그리고 1을 빼준 후 vheight 를 곱합니다.
function scrollUp() { const vheight = $('.test').height(); $('html, body').animate({ scrollTop: ((Math.ceil($(window).scrollTop() / vheight)-1) * vheight) }, 500); };
 
앞에서 .next_btn 에 클릭 이벤트를 주었던 것과 마찬가지로 이번에는 "이전" 버튼인 .prev_btn 에 클릭 이벤트를 추가하고 scrollUp() 함수를 호출합니다.
$(function(){ $('.next_btn').click(function(e){ e.preventDefault(); scrollDown(); }); $('.prev_btn').click(function(e){ e.preventDefault(); scrollUp(); }); });
 

4.3 유효성 검사

테스트에서 답안을 선택하지 않은 문항이 있다면 다음 문항으로 이동하거나 제출되지 않도록 유효성 검사 기능을 추가합니다.
 

4.3.1 다음 문항으로 넘어가기

"다음" 버튼을 클릭하면 답안이 선택되었는지 검사를 진행하기 위해 $('.next_btn').click(function(e){}) 에서 코드를 작성합니다. 우선 문제의 답안들을 배열로 가져와 변수 divs 에 저장하겠습니다.
$(this) 는 클릭 이벤트가 발생한 요소들의 정보인 object로 여기서는 클릭된 버튼을 의미합니다. $(this).parent() 는 버튼의 부모인 버튼을 감쌌던 div 태그이고, $(this).parent().prev()div 의 이전 태그를 선택하는데 이것은 답안들을 감싼 또 다른 div 태그입니다. 이때 선택된 div 태그의 자식들이 바로 우리가 가져올 답안 배열입니다.
따라서 $(this).parent().prev().children() 는 문제의 답안들을 모두 배열로 가져올 수 있습니다.
let divs = $(this).parent().prev().children();
 
find() 함수를 통해 divs 배열에서 체크된 input 태그를 찾은 후 변수 inputs 에 저장합니다. 그리고 모든 문항이 선택되지 않았을 경우는 inputs 변수의 길이가 1보다 작을 경우와 동일하므로 이때 "문항이 선택되지 않았습니다."라는 문구가 나오는 경고창을 띄웁니다.
return false 를 추가하여 다음 문항으로 넘어가는 것을 방지합니다.
$('.next_btn').click(function(e){ let divs = $(this).parent().prev().children(); let inputs = divs.find('input:checked'); if(inputs.length < 1) { alert('문항이 선택되지 않았습니다.'); return false; } e.preventDefault(); scrollDown(); });
 

4.3.2 제출하기

모든 문항을 풀고 "제출" 버튼을 누르면 테스트지가 서버로 전송됩니다. 이때도 앞에서 유효성 검사를 진행했던 것과 같이 답안이 선택되지 않은 문항이 있을 경우가 있는지 검사를 진행해야 합니다.
체크된 모든 input 태그를 radios 라는 변수에 저장하고 그 변수의 길이가 3보다 작을 경우 경고창을 띄우도록 합니다.
$("#form").submit(function() { let radios = $('input[type=radio]:checked'); if(radios.length < 3) { alert("문항이 선택되지 않았습니다."); return false; } return true; });
 
💡
이때 radios.length < 33은 문항 수를 의미합니다. 그렇기 때문에 백엔드 실습 진행시에는 10으로 변경시켜 주어야 합니다.
 

4.4 새로고침

새로고침(F5) 시 현재 화면에서 이동되지 않습니다. 이럴 경우 다시 1번부터 문제를 풀어야 하기 때문에 계속 "이전" 버튼을 클릭하여 이전 문항으로 이동해야 하는 문제점이 있습니다. 이를 해결하기 위해 $(function(){}); 내부에 아래 코드를 추가하여 로드 시 스크롤 위치가 0으로 이동할 수 있도록 합니다.
$("html, body").animate({ scrollTop: 0 }, 500);
 

5. 전체 코드

5.1 form.html

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" type="text/css" href="css/reset.css"> <link rel="stylesheet" type="text/css" href="css/form.css"> <script src="https://code.jquery.com/jquery-3.5.1.js"></script> <script type="text/javascript" src="js/form.js"></script> <title>나의 개발 유형찾기</title> </head> <body> <section id="survey"> <div class="wrapper"> <form id="form"> <div class="test"> <div class="question_container"> <h3 class="number">1/3</h3> <h3 class="question">당신이 가장 재밌었던 수업은?</h3> </div> <div class="answer"> <div> <input type="radio" name="answer_1" /> <label>게임 개발(유니티, 언리얼, Pygame)</label> </div> <div> <input type="radio" name="answer_1" /> <label>백엔드 또는 인프라(C계열-닷넷, JAVA-Spring, Python-Django, Network, Ubuntu, CentOS, 부하분산, etc)</label> </div> <div> <input type="radio" name="answer_1" /> <label>정보보안(해킹과 방어)</label> </div> <div> <input type="radio" name="answer_1" /> <label>프론트엔드(HTML, CSS, Javascript, etc)</label> </div> <div> <input type="radio" name="answer_1" /> <label>데이터 분석과 인공지능(전처리, 분석, 시각화, 머신러닝, 딥러닝)</label> </div> <div> <input type="radio" name="answer_1" /> <label>이 길은 내 길이 아닌 것 같다... 고로 재미있는지 모르겠다. </label> </div> </div> <div class="btn_wrap"> <button class="next_btn">다 음</button> </div> </div> <div class="test"> <div class="question_container"> <h3 class="number">2/3</h3> <h3 class="question">여러분의 서비스가 성공하였다면, 서비스를 성공시킨 요소는?</h3> </div> <div class="answer"> <div> <input type="radio" name="answer_2" /> <label>데이터 분석을 통한 효과적인 개인 맞춤형 서비스</label> </div> <div> <input type="radio" name="answer_2" /> <label>화려하거나 품격있는 디자인, 흥미로운 콘텐츠 제작</label> </div> <div> <input type="radio" name="answer_2" /> <label>게임적인(Gamification, 게이미피케이션) 요소 도입</label> </div> <div> <input type="radio" name="answer_2" /> <label>안정적인 서비스 운용</label> </div> <div> <input type="radio" name="answer_2" /> <label>믿을 수 있는 정보 관리</label> </div> </div> <div class="btn_wrap"> <button class="prev_btn">이 전</button> <button class="next_btn">다 음</button> </div> </div> <div class="test"> <div class="question_container"> <h3 class="number">3/3</h3> <h3 class="question">당신은 친구의 집들이에 가려고 합니다. 당신이 선택한 선물은?</h3> </div> <div class="answer"> <div> <input type="radio" name="answer_10" /> <label>집 꾸미기에 도움이 되는 예쁜 인테리어 소품을 골라볼까?</label> </div> <div> <input type="radio" name="answer_10" /> <label>실용성이 갑이지! 화장지랑 수건 세트랑...</label> </div> <div> <input type="radio" name="answer_10" /> <label>음... 우선 센스있는 집들이 선물 먼저 검색해봐야지!</label> </div> <div> <input type="radio" name="answer_10" /> <label>집들이 가서 뭐하겠어! 보드 게임이나 사 가자!</label> </div> <div> <input type="radio" name="answer_10" /> <label>역시 집은 안전이 최고!! 현관문 보안장치 사줘야겠다.</label> </div> </div> <div class="btn_wrap"> <button class="prev_btn">이 전</button> <input type="submit" value="제 출" class="submit_btn"/> </div> </div> </form> </div> </section> </body> </html>
 

5.2 form.css

@font-face { font-family: "GmarketSansBold"; src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2001@1.1/GmarketSansBold.woff") format('woff'); font-weight: normal; font-style: normal; } @font-face { font-family: "NEXON Lv1 Gothic OTF"; src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_20-04@2.1/NEXON Lv1 Gothic OTF.woff") format("woff"); font-weight: normal; font-style: normal; } body { background-color: #FAF1BE; color: #000; overflow: hidden; font-family: "NEXON Lv1 Gothic OTF"; } #survey{ display: flex; justify-content: center; width: 100vw; height: 100vh; margin-top: 50px; } .test { max-width: 600px; height: 100vh; } .question_container { display: flex; flex-direction: column; align-items: center; font-size: 30px; line-height: 1.3; padding-bottom: 30px; } .question_container .number { padding-bottom: 30px; } .question_container .question { font-family: "GmarketSansBold"; } .answer { font-size: 17px; line-height: 1.3; padding-bottom: 30px; } .answer div { padding-bottom: 15px; } .answer input { cursor: pointer; } .btn_wrap { display: flex; justify-content: center; } .btn_sort{ justify-content: space-between; } button, .submit_btn { font-family: "NEXON Lv1 Gothic OTF"; font-size: 20px; width: 100px; height: 40px; padding: 5px; border-style: none; border-radius: 10px; background-color: #7F47DD; color: #fff; cursor: pointer; } button:hover, .submit_btn:hover{ color: #7F47DD; background-color: #fff; }
 

5.3 form.js

function scrollUp() { const vheight = $('.test').height(); $('html, body').animate({ scrollTop: ((Math.ceil($(window).scrollTop() / vheight)-1) * vheight) }, 500); }; function scrollDown() { const vheight = $('.test').height(); $('html, body').animate({ scrollTop: ((Math.floor($(window).scrollTop() / vheight)+1) * vheight) }, 500); } $(function(){ $('.next_btn').click(function(e){ let divs = $(this).parent().prev().children(); let inputs = divs.find('input:checked'); if(inputs.length < 1) { alert('문항이 선택되지 않았습니다.'); return false; } e.preventDefault(); scrollDown(); }); $('.prev_btn').click(function(e){ e.preventDefault(); scrollUp(); }); $("#form").submit(function() { let radios = $('input[type=radio]:checked'); if(radios.length < 10) { alert("문항이 선택되지 않았습니다."); return false; } return true; }); $("html, body").animate({ scrollTop: 0 }, 500); });