구름에듀 사이트의 판다코딩의 HTML/JS/CSS로 나만의 MBTI 사이트 만들기 강의를 보고 만들었습니다.
총 3개의 섹션으로 나누어 한 페이지로 레이아웃하여
1️⃣ 이 보일 때 display:block
나머지 2️⃣,3️⃣은 display:none
으로 처리할 것이다.
=> SPA(Single Page Application)
id="main"
id="qna"
id="result"
부트스트랩의 그리드 시스템과 css를 활용하여 편리하게 반응형 페이지의 골격과 스타일을 적용한다.
<body>
<div id="container">
<!--01. Main page -->
<section id="main" class="mx-auto my-5 py-5 px-3" style="display:block;">
<h2> 제목 입력 </h2>
<div class="col-lg-4 col-md-8 col-sm-12 mx-auto">
<img src="./img/main.png" class="img-fluid" alt="mainImage"/>
</div>
<p>테스트 소개</p>
<button class="btn btn-outline-primary mt-3">시작하기</button>
</section>
</div>
</body>
<!-- 02. QnA page -->
<section id="qna">
<!-- 상태바 -->
<div class="status mx-auto mt-5">
<div class="statusBar"></div>
</div>
<!-- 질문과 대답 -->
<div class="qBox mx-auto my-5 py-3"></div>
<div class="answerBox"></div>
</section>
<!-- 03. Result page -->
<section id="result" class="mx-auto my-5 py-5 px-3">
<h2>결과 제목</h2>
<div class="resultname"> 결과 이름 </div>
<div id="resultImg" class="my-3 col-lg-6 col-md-8 col-sm-10 col-12 mx-auto"></div>
<div class="resultDesc"> 결과 설명</div>
<button type="button" class="kakao mt-3 py-2 px-3">공유하기</button>
</section>
index.html의 모든 선택자가 공통적으로 들어가는 스타일 속성을 지정해준 디폴트 css도 따로 만들어 링크한다.
1️⃣,2️⃣,3️⃣ 의 각 css 파일을 만들고 index.html에 링크한다.
정교하고 직관적인 애니메이션 기능을 보여주기 위한 css 파일을 별도로 만들어 링크한다.
@keyframes
이용하여 CSS 애니메이션의 중간 절차를 제어한다. 자세한설명fadeIn
fadeOut
을 정의한뒤 html DOM객체의 style.animation에 적용한다. main.style.animation = 'fadeOut 1s';
qna.style.animation = 'fadeIn 1s';
/* 서서히 생기는 효과*/
@keyframes fadeIn {
from {opacity : 0;}
to {opacity : 1;}
}
/* 서서히 사라지는 효과*/
@keyframes fadeOut {
from {opacity : 1;}
to {opacity : 0;}
}
/* @-webkit-keyframes : 크롬에서 애니메이션 적용하기 */
@-webkit-keyframes fadeIn {
from {opacity : 0;}
to {opacity : 1;}
}
@-webkit-keyframes fadeOut {
from {opacity : 1;}
to {opacity : 0;}
}
/* 돔객체에 넣어주는것이 아닌 클래스 선택자로 css animation 속성값 지정해줘도 됨*/
/*
.fadeIn {
animation: fadeIn; <- 위에서 만들어둔 애니메이션 지정
animation-duration: 0.5s;
}
.fadeOut {
animation: fadeOut;
animation-duration: 0.5s;
}
*/
qnaList
: 12개의 {qna}
객체가 담긴 Q&A 리스트 배열q
: 질문,a
: 3개의 {answer,type}
객체가 담긴 answer 리스트 배열answer
: 답변,type
: 해당 답변에 가까운 4가지 타입이 담긴 배열infoList
: 12개의 {name,desc}
객체가 담긴 결과리스트 배열name
: 결과,desc
: 설명function begin() {
//1️⃣ 1초동안 서서히 사라지기 적용
main.style.WebkitAnimation = 'fadeOut 1s';
main.style.animation = 'fadeOut 1s';
//2️⃣ 0.45초후에 1초동안 서서히 나타나기 적용
setTimeout(() => {
qna.style.WebkitAnimation = 'fadeIn 1s';
qna.style.animation = 'fadeIn 1s';
// + 0.45초후에 1️⃣ 완전히 안보이게,2️⃣ 완전히 나타남
setTimeout(() => {
main.style.display = 'none';
qna.style.display = 'block';
}, 450);
//질문나오는 goNext() 호출
let qIdx = 0;
goNext(qIdx);
}, 450);
}
function goNext(qIdx) {
let q = document.querySelector('.qBox');
//`q`의 value 처리
//`qnaList`의 qIdx번째 인덱스의 `q`가 들어옴
q.innerHTML = qnaList[qIdx].q;
// `a` 의 value 처리
//`qnaList`객체의 qIdx번째 인덱스의 `a`의 배열의 i번 반복
for (let i in qnaList[qIdx].a) {
//addAnswer()에 `qnaList`의 qIdx번째 인덱스번째의 `a`의 i번째 인덱스의 answer와 qIdx를 인자로 전달
addAnswer(qnaList[qIdx].a[i].answer, qIdx);
}
function addAnswer(answerText, qIdx) {
//answerText = qnaList[qIdx].a[i].answer
//응답 전체를 담는 박스
let a = document.querySelector('.answerBox');
//각 3가지 응답 버튼
let answer = document.createElement('button');
answer.classList.add('answerList');
//부트스트랩 css 및 애니메이션 적용
answer.classList.add('my-3');
answer.classList.add('py-3');
answer.classList.add('mx-auto');
answer.classList.add('fadeIn');
//응답전체 박스에 개별응답버튼 순서대로넣기
a.appendChild(answer);
//개별응답버튼에 파라미터로 받은 answerText( = qnaList[qIdx].a[i].answer) 넣기
answer.innerHTML = answerText;
//버튼 클릭할때마다 발생하는 이벤트 적용
answer.addEventListener(
'click',
function () {
//모든 응답버튼 children에 담기
let children = document.querySelectorAll('.answerList');
//모든 응답버튼 반복
for (let i = 0; i < children.length; i++) {
// 모든 응답버튼 비활성화,0.5초 동안 서서히 사라지기
children[i].disabled = true;
children[i].style.WebkitAnimation = 'fadeOut 0.5s';
children[i].style.animation = 'fadeOut 0.5s';
}
//0.45초 뒤에
setTimeout(function(){
//해당 질문의 응답지를 숨긴다.
// ✨만약 이 처리를 해주지 않는다면 응답이 숨겨지지 않고 쌓이게 된다.
for(let i=0;i<children.length;i++){
children[i].style.display='none';
}
//다음 질문으로 넘어간다.
goNext(++qIdx);
},450)
});
}
let status = document.querySelector('.statusBar');
//const endPoint=12;
//전체 12가지 질문에서 질문하나 할때마다 채워짐
status.style.width = (100 / endPoint) * (qIdx + 1) + '%';
}
function goNext(qIdx){
//endPoint 12와 같아진다면 goResult() 호출 후 return으로 구문 종료
if(qIdx === endPoint ){
goResult();
return;
}
//결과페이지로 넘어가게한다.
function goResult() {
// qna 1초동안 서서히 사라짐
qna.style.webkitAnimation = 'fadeOut 1s';
qna.style.animation = 'fadeOut 1s';
//0.5초뒤에
setTimeout(function () {
//결과가 서서히 1초동안 나타남
result.style.webkitAnimation = 'fadeIn 1s';
result.style.animation = 'fadeIn 1s';
//그리고 0.5초뒤에
setTimeout(function () {
//qna는 완전히 사라지고, 결과는 완전히 나타남
qna.style.display = 'none';
result.style.display = 'block';
}, 500);
},500);
//결과를 구현하는 함수 호출
setResult();
}
// 고른 응답의 최대 점수를 매기기위한 변수 설정
const select = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
//addAnswer()에서 answer.addEventListener가 발생할때마다
//선택한 응답의 type의 프로퍼티값을 target에 담기
let target = qnaList[qIdx].a[idx].type;
//type의 프로퍼티값 즉 해당 배열길이만큼 다음 구문 반복
for(let i=0;i<target.length; i++){
//점수 매기기위한 12개의 배열을 담고 있는 select변수에
//선택한 응답의 type의 해당 인덱스가 담고있는 값을 넣어
//select의 해당 인덱스의 숫자에 +1 해준다.
select[target[i]] += 1;
}
//최종 응답 계산 함수
function calResult() {
// 고른 응답의 점수를 매기기위한 select변수를 전개구문한다
// Math메서드에 넣어 점수가 매겨진 배열의 최대값을 구함(동일 점수라면 맨 처음 점수로 반환)
//indexOf메스드의 인수로 전달된 요소를 검색하여
let result = select.indexOf(Math.max(...select));
// 검색된 해당 인덱스 번호 반환
return result;
}
function setResult() {
//return된 result값 point에 담기
//result : 최대값이 발견된 해당 인덱스 번호
let point = calResult();
//결과이름 담기
const resultName = document.querySelector('.resultname');
// infoList의 최대값이 발견된 인덱스 번호의 결과이름을 담는다
resultName.innerHTML = infoList[point].name;
//결과 이미지
let resultImg = document.createElement('img');
const imgDiv = document.querySelector('#resultImg');
//최대값이 발견된 인덱스 번호 이미지 번호로 담기
let imgURL = 'img/image-' + point + '.png';
resultImg.src = imgURL;
resultImg.alt = point;
//부트스트랩 .img-fluid
//max-width: 100%; height: auto; 부모 너비에 맞게 크기가 조정되도록 이미지에 추가됨
resultImg.classList.add('img-fluid');
//dom객체에 자식요소로 넣어주기
imgDiv.appendChild(resultImg);
//결과 설명
const resultDesc = document.querySelector('.resultDesc');
//infoList의 최대값이 발견된 인덱스의 desc 담기
resultDesc.innerHTML = infoList[point].desc;
}
<script src="https://developers.kakao.com/sdk/js/kakao.js"></script>
<script>
Kakao.init('Javascript key');
Kakao.isInitialized();
</script>
<button onclick="js:setShare()" type="button" class="kakao mt-3 py-2 px-3">공유하기</button>
<script src="./js/share.js"></script>
//netlify 로 배포한 url
const url = 'https://mymbtitwelvelovetype.netlify.app/';
function setShare(){
let resultImg = document.querySelector('#resultImg');
//alt = point
let resultAlt = resultImg.firstElementChild.alt;
const shareTitle = '십이간지 연애유형 결과';
const shareDes = infoList[resultAlt].name;
const shareImage = url + 'img/image-' + resultAlt + '.png';
const shareURL = url + 'page/result-' + resultAlt + '.html';
//Kakao.Share.sendDefault() 함수
//카카오톡 공유하기 버튼을 추가하지 않고, 메시지 보내기 요청만 합니다.
Kakao.Link.sendDefault({
objectType: 'feed',
content: {
title: shareTitle,
description: shareDes,
imageUrl: shareImage,
link: {
mobileWebUrl: shareURL,
webUrl: shareURL
},
},
buttons: [
{
title: '결과확인하기',
link: {
mobileWebUrl: shareURL,
webUrl: shareURL,
},
},
]
});
}