728x90
반응형

완성 화면

 

 

 

 

코드 보기 / CSS

.reveal > div,
            .reveal > span {
                opacity: 0;
            }
            .reveal.show > div,
            .reveal.show > span {
                animation: opacity 1s linear forwards;
            }
            .reveal {
                position: relative;
            }
            .reveal::before {
                content: '';
                position: absolute;
                left: 0;
                top: 0;
                width: 0;
                height: 100%;
                background-color: #fff;
                z-index: 1;
            }
            .reveal.reveal-TWO::after {
                content: '';
                position: absolute;
                left: 0;
                top: 0;
                width: 0;
                height: 100%;
                z-index: 1;
                background-color: cornflowerblue;
            }
            /* before animation (1개)*/
            .reveal.show::before {
                animation:  reveal 1s cubic-bezier(0, 0.78, 0.58, 1);
            }
            .reveal.reveal-RTL.show::before {
                animation:  reveal-RTL  1s cubic-bezier(0, 0.78, 0.58, 1);
            }
            .reveal.reveal-TTB.show::before {
                animation: reveal-TTB 1s cubic-bezier(0, 0.78, 0.58, 1);
            }
            .reveal.reveal-BTT.show::before {
                animation: reveal-BTT 1s cubic-bezier(0, 0.78, 0.58, 1);
            }
            /*before,after (2개) */
            .reveal.show::after {
                animation:  reveal 1s 0.5s cubic-bezier(0, 0.78, 0.58, 1);
            }
            .reveal.reveal-RTL.show::after {
                animation:  reveal-RTL  1s 0.5s cubic-bezier(0, 0.78, 0.58, 1);
            }
            .reveal.reveal-TTB.show::after {
                animation: reveal-TTB 1s 0.5s cubic-bezier(0, 0.78, 0.58, 1);
            }
            .reveal.reveal-BTT.show::after {
                animation: reveal-BTT 1s 0.5s cubic-bezier(0, 0.78, 0.58, 1);
            }
            /* animation */
            @keyframes opacity {
                0%          {opacity: 0;}
                60%         {opacity: 0;}
                70%         {opacity: 1;}
                100%        {opacity: 1;}
            }
            @keyframes reveal {
                0%      {width: 0;      left: 0;}
                50%     {width: 100%;   left: 0;}
                80%     {width: 100%;   left: 0;}
                100%    {width: 0;      left: 100%;}
            }
            @keyframes reveal-RTL {
                0%      {width: 0;      left: auto;  right: 0;}
                50%     {width: 100%;   left: auto;  right: 0;}
                80%     {width: 100%;   left: auto;  right: 0;}
                100%    {width: 0;      left: auto;  right: 100%;}
            }
            @keyframes reveal-TTB {
                0%   {width: 100%; height: 0;    top: 0;}
                50%  {width: 100%; height: 100%; top: 0;}
                80%  {width: 100%; height: 100%; top: 0;}
                100% {width: 100%; height: 0;    top: 100%;}
            }
            @keyframes reveal-BTT {
                0%   {width: 100%; height: 0;    bottom:0;    top: auto;}
                50%  {width: 100%; height: 100%; bottom:0;    top: auto;}
                80%  {width: 100%; height: 100%; bottom:0;    top: auto;}
                100% {width: 100%; height: 0;    bottom:100%; top: auto;}
            }
            .parallax__item__num,
            .parallax__item__title {
                display: none;
            }

 

 

 

 

코드 보기 / HTML

<div class="parallax__wrap">
           <section id="section1" class="parallax__item">
                <span class="parallax__item__num">01</span>
                <h2 class="parallax__item__title">Section 01</h2>
                <figure class="parallax__item__imgWrap">
                    <div class="parallax__item__img"></div>
                </figure>
                <p class="parallax__item__desc split">좋은 책은 우리에게 생각하는 법을 가르쳐준다.</p>
           </section>
           <!--//section 01-->

 

 

 

코드 보기 / JAVASCRIPT

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.5/gsap.min.js"></script>
    <script>
        //reveal class 추가 시 -> 자식 요소를 span으로 감싼다.
        document.querySelectorAll("p.reveal").forEach(text => {
            text.innerHTML = `<span>${text.innerHTML}</span>`;
        })

        function scroll() {
            let scrollTop = window.scrollY || window.pageYOffset;

            const reveals = document.querySelectorAll(".reveal");

            reveals.forEach(reveal => {
                let revealOffset = reveal.offsetTop + reveal.parentElement.offsetTop;
                let revealDelay = reveal.dataset.delay;


                // if(scrollTop >= revealOffset - window.innerHeight/2) {
                //     reveal.classList.add("show");
                // }

                if(scrollTop >= revealOffset - window.innerHeight) {
                    if(revealDelay == undefined) {
                        reveal.classList.add("show");
                    } else {
                        setTimeout (() => {
                            reveal.classList.add("show")
                        }, revealDelay);
                    }
                }
            })

            document.querySelector(".scroll span").innerText = Math.round(scrollTop);
            requestAnimationFrame(scroll);

        }
        scroll();

 

document.querySelectorAll("p.reveal").forEach(text => { ... })

: 문서에서 CSS 선택자 p.reveal에 해당하는 모든 요소를 선택하고 각 요소에 대해 반복문을 실행합니다. 선택된 요소들은 "reveal" 클래스를 가지고 있는 p 요소들입니다.

 


text.innerHTML = <span>${text.innerHTML}</span>;

: 선택된 p.reveal 요소의 내용을 <span> 태그로 감싸서 변경합니다. 이를 통해 텍스트를 자식 요소로 갖는 <span> 태그가 추가됩니다.

 


function scroll() { ... }

: scroll 함수를 정의합니다. 이 함수는 스크롤 이벤트를 처리하고, 요소의 나타남을 관리합니다.

 


let scrollTop = window.scrollY || window.pageYOffset;

: scrollTop 변수를 선언하고, 현재 스크롤 위치를 저장합니다. window.scrollY 또는 window.pageYOffset 속성을 사용하여 스크롤 위치를 가져옵니다.

 


const reveals = document.querySelectorAll(".reveal");

: 문서에서 CSS 선택자 .reveal에 해당하는 모든 요소를 선택하여 reveals에 저장합니다. 선택된 요소들은 "reveal" 클래스를 가지고 있는 모든 요소입니다.



reveals.forEach(reveal => { ... })

: reveals에 포함된 각 요소에 대해 반복문을 실행합니다.



let revealOffset = reveal.offsetTop + reveal.parentElement.offsetTop;

: reveal 요소의 세로 위치를 계산하여 revealOffset 변수에 저장합니다. offsetTop 속성은 요소의 부모를 기준으로 한 상대적인 세로 위치를 반환합니다.


let revealDelay = reveal.dataset.delay;

: reveal 요소의 data-delay 속성 값을 가져와서 revealDelay 변수에 저장합니다. data-delay 속성은 요소가 나타나기까지의 지연 시간을 나타냅니다.



if(scrollTop >= revealOffset - window.innerHeight) { ... }

: 스크롤 위치가 revealOffset - window.innerHeight보다 크거나 같을 때 다음 조건을 확인합니다.



if(revealDelay == undefined) { ... } else { ... }

: revealDelay 변수가 정의되어 있는지 확인합니다. 만약 정의되어 있지 않다면 지연 없이 즉시 reveal 요소에 "show" 클래스를 추가합니다. 그렇지 않다면 setTimeout 함수를 사용하여 revealDelay 값만큼의 지연 후에 "show" 클래스를 추가합니다.



document.querySelector(".scroll span").innerText = Math.round(scrollTop);

: 문서에서 CSS 선택자 .scroll span에 해당하는 요소를 선택하고, 해당 요소의 내용을 현재 스크롤 위치인 scrollTop으로 변경합니다.



requestAnimationFrame(scroll);

: 스크롤 이벤트 처리를 위해 requestAnimationFrame 함수를 호출하여 scroll 함수를 반복적으로 실행합니다. 이를 통해 부드러운 스크롤 이벤트 처리가 가능해집니다.

728x90
반응형

 

완성 화면

 

 

 

 

코드 보기 / CSS

* {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'ReciaSerifDisplay';
        }

        body {
            height: 20000px;
            background: #102a46;
        }

        .scrollTop {
            position: fixed;
            left: 10px;
            top: 10px;
            z-index: 1000;
            width: 40px;
            height: 40px;
            background-color: rgba(0, 0, 0, 0.5);
            text-align: center;
            font-size: 14px;
            line-height: 40px;
            color: #fff;
        }

        .fixed {
            position: fixed;
            left: 50%;
            top: 50%;
            transform: translate(-50%, -50%);
            z-index: 1;
        }

        .s1-text1 {
            font-size: 30vw;
            color: #fff;
            text-align: center;
        }

        .s1-text2 {
            font-size: 0vw;
            line-height: 2;
            padding-top: 8vw;
            color: #fff;
            text-align: center;
        }

        .s1-text3 {
            font-size: 0vw;
            line-height: 2;
            padding-top: 8vw;
            color: #fff;
            text-align: center;
        }

        .s1-text4 {
            font-size: 0vw;
            line-height: 2;
            padding-top: 8vw;
            color: #fff;
            text-align: center;
        }

        .s1-img1 {
            width: 200vw;
            height: 100vh;
        }

        .s1-img1>div:nth-child(1) {
            padding: 0 0vh;
        }

        .s1-img1>div:nth-child(2) {
            padding: 0 0vh;
        }

        .s1-img1>div:nth-child(3) {
            padding: 0 0vh;
        }

        .s1-img1>div:nth-child(4) {
            padding: 0 0vh;
        }

        .s1-img1>div:nth-child(5) {
            padding: 0 0vh;
        }

        .s1-img1>div {
            height: 20vh;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .s1-img1>div>div {
            width: 19vh;
            height: 19vh;
            background-position: center;
            background-size: cover;
            background-repeat: no-repeat;
            border: 5px solid #000;
        }

        .s1-img1-1>div:first-child {
            background-image: url(https://source.unsplash.com/500x500?moon);
        }

        .s1-img1-1>div:last-child {
            background-image: url(https://source.unsplash.com/500x500?moonlight);
        }

        .s1-img1-2>div:first-child {
            background-image: url(https://source.unsplash.com/500x500?mikyway);
        }

        .s1-img1-2>div:last-child {
            background-image: url(https://source.unsplash.com/500x500?galaxy);
        }

        .s1-img1-3>div:first-child {
            background-image: url(https://source.unsplash.com/500x500?Moon);
        }

        .s1-img1-3>div:last-child {
            background-image: url(https://source.unsplash.com/500x500?Moonlight);
        }

        .s1-img1-4>div:first-child {
            background-image: url(https://source.unsplash.com/500x500?Milkyway);
        }

        .s1-img1-4>div:last-child {
            background-image: url(https://source.unsplash.com/500x500?Galaxy);
        }

        .s1-img1-5>div:first-child {
            background-image: url(https://source.unsplash.com/500x500?sky);
        }

        .s1-img1-5>div:last-child {
            background-image: url(https://source.unsplash.com/500x500?Moonsky);
        }

 

scrollTop

: scrollTop 요소에 대한 스타일을 정의한다.

position: fixed는 요소가 고정된 위치에 있음을 나타내며, left: 10px와 top: 10px는 요소가 뷰포트에서 왼쪽과 위쪽에서 10픽셀 떨어져 있음을 나타낸다.z-index: 1000은 요소가 다른 요소 위에 놓이는 우선 순위를 나타내며, 숫자가 높을수록 요소가 위에 나타난다.width: 40px와 height: 40px는 요소의 너비와 높이를 40픽셀로 설정한다.
background-color: rgba(0, 0, 0, 0.5)는 요소의 배경색상을 반투명한 검정색으로 설정한다.
text-align: center는 요소 내부의 텍스트를 중앙 정렬한다.
font-size: 14px는 요소 내부의 글꼴 크기를 14픽셀로 설정한다.
line-height: 40px는 요소 내부의 줄 높이를 40픽셀로 설정한다.
color: #fff는 요소 내부의 글자색을 흰색으로 설정한다.

 


.fixed

: fixed 클래스의 요소에 대한 스타일을 정의한다.
position: fixed는 요소가 고정된 위치에 있음을 나타내며, left: 50%와 top: 50%는 요소가 뷰포트에서 가로와 세로로 각각 중앙에 위치함을 나타낸다.
transform: translate(-50%, -50%)는 요소를 가운데 정렬하기 위해 위치를 조정하는 데 사용된다.
z-index: 1은 요소가 다른 요소 위에 놓이는 우선 순위를 나타내며, 숫자가 높을수록 요소가 위에 나타낸다.

 

.s1-text1~4

: 스타일링이 적용되는 요소의 클래스명이 .s1-text1~4이다.
font-size: 30vw는 요소 내부의 글꼴 크기를 viewport width(뷰포트 너비)의 30%로 설정한다.
color: #fff는 요소 내부의 글자색을 흰색으로 설정한다.
text-align: center는 요소 내부의 텍스트를 중앙 정렬한다.

 

.s1-img1

: 스타일링이 적용되는 요소의 클래스명이 .s1-img1 이다.
width: 200vw는 요소의 너비를 viewport width(뷰포트 너비)의 200%로 설정한다. (즉, 화면보다 2배 넓다.)
height: 100vh는 요소의 높이를 viewport height(뷰포트 높이)의 100%로 설정한다. (즉, 화면 전체를 차지한다.)

 

.s1-img1>div:nth-child(1~5)

:.s1-img1 요소의 첫 번째 자식부터 다섯번째 자식 요소에 대한 스타일을 정의한다.
padding: 0 0vh는 요소의 상하좌우 여백을 모두 0으로 설정한다. (즉, 여백이 없다.)

 

 

 

코드 보기 / HTML

    <div class="scrollTop"></div>
    <section id="section1">
        <div class="s1-text1 fixed" data-0="font-size: 0vw; opacity:1" data-1000="font-size: 30vw; opacity:1"
            data-2000="font-size: 0vw; opacity:1" data-3000="font-size: 0vw; opacity:0">HELLO
        </div>

        <div class="s1-text2 fixed" data-2500="font-size: 0vw; transform: translate(-50%, -50%) rotate(0deg);"
            data-3000="font-size: 30vw; transform: translate(-50%, -50%) rotate(1080deg);"
            data-8500="font-size: 0vw;">MOON
        </div>
        
        <div class="s1-img1 fixed" 
        data-3500="width: 200vw;" 
        data-4000="width: 20vw;" 
        data-5000="width: 90vw;">
            
            <div class="s1-img1-1" data-3500="transform: rotate(270deg);" data-5000="transform: rotate(0deg);">
                <div></div>
                <div></div>
            </div>

            <div class="s1-img1-2" 
                data-4000="margin:0 auto; width: 200vw;" 
                data-5000="width: 20vw; transform: rotate(360deg);"
                data-6000="width: 200vw; transform: rotate(0deg); width: 90vw;">
                <div data-3500="display: none;" data-5000="display: block;"></div>
                <div data-3500="display: none;" data-5000="display: block;"></div>
            </div>

            <div class="s1-img1-3" data-6000="width: 20vw; transform: rotate(360deg);"
                data-7000="margin:0 auto; width: 200vw; transform: rotate(0deg); width: 90vw;">
                <div data-3500="display: none;" data-6000="display: block;"></div>
                <div data-3500="display: none;" data-6000="display: block;"></div>
            </div>

            <div class="s1-img1-4" 
                data-7000="margin:0 auto;  width: 20vw; transform: rotate(360deg);"
                data-8000="width: 200vw; transform: rotate(0deg); width: 90vw;">
                <div data-3500="display: none;" data-7000="display: block;"></div>
                <div data-3500="display: none;" data-7000="display: block;"></div>
            </div>

            <div class="s1-img1-5" 
                data-8000="margin:0 auto; width: 20vw; transform: rotate(360deg);"
                data-9000="width: 200vw; transform: rotate(0deg); width: 90vw;">
                <div data-3500="display: none;" data-8000="display: block;"></div>
                <div data-3500="display: none;" data-8000="display: block;"></div>
            </div> 

            <div class="s1-text3 fixed" data-8500="font-size: 0vw; transform: translate(-50%, -50%) rotate(0deg);"
            data-10000="font-size: 10vw; transform: translate(-50%, -50%) rotate(1080deg);"
            data-11000="font-size: 0vw;"
            > A SKY FULL OF STARS
            </div>

            <div class="s1-text4 fixed" data-11000="font-size: 0vw; transform: translate(-50%, -50%) rotate(0deg);"
            data-12000="font-size: 3vw; transform: translate(-50%, -50%) rotate(1080deg);"> 
            The moon shows us itself without changing, <br> providing us with an opportunity to search for ourselves. <br>- Hiroshi Matsumoto.
            </div>
        </div>
    </section>

 

.s1-text1 fixed 


data-0="font-size: 0vw; opacity:1" : 해당 요소가 초기에 표시될 때의 스타일을 정의한다.  글꼴 크기는 0vw (즉 보이지 않음), 투명도는 1이다.


data-1000="font-size: 30vw; opacity:1"는 스크롤을 1000px만큼 내렸을 때의 스타일을 정의한다. 글꼴 크기는 30vw(뷰포트의 30%), 투명도는 1이다.


data-2000="font-size: 0vw; opacity:1"는 스크롤을 2000px만큼 내렸을 때의 스타일을 정의한다. 글꼴 크기는 0vw, 투명도는 1이다.


data-3000="font-size: 0vw; opacity:0"는 스크롤을 3000px만큼 내렸을 때의 스타일을 정의한다. 글꼴 크기는 0vw, 투명도는 0이다. (요소가 화면에서 사라집니다.)
HELLO는 요소 내부의 텍스트이다.

 

 

s1-text2 fixed 


data-2500="font-size: 0vw; transform: translate(-50%, -50%) rotate(0deg);": 스크롤을 2500px만큼 내렸을 때의 스타일을 정의한다. 글꼴 크기는 0vw, 요소의 위치를 중앙으로 이동시키는 transform: translate(-50%, -50%), 요소의 회전 각도를 0도로 설정하는 rotate(0deg)을 적용한다.


data-3000="font-size: 30vw; transform: translate(-50%, -50%) rotate(1080deg);": 스크롤을 3000px만큼 내렸을 때의 스타일을 정의한다. 글꼴 크기는 30vw, 요소의 위치를 중앙으로 이동시키는 transform: translate(-50%, -50%)를 사용하여 요소를 가운데 정렬하기 위해 위치를 조정하는 데 사용된다.

data-8500="font-size: 0vw; : 스크롤을 8500px만큼 내렸을 때의 스타일을 정의한다. 글꼴 크기는 0vw으로 점점 작아지면서 요소의 텍스트가 사라지게 하는 역할을 한다.

 

 

.s1-img1 

: s1-img1이라는 선택자를 가진 div 박스 안의 모든 요소에 같은 효과를 주기 위해  CSS 선택자를 사용한다.

fixed 클래스는 이미지가 화면에 고정되도록 한다.

data-3500, data-4000, data-5000 속성은 이미지 요소가 화면에서 어떻게 변화할지를 정의한다.

data-3500은 이미지 요소가 처음에 화면에 나타나면서(화면의 3500px인 위치) 너비가 200vw로 설정된다.

data-4000은 스크롤이 4000px에 도달했을 때 이미지 요소의 너비가 20vw로 설정된다는 것을 의미한다.

data-5000은 스크롤이 5000px에 도달했을 때 이미지 요소의 너비가 90vw로 설정됩니다.

 

 

.s1-img1-1

: .s1-img1-1 클래스를 가진  요소에 대한 애니메이션을 설정하기 위해 .s1-img1-1 CSS 선택자를 사용한다.

data-3500은 이미지 요소가 처음에 화면에 나타날 때, .s1-img1-1 클래스의 회전이 270deg로 설정된다.

data-5000은 스크롤이 5000px에 도달했을 때 이미지 요소의 회전이 0deg로 설정된다.

이렇게 함으로써 이미지 요소의 자식 요소도 화면에 나타나면서 회전하게 된다.



.s1-img1-2~5

: 클래스를 사용하여 이미지 요소의 자식 요소를 정의한다.

data-nnnn 속성은 이미지 요소가 화면의 픽셀 값이 data 위에 오는 nnnn과 일치할 때 어떤 CSS 속성을 줄 지 설정하는 구문이다.

가로 길이(너비)가 200vw나 20vw, 90vw로 설정된다는 의미이다. 

200vw는 화면 밖으로 요소가 나가 보이지 않으며 20vw는 너비가 줄어들면서 요소들이 서로 가까워진다. 90vw는 화면의 양측에 요소가 위치하도록 한다.

margin: 0 auto는 이미지가 수평 중앙에 위치하도록 한다. 

tranform: rotare(nnn deg)는 일치하는 nnn값만큼 회전한다는 것을 의미한다.

 

data-3500 속성은 첫 번째와 두 번째 <div> 요소가 스크롤값이 3500px이 될 때부터  화면에서 나타나지 않도록 설정한다.

data-n000 속성은 그 값에 해당하는 픽셀 값이 되었을 때 첫 번째와 두 번째 <div> 요소가 화면에 나타나도록 display: block으로 설정한다.

 

 

 

 

 

코드 보기 / JAVASCRIPT

<script src="https://cdnjs.cloudflare.com/ajax/libs/skrollr/0.6.30/skrollr.min.js"></script>
    <script type="text/javascript">
        let s = skrollr.init();

        window.addEventListener("scroll", () => {
            let scrollTop = window.pageYOffset || window.scrollY

            document.querySelector(".scrollTop").innerText = parseInt(scrollTop);

        });

 

skrollr 라이브러리를 script src을 통해 로드하고 초기화한다.

skrollr은 스크롤 이벤트를 이용하여 웹사이트의 요소를 애니메이션화시키는 자바스크립트 라이브러리이다.


그 다음, window 객체에 scroll 이벤트 리스너를 등록한다. 

스크롤 이벤트가 발생하면 이벤트 콜백 함수가 실행됩니다.

이벤트 콜백 함수는 window.pageYOffset 또는 window.scrollY 프로퍼티를 사용하여 현재 스크롤 위치를 가져와서, document.querySelector(".scrollTop")를 사용하여 .scrollTop 클래스를 가진 요소를 찾고, 그 요소의 innerText 값을 현재 스크롤 위치로 설정한다.

따라서, 스크롤을 할 때마다 페이지의 위쪽에서 현재 위치까지의 스크롤 거리가 .scrollTop 클래스를 가진 요소의 텍스트로 출력된다.

728x90
반응형

 

완성 화면

 

 

 

 

 

코드 보기 / HTML

  <div class="parallax__wrap">
           <section id="section1" class="parallax__item">
                <span class="parallax__item__num">01</span>
                <h2 class="parallax__item__title">Section 01</h2>
                <figure class="parallax__item__imgWrap">
                    <div class="parallax__item__img"></div>
                </figure>
                <p class="parallax__item__desc">좋은 책은 우리에게 생각하는 법을 가르쳐준다.</p>
           </section>
           <!--//section 01-->

           <section id="section2" class="parallax__item">
                <span class="parallax__item__num">02</span>
                <h2 class="parallax__item__title">Section 02</h2>
                <figure class="parallax__item__imgWrap">
                    <div class="parallax__item__img"></div>
                </figure>
                <p class="parallax__item__desc">독서란 삶의 전체를 돌아보는 것이다.</p>
            </section>
            <!--//section 02-->

            <section id="section3" class="parallax__item">
                <span class="parallax__item__num">03</span>
                <h2 class="parallax__item__title">Section 03</h2>
                <figure class="parallax__item__imgWrap">
                    <div class="parallax__item__img"></div>
                </figure>
                <p class="parallax__item__desc">이미 달은 충분히 밝다. 우리가 할 일은 그 빛을 받아들이는 것 뿐이다.</p>
            </section>
            <!--//section 03-->

            <section id="section4" class="parallax__item">
                <span class="parallax__item__num">04</span>
                <h2 class="parallax__item__title">Section 04</h2>
                <figure class="parallax__item__imgWrap">
                    <div class="parallax__item__img"></div>
                </figure>
                <p class="parallax__item__desc">달은 어디서든 그 자리에 있지만, 인생에서는 다른 위치에 서 있어야 한다.</p>
            </section>
            <!--//section 04-->

            <section id="section5" class="parallax__item">
                <span class="parallax__item__num">05</span>
                <h2 class="parallax__item__title">Section 05</h2>
                <figure class="parallax__item__imgWrap">
                    <div class="parallax__item__img"></div>
                </figure>
                <p class="parallax__item__desc">나는 항상 밤에 달을 볼 때마다, 이 작은 빛이 우리의 모든 문제를 해결할 수 있을 것 같다.</p>
            </section>
            <!--//section 05-->

            <section id="section6" class="parallax__item">
                <span class="parallax__item__num">06</span>
                <h2 class="parallax__item__title">Section 06</h2>
                <figure class="parallax__item__imgWrap">
                    <div class="parallax__item__img"></div>
                </figure>
                <p class="parallax__item__desc">달을 보며 꿈을 꾸지 마라. 당신의 발자취를 따라가라.</p>
            </section>
            <!--//section 06-->

            <section id="section7" class="parallax__item">
                <span class="parallax__item__num">07</span>
                <h2 class="parallax__item__title">Section 07</h2>
                <figure class="parallax__item__imgWrap">
                    <div class="parallax__item__img"></div>
                </figure>
                <p class="parallax__item__desc">책을 읽는 사람은 많은 삶을 살 수 있다.</p>
            </section>
            <!--//section 07-->

            <section id="section8" class="parallax__item">
                <span class="parallax__item__num">08</span>
                <h2 class="parallax__item__title">Section 08</h2>
                <figure class="parallax__item__imgWrap">
                    <div class="parallax__item__img"></div>
                </figure>
                <p class="parallax__item__desc">한 사람이 책 한 권을 읽을 때마다, 세상은 한 명씩 영혼이 더 나아진다.</p>
            </section>
            <!--//section 08-->

            <section id="section9" class="parallax__item">
                <span class="parallax__item__num">09</span>
                <h2 class="parallax__item__title">Section 09</h2>
                <figure class="parallax__item__imgWrap">
                    <div class="parallax__item__img"></div>
                </figure>
                <p class="parallax__item__desc">독서는 자유로운 인간이 되는 열쇠이다.</p>
            </section>
            <!--//section 09-->
        </div>

 

 

 

 

 

 

코드 보기 / JAVASCRIPT

        window.addEventListener("scroll",()=>{
            let scrollTop = window.pageYOffset || window.scrollY || document.documentElement.scrollTop; 
            
            document.querySelectorAll(".parallax__item").forEach((item, index) => {
                if(scrollTop >= item.offsetTop - 2) {
                    document.querySelectorAll(".parallax__nav li").forEach((el, index)=>{
                        el.classList.remove("active");
                    });
                    document.querySelector(".parallax__nav li:nth-child("+(index+1)+")").classList.add("active"); 
                }
            });

            document.querySelectorAll(".parallax__nav li a").forEach(li => {
                li.addEventListener("click", (el)=>{
                    el.preventDefault();
                    document.querySelector(li.getAttribute("href")).scrollIntoView({
                        behavior: "smooth"
                    })
                })
            })


            //info scroll

            document.querySelector(".scroll span").innerText = parseInt(scrollTop);
            document.querySelector(".info .offset1").innerText = document.getElementById("section1").offsetTop;
            document.querySelector(".info .offset2").innerText = document.getElementById("section2").offsetTop;
            document.querySelector(".info .offset3").innerText = document.getElementById("section3").offsetTop;
            document.querySelector(".info .offset4").innerText = document.getElementById("section4").offsetTop;
            document.querySelector(".info .offset5").innerText = document.getElementById("section5").offsetTop;
            document.querySelector(".info .offset6").innerText = document.getElementById("section6").offsetTop;
            document.querySelector(".info .offset7").innerText = document.getElementById("section7").offsetTop;
            document.querySelector(".info .offset8").innerText = document.getElementById("section8").offsetTop;
            document.querySelector(".info .offset9").innerText = document.getElementById("section9").offsetTop;

 

window.addEventListener("scroll",()=>{ 

: window에 스크롤 이벤트를 추가한다. 스크롤이 발생하면 이벤트 핸들러 함수가 실행된다.

 


let scrollTop = window.pageYOffset || window.scrollY || document.documentElement.scrollTop; 

: 현재 스크롤 위치를 scrollTop 변수에 할당한  후, window.pageYOffset, window.scrollY, document.documentElement.scrollTop 중 어떤 값이든 존재하는 값을 가져온다.

 


document.querySelectorAll(".parallax__item").forEach((item, index) => { 

: .parallax__item 클래스를 가진 모든 요소를 반복하여 순회한다. 각 요소는 item 변수에 할당된다.

 

if(scrollTop >= item.offsetTop - 2) { 

: 현재 스크롤 위치가 item 요소의 offsetTop 값보다 2보다 크거나 같은 경우 아래 코드를 실행한다.


document.querySelectorAll(".parallax__nav li").forEach((el, index)=>{ el.classList.remove("active"); }); 

: .parallax__nav li 요소들에서 active 클래스를 모두 제거한다.

 

document.querySelector(".parallax__nav li:nth-child("+(index+1)+")").classList.add("active"); 

: 현재 item 요소의 인덱스에 해당하는 .parallax__nav li 요소에 active 클래스를 추가한다.

 


document.querySelectorAll(".parallax__nav li a").forEach(li => { 

: .parallax__nav li a 요소를 반복하여 순회합니다. 각 요소는 li 변수에 할당된다.

 


li.addEventListener("click", (el)=>{ 

: li 요소에 클릭 이벤트 리스너를 추가합니다. 클릭이 발생하면 아래 코드가 실행된다.


el.preventDefault(); 

: 이벤트의 기본 동작을 방지한다.

 

document.querySelector(li.getAttribute("href")).scrollIntoView({behavior: "smooth"}) 

: 클릭한 링크의 href 속성값으로 이동하며, 부드러운 스크롤 효과를 적용한다.

 


document.querySelector(".scroll span").innerText = parseInt(scrollTop); 

: 현재 스크롤 위치를 .scroll 요소에 표시한다.

 


document.querySelector(".info .offset1").innerText = document.getElementById("section1").offsetTop; 

: 각 섹션의 offsetTop 값을 .info 요소 내 해당 클래스를 가진 요소의 텍스트에 할당한다.

728x90
반응형

 

 

완성 화면

 

 

 

 

코드 보기 / CSS

.slider__wrap {
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
        width: 800px;
        height: 450px;
        box-shadow: 0 50px 100px rgba(0,0,0,0.5);
      }
      .slider__img {
        position: relative;
        width: 100%;
        height: 100%;
        overflow: hidden;
      }
      .slider__img img {
        position: absolute;
        width: 100%;
        height: 100%;
        object-fit: cover;
        opacity: 0;
        transform: scale(1.2);
        transition: all 600ms ease-in-out;
      }
      .slider__img img.active {
        opacity: 1;
        transform: scale(1);
      }

      .slider__thumnail{
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, 240px);
        width: 100px;
        display: flex;
        justify-content: center;
        gap: 10px;
      }
      .slider__thumnail img{
        cursor: pointer;
        border: 3px solid transparent;
      }
      .slider__thumnail img.active {
        border: 3px solid #fff
      }
      .slider__btn a {
        position: absolute;
        top: 0;
        width: 100px;
        height: 100%;
        display: flex;
        justify-content: center;
        align-items: center;
        font-size: 13px;
        background-color: rgba(0, 0, 0, 0.3);
        color: #fff;
        transition: all 400ms ease-in-out;
      }
      .slider__btn a:hover{
        background-color: rgba(255, 255, 255, 0.2);
      }
      .slider__btn a.next {
        right: 0;
    }
    .slider__btn a.previous {
        left: 0;
    }

 

 

 

 

 

코드 보기 / HTML

<main id="main">
        <div class="slider__wrap">
            <div class="slider__img"></div>
            <div class="slider__thumnail"></div>
            <div class="slider__btn">
                <a href="#" class="previous">previous (이전)</a>
                <a href="#" class="next">next (다음)</a>
            </div>
        </div>
    </main>

 

 

 

 

 

코드 보기 / JAVASCRIPT

      let images = [
            "img/sliderEffect01-min.jpg",
            "img/sliderEffect02-min.jpg",
            "img/sliderEffect03-min.jpg",
            "img/sliderEffect04-min.jpg",
            "img/sliderEffect05-min.jpg",
            "img/sliderEffect07-min.jpg",
            "img/sliderEffect08-min.jpg",
            "img/sliderEffect09-min.jpg",
            "img/sliderEffect10-min.jpg"
        ];

        function ImageSlider (parent, images) {
            let currentIndex = 0;
            //선택자
            let Slider = {

                parent : parent,
                images : parent.querySelector(".slider__img"),
                thumnail : parent.querySelector(".slider__thumnail"),
                PreviousBtn : parent.querySelector(".slider__btn .previous"),
                NextBtn : parent.querySelector(".slider__btn .next")
            }

            //이미지를 화면에 출력
            Slider.images.innerHTML = images.map((image, index)=>{
                return `<img src="${image}" alt="이미지${index}">`
            }).join("");

            //큰 활성화 시 효과
            let imageNode = Slider.images.querySelectorAll("img");
            imageNode[currentIndex].classList.add("active");
            
            //썸네일에 이미지 출력
            Slider.thumnail.innerHTML = Slider.images.innerHTML;

             //썸네일에 active 활성화
             let thumnailNode = Slider.thumnail.querySelectorAll("img");
             thumnailNode[currentIndex].classList.add("active");


            
            //썸네일 클릭 시 이동
            // for(let i=0; i<thumnailNode.length; i++){
            //     thumnailNode[i].addEventListener("click", function(){
            //         Slider.thumnail.querySelector("img.active").classList.remove("active");
            //         thumnailNode[i].classList.add("active");

            //         imageNode[currentIndex].classList.remove("active");
            //         currentIndex = i;
            //         imageNode[currentIndex].classList.add("active")
            //     });
            // }
    
            thumnailNode.forEach((el, i) => {
                el.addEventListener("click", function(){
                Slider.thumnail.querySelector("img.active").classList.remove("active");
                el.classList.add("active");

                imageNode[currentIndex].classList.remove("active");
                currentIndex = i;
                imageNode[currentIndex].classList.add("active")
                });
            });

            //왼쪽 버튼 클릭
            Slider.PreviousBtn.addEventListener("click", function(){
               imageNode[currentIndex].classList.remove("active");
               currentIndex--;

               if(currentIndex < 0) currentIndex = images.length - 1;
               imageNode[currentIndex].classList.add("active");

                //활성화 되는 이미지와 같은 썸네일에 active 활성화
                Slider.thumnail.querySelector("img.active").classList.remove("active");
                thumnailNode[currentIndex].classList.add("active");

            });

            //오른쪽 버튼 클릭
            Slider.NextBtn.addEventListener("click", function(){
                imageNode[currentIndex].classList.remove("active");

                currentIndex = (currentIndex + 1) % images.length;
                imageNode[currentIndex].classList.add("active");

                //활성화 되는 이미지와 같은 썸네일에 active 활성화
                Slider.thumnail.querySelector("img.active").classList.remove("active");
                thumnailNode[currentIndex].classList.add("active");
            });
        }
        ImageSlider(document.querySelector(".slider__wrap"),images);

 

 

 

 

parent  : 슬라이더를 감싸는 부모 요소이다.
images : 슬라이더에 들어갈 이미지의 배열이다.



ImageSlider 함수

: Slider 객체를 생성하고, 이 객체 안에 슬라이더에 필요한 요소들을 할당한다.

parent : 슬라이더를 감싸는 부모 요소입니다. Slider 객체의 parent 프로퍼티에 할당된다.
images : 슬라이더에 들어갈 이미지 배열입니다. 현재 코드에서는 images를 사용하지 않는다.
images 프로퍼티는 parent.querySelector(".slider__img")로 할당된다. 이는 슬라이더 안에 있는 이미지 요소를 선택하는선택자이다.
thumnail 프로퍼티는 parent.querySelector(".slider__thumnail")로 할당된다. 이는 슬라이더의 썸네일을 나타내는 요소를 선택하는 선택자이다.
PreviousBtn 프로퍼티는 parent.querySelector(".slider__btn .previous")로 할당된다. 이는 이전 이미지로 이동하는 버튼을 나타내는 요소를 선택하는 선택자이다.
NextBtn 프로퍼티는 parent.querySelector(".slider__btn .next")로 할당된다. 이는 다음 이미지로 이동하는 버튼을 나타내는 요소를 선택하는 선택자이다.
이렇게 생성된 Slider 객체를 통해 슬라이더에 필요한 요소들에 접근할 수 있다.



Slider.images.innerHTML에 map() 함수를 사용하여 images 배열에서 이미지를 가져와 HTML 코드로 변환한다.
join() 함수는 배열의 각 요소를 문자열로 변환하고, 배열 요소 사이에 구분자를 삽입하여 하나의 문자열로 결합한다.
따라서 Slider.images.innerHTML에는 이미지 요소를 포함한 HTML 코드가 삽입된다.
다음으로, Slider.images.querySelectorAll("img")로 이미지 요소를 선택하여 imageNode에 할당한다.

currentIndex 변수로 현재 활성화된 이미지를 지정하고, 이를 imageNode에서 선택한 이미지 요소에 classList.add("active")를 사용하여 추가한다.
이로써 현재 활성화된 이미지에 대한 효과를 적용한다.

Slider.thumnail.innerHTML에도 Slider.images.innerHTML과 같은 방식으로 HTML 코드를 삽입합니다. 따라서 썸네일 이미지 요소도 이미지 슬라이더 이미지와 동일하게 표시된다.

마지막으로, Slider.thumnail.querySelectorAll("img")로 썸네일 이미지 요소를 선택하여 thumnailNode에 할당한다. 

이전과 마찬가지로 currentIndex 변수로 현재 활성화된 이미지를 지정하고, 이를 thumnailNode에서 선택한 이미지 요소에 classList.add("active")를 사용하여 추가한다. 이로써 현재 활성화된 썸네일에 대한 효과를 적용한다.






Slider.PreviousBtn.addEventListener() 함수:  왼쪽 버튼에 클릭 이벤트를 등록한다.

이벤트 핸들러 함수에서는 현재 활성화된 이미지에서 active 클래스를 제거하고, currentIndex 변수를 1 감소시킨다.
이후, currentIndex 값이 0보다 작으면, currentIndex 값을 images.length - 1로 설정하여, 마지막 이미지를 가리키도록 한다.
다음으로, imageNode[currentIndex]로 현재 활성화된 이미지 요소를 선택하고, classList.add("active")를 사용하여 해당 이미지에 active 클래스를 추가한다.
마지막으로, 활성화된 이미지와 동일한 썸네일 이미지에 active 클래스를 추가하여, 썸네일 이미지도 함께 활성화시킨다. 
이렇게 함으로써 왼쪽 버튼을 클릭했을 때, 이미지 슬라이더의 활성화된 이미지가 왼쪽으로 이동하고, 이에 따라 썸네일 이미지도 변경된다.






Slider.NextBtn.addEventListener() 함수 : 오른쪽 버튼에 클릭 이벤트를 등록합니다.

이벤트 핸들러 함수에서는 현재 활성화된 이미지에서 active 클래스를 제거하고, currentIndex 값을 (currentIndex + 1) % images.length으로 설정한다.
이렇게 함으로써, currentIndex 값이 images.length와 같아지면, 0으로 돌아가게 된다. 

이후, imageNode[currentIndex]로 현재 활성화된 이미지 요소를 선택하고, classList.add("active")를 사용하여 해당 이미지에 active 클래스를 추가한다.
마지막으로, 활성화된 이미지와 동일한 썸네일 이미지에 active 클래스를 추가하여, 썸네일 이미지도 함께 활성화 시킨다. 이렇게 함으로써 오른쪽 버튼을 클릭했을 때, 이미지 슬라이더의 활성화된 이미지가 오른쪽으로 이동하고, 이에 따라 썸네일 이미지도 변경된다.

 


마지막으로, ImageSlider() 함수를 호출하여, 이미지 슬라이더를 초기화합니다. 
함수에는 document.querySelector(".slider__wrap")로 슬라이더를 감싸는 부모 요소와 images 배열을 인자로 전달한다.
thumnailNode.forEach() 함수를 사용하여 thumnailNode에 있는 모든 썸네일 이미지 요소에 대해 반복합니다. 
각 썸네일 이미지 요소에 addEventListener() 함수를 사용하여 클릭 이벤트를 등록한다.

이벤트 핸들러 함수에서는 먼저 Slider.thumnail.querySelector("img.active")를 사용하여 현재 활성화된 썸네일 이미지를 선택하고, classList.remove("active")를 사용하여 해당 이미지에서 active 클래스를 제거한다.
다음으로, 클릭된 썸네일 이미지 요소에 classList.add("active")를 사용하여 active 클래스를 추가한다.
그리고 currentIndex 변수를 클릭된 썸네일 이미지 요소의 인덱스 값으로 업데이트한다.
이후, imageNode[currentIndex]로 현재 활성화된 이미지 요소를 선택하고, classList.remove("active")를 사용하여 해당 이미지에서 active 클래스를 제거한다.
마지막으로, imageNode[currentIndex]에 classList.add("active")를 사용하여 클릭된 썸네일 이미지와 해당하는 이미지를 활성화한다.

이렇게 함으로써 썸네일 이미지를 클릭했을 때, 해당 이미지가 활성화되고 이에 따라 이미지 슬라이더가 변경된다.

728x90
반응형

 

 

 

완성 화면

 

 

 

 

 

 

 

코드 보기 / CSS

.slider__wrap {
        width: 100%;
        height: 100vh;
        display: flex;
        justify-content: center;
        align-items: center;
      }
      .slider__img {                /*이미지가 나타나는(보이는) 영역*/
        position: relative;
        width: 800px;
        height: 450px;
        overflow: hidden;
      }
      .slider__inner {              /*이미지가 슬라이드 되는(움직이는) 영역*/
        display: flex;
        flex-wrap: wrap;
        width: 4800px;
        height: 450px;
      }
      .slider {
        position: relative;
        width: 800px;
        height: 450px;
      }
      .slider__btn a{
        position: absolute;
        top: 50%;
        transform: translateY(-50%);
        width: 60px;
        height: 60px;
        /* background-color: #fff; */
      }
      .slider__btn a.previous {
        left: 0;
        background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='icon icon-tabler icon-tabler-chevrons-left' width='60' height='60' viewBox='0 0 24 24' stroke-width='2' stroke='%23ffffff' fill='none' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath stroke='none' d='M0 0h24v24H0z' fill='none'/%3E%3Cpolyline points='11 7 6 12 11 17' /%3E%3Cpolyline points='17 7 12 12 17 17' /%3E%3C/svg%3E");
        text-indent: -99999px;
    }
      .slider__btn a.next {
        right: 0;
        background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='icon icon-tabler icon-tabler-chevrons-right' width='60' height='60' viewBox='0 0 24 24' stroke-width='2' stroke='%23ffffff' fill='none' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath stroke='none' d='M0 0h24v24H0z' fill='none'/%3E%3Cpolyline points='7 7 12 12 7 17' /%3E%3Cpolyline points='13 7 18 12 13 17' /%3E%3C/svg%3E");
        text-indent: -99999px;
    }
      .slider__btn a.previous:hover {
        background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='icon icon-tabler icon-tabler-chevrons-left' width='60' height='60' viewBox='0 0 24 24' stroke-width='2' stroke='%23000000' fill='none' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath stroke='none' d='M0 0h24v24H0z' fill='none'/%3E%3Cpolyline points='11 7 6 12 11 17' /%3E%3Cpolyline points='17 7 12 12 17 17' /%3E%3C/svg%3E");
      }
      .slider__btn a.next:hover {
        background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='icon icon-tabler icon-tabler-chevrons-right' width='60' height='60' viewBox='0 0 24 24' stroke-width='2' stroke='%23000000' fill='none' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath stroke='none' d='M0 0h24v24H0z' fill='none'/%3E%3Cpolyline points='7 7 12 12 7 17' /%3E%3Cpolyline points='13 7 18 12 13 17' /%3E%3C/svg%3E");
      }
      .slider__dot {
        position: absolute;
        left: 50%;
        bottom: 20px;
        transform: translateX(-50%);
      }
      .slider__dot .dot {
        width: 20px;
        height: 20px;
        background-color: #fff;
        display: inline-block;
        border-radius: 50%;
        text-indent: -9999px;
        transition: all 0.5s;
        margin: 3px;
      }
      .slider__dot .dot.active{
        background-color: rgba(0, 0, 0, 1.5);
      }

 

 

 

 

 

코드 보기 / HTML

<div class="slider__wrap">
            <div class="slider__img">
                <div class="slider__inner">
                    <div class="slider s01"><img src="./img/sliderEffect02-min.jpg" alt="이미지1"></div>
                    <div class="slider s02"><img src="./img/sliderEffect04-min.jpg" alt="이미지2"></div>
                    <div class="slider s03"><img src="./img/sliderEffect06-min.jpg" alt="이미지3"></div>
                    <div class="slider s04"><img src="./img/sliderEffect08-min.jpg" alt="이미지4"></div>
                    <div class="slider s05"><img src="./img/sliderEffect07-min.jpg" alt="이미지5"></div>
                </div>
            </div>
            <div class="slider__btn">
                <a href="#" class="previous">previous (이전)</a>
                <a href="#" class="next">next (다음)</a>
            </div>
            <div class="slider__dot">
                <!-- <a href="#" class="dot active">1</a>
                <a href="#" class="dot">2</a>
                <a href="#" class="dot">3</a>
                <a href="#" class="dot">4</a>
                <a href="#" class="dot">5</a> -->
            </div>
        </div>

 

 

 

 

 

코드 보기  / JAVASCRIPT

 const sliderWrap = document.querySelector(".slider__wrap");
        const sliderImg = sliderWrap.querySelector(".slider__img");
        const sliderInner = sliderWrap.querySelector(".slider__inner");
        const slider = sliderWrap.querySelectorAll(".slider");
        const sliderDot = sliderWrap.querySelector(".slider__dot");
        const sliderBtn = sliderWrap.querySelectorAll(".slider__btn a");

        let currentIndex = 0;                   //현재 보이는 (가장 위에 있는) 이미지
        let sliderInterval = 3000;              //다음 이미지로 넘어가는 간격의 시간
        let sliderCount = slider.length;        //전체 이미지의 총 수
        let sliderWidth = sliderWrap.querySelector(".slider__img").offsetWidth;            //이미지 width 값
        let dotIndex = "";
        
        function init() {
            //슬라이드 될 이미지만큼 도트 메뉴 생성
            slider.forEach(()=>dotIndex += "<a href='#' class='dot'></a>");
            sliderDot.innerHTML = dotIndex;
            
            //도트 메뉴 활성화 (첫 번째)
            sliderDot.firstChild.classList.add("active");
        }
        init();

        //슬라이드 이미지의 위치 변경
        function moveSlider(num){
            sliderInner.style.transition = "all 400ms";
            sliderInner.style.transform = `translateX(-${sliderWidth * num}px)`;
            currentIndex = num;


            //버튼 활성화
            let DotActive = document.querySelectorAll(".slider__dot .dot");
            DotActive.forEach((active)=>active.classList.remove("active"));
            DotActive[num].classList.add("active");
        }

        // 도트 메뉴 클릭 시 이동
        sliderDot.querySelectorAll(".dot").forEach((dot, index) => {
            dot.addEventListener("click", () => {
                moveSlider(index);
            });
        });

        //슬라이드 버튼 클릭 시
        sliderBtn.forEach((btn, index)=>{
            btn.addEventListener("click",()=>{
                let previousIndex = (currentIndex + (sliderCount - 1)) % sliderCount;
                let nextIndex = (currentIndex + 1) % sliderCount;

                if(btn.classList.contains("previous")){
                    moveSlider(previousIndex);
                } else {
                    moveSlider(nextIndex);
                }
            });
        });

 

 

 

sliderWrap : 슬라이드 이미지를 감싸는 부모 요소
sliderImg : 슬라이드 이미지들을 감싸고 있는 요소
sliderInner : 슬라이드 이미지들을 실제로 이동시키는 요소
slider : 각 슬라이드 이미지를 나타내는 요소들의 배열
sliderDot : 슬라이드 도트 메뉴를 감싸는 요소
sliderBtn : 슬라이드 이전/다음 버튼을 나타내는 요소들의 배열
currentIndex : 현재 보이는 (가장 위에 있는) 이미지의 인덱스
sliderInterval : 다음 이미지로 넘어가는 간격의 시간 (밀리초 단위)
sliderCount : 전체 이미지의 총 개수
sliderWidth : 이미지의 너비 값
dotIndex : 도트 메뉴를 생성하기 위해 사용되는 문자열 변수


init() 함수 :  각 슬라이드 이미지 수만큼 도트 메뉴를 생성하고, 첫 번째 도트 메뉴를 활성화하는 역할을 한다.


moveSlider() 함수 : 슬라이드 이미지의 위치를 변경하고, 현재 보이는 이미지의 인덱스를 업데이트한다.

이 때, 도트 메뉴에서도 현재 보이는 이미지의 인덱스에 해당하는 도트 메뉴를 활성화합니다.

 

sliderDot.querySelectorAll(".dot").forEach : sliderDot 요소 내부의 .dot 요소들에 대해 forEach()를 사용하여 클릭 이벤트를 등록한다. 도트 메뉴를 클릭하면 해당 인덱스에 해당하는 슬라이드 이미지를 보여준다.

 

 sliderBtn.forEach((btn, index) : sliderBtn 요소들에 대해서도 forEach()를 사용하여 클릭 이벤트를 등록한다.

이전/다음 버튼을 클릭하면 이전/다음 슬라이드 이미지를 보여준다.

previousIndex와 nextIndex는 이전/다음 슬라이드 이미지의 인덱스를 계산하는데 사용된다.

이 때, % 연산자를 사용하여 인덱스의 범위가 슬라이드 이미지 수 내에 유지되도록 한다.

 

 

 

 

init()

 

slider.forEach(()=>dotIndex += "<a href='#' class='dot'></a>");

: slider 변수에 할당된 이미지들의 개수만큼 dotIndex 변수에 문자열을 추가한다.

이 문자열은 각 이미지에 해당하는 도트 메뉴를 생성하는 HTML 코드가 된다.


sliderDot.innerHTML = dotIndex;

: sliderDot 변수에 할당된 도트 메뉴를 가지고 있는 HTML 요소에 dotIndex 문자열을 할당하여 도트 메뉴를 생성한다.

sliderDot.firstChild.classList.add("active");

: 첫 번째 도트 메뉴를 활성화합니다.

이를 위해 sliderDot 요소의 첫 번째 자식 요소에 active 클래스를 추가합니다.

 

 

 

 

 

moveSlider

 

sliderInner.style.transition = "all 400ms";

: 슬라이드 이미지가 이동할 때 애니메이션 효과를 추가한다.

all은 모든 속성에 대해 애니메이션을 적용하며, 400ms는 애니메이션 시간을 0.4초로 지정한다.

sliderInner.style.transform = translateX(-${sliderWidth * num}px);

: 슬라이드 이미지를 이동시킨다.

translateX는 요소를 수평 방향으로 이동시키는 CSS 함수이며, - ${sliderWidth * num}px는 이동 거리를 나타낸다. sliderWidth는 슬라이드 이미지의 가로 크기를 나타내며, num은 이동할 이미지의 인덱스이다.

currentIndex = num;

: 현재 보여지는 이미지의 인덱스를 num으로 업데이트 한다.

let DotActive = document.querySelectorAll(".slider__dot .dot");

: 도트 메뉴를 나타내는 HTML 요소를 선택한다.

DotActive.forEach((active)=>active.classList.remove("active"));

:도트 메뉴의 활성화 클래스 active를 모두 제거한다.

DotActive[num].classList.add("active");

: num에 해당하는 이미지에 해당하는 도트 메뉴에 active 클래스를 추가하여 해당 도트 메뉴를 활성화한다.

 

 

 

 

 

sliderDot.querySelectorAll(".dot").forEach((dot, index)

 

 

sliderDot.querySelectorAll(".dot")

: 도트 메뉴 요소를 모두 선택한다.

forEach((dot, index) => {...})

: 선택된 도트 메뉴 요소마다 콜백 함수를 실행한다.

이때, dot은 각 도트 메뉴 요소를 나타내며, index는 해당 요소의 인덱스를 나타낸다.

dot.addEventListener("click", () => {...})

:  각 도트 메뉴 요소에 클릭 이벤트 핸들러를 등록한다.

moveSlider(index)

: 클릭한 도트 메뉴에 해당하는 이미지를 화면에 보여주도록 moveSlider() 함수를 호출한다.

index는 클릭한 도트 메뉴 요소의 인덱스이며, 이를 moveSlider() 함수의 인자로 전달하여 해당 이미지를 화면에 보여주게 된다.

 

 

 

 

 

 sliderBtn.forEach((btn, index)

: 이전/다음 버튼을 클릭할 때 실행되는 이벤트 핸들러를 등록한다.


sliderBtn.forEach((btn, index) => {...})

:  이전/다음 버튼 요소를 모두 선택한다.

btn.addEventListener("click", () => {...})

: 각 버튼 요소에 클릭 이벤트 핸들러를 등록한다.

let previousIndex = (currentIndex + (sliderCount - 1)) % sliderCount;

: 이전 버튼을 클릭했을 때 보여줄 이미지의 인덱스를 계산한다.

currentIndex는 현재 화면에 보이는 이미지의 인덱스이며, (sliderCount - 1)은 이미지 배열의 마지막 인덱스를 의미한다.

% sliderCount는 계산 결과가 이미지 배열의 범위를 벗어나지 않도록 인덱스를 조정한다.

let nextIndex = (currentIndex + 1) % sliderCount;

: 다음 버튼을 클릭했을 때 보여줄 이미지의 인덱스를 계산한다.

currentIndex는 현재 화면에 보이는 이미지의 인덱스이며, + 1은 다음 인덱스를 나타낸다

% sliderCount는 계산 결과가 이미지 배열의 범위를 벗어나지 않도록 인덱스를 조정한다.

if(btn.classList.contains("previous")) {...}

: 클릭한 버튼이 이전 버튼인지 다음 버튼인지 판별한다.

이전 버튼을 클릭한 경우 previousIndex에 해당하는 이미지를 보여주도록 moveSlider(previousIndex) 함수를 호출하며, 다음 버튼을 클릭한 경우 nextIndex에 해당하는 이미지를 보여주도록 moveSlider(nextIndex) 함수를 호출한다.

 

 

 

 

 

 

728x90
반응형

 

 

 

완성 화면

 

 

 

 

 

 

 

코드 보기 / JAVASCRIPT

	const sliderWrap = document.querySelector(".slider__wrap");
        const sliderImg = sliderWrap.querySelector(".slider__img");
        const sliderInner = sliderWrap.querySelector(".slider__inner");
        const slider = sliderWrap.querySelectorAll(".slider");

        let currentIndex = 0;                   //현재 보이는 (가장 위에 있는) 이미지
        let sliderInterval = 3000;              //다음 이미지로 넘어가는 간격의 시간
        let sliderCount = slider.length;        //전체 이미지의 총 수
        let sliderWidth = sliderWrap.querySelector(".slider__img").offsetWidth;            //이미지 width 값
        let sliderHeight = sliderWrap.querySelector(".slider__img").offsetHeight;            //이미지 height 값
        let sliderClone = sliderInner.firstElementChild.cloneNode(true);        //슬라이드 이미지 중 첫번째 요소(이미지)를 복사

        
        //sliderClone을 슬라이드 될 이미지의 마지막에 추가(붙여넣기)
        sliderInner.appendChild(sliderClone);

        function sliderEffect() {
            currentIndex++;

            sliderInner.style.transition = "all 0.5s";
            sliderInner.style.transform = `translateY(-${sliderHeight*currentIndex}px)`;

            //마지막 슬라이드일 때
            if(currentIndex == sliderCount){
                setTimeout(()=>{
                    sliderInner.style.transition = "0s";
                    sliderInner.style.transform = "translateY(0px)"
                },500);
                currentIndex = 0;
            }
        }
    
        setInterval(sliderEffect, sliderInterval);

 


querySelector() 메서드를 사용하여 HTML 문서에서 이미지 슬라이드 쇼의 요소들을 가져온다.

querySelector() 메서드는 CSS 선택자를 사용하여 문서에서 요소를 선택할 수 있다.

그리고 let 키워드를 사용하여 몇 가지 변수를 정의한다. 

currentIndex 변수는 현재 보이는 이미지의 인덱스를 저장하며, sliderInterval 변수는 이미지가 전환되는 간격을 나타낸다. 

sliderCount 변수는 전체 이미지의 수를 저장하고, sliderHeight 변수는 이미지의 높이를 저장한다.

cloneNode() 메서드를 사용하여 첫 번째 이미지를 복제한 sliderClone 요소를 만들고, appendChild() 메서드를 사용하여 sliderClone 요소를 sliderInner 요소의 마지막 자식으로 추가한다. 

이렇게 하면 슬라이드가 무한으로 반복되는 효과를 구현할 수 있다.

그리고 sliderEffect() 함수를 만든다. 

이 함수는 setInterval() 메서드를 사용하여 일정한 간격으로 호출되며, currentIndex 변수를 증가시킨다. 

그리고 style 속성을 사용하여 sliderInner 요소를 이동시키는 애니메이션 효과를 적용한다. 

마지막 슬라이드일 경우 setTimeout() 메서드를 사용하여 일정 시간 후에 sliderInner 요소를 초기화시킨다.

마지막으로 setInterval() 메서드를 사용하여 sliderEffect() 함수를 일정한 간격으로 호출한다.

 

 

 

코드 보기 / GSAP

 sliderInner.appendChild(sliderClone);

        function sliderEffect() {
            currentIndex++;
            gsap.to(sliderInner, {
                duration: 0.5,
                y: -sliderHeight * currentIndex,
                ease: "power2.inOut",
                onComplete: () => {
                    if (currentIndex == sliderCount) {
                        gsap.set(sliderInner, { y: 0 });
                        currentIndex = 0;
                    }
                },
            });
        }
        setInterval(sliderEffect, sliderInterval);

 

이 코드에서는 sliderHeight 변수를 사용하여 이미지의 높이를 계산하여 이동합니다.


gsap.to() 메소드를 사용하여 sliderInner 요소를 이동시키고, duration 옵션을 사용하여 애니메이션 시간을 설정한다.

y 옵션을 사용하여 sliderInner 요소를 y축 방향으로 이동시키고, ease 옵션을 사용하여 이동 효과를 설정합니다.

onComplete 콜백 함수는 애니메이션이 끝난 후 실행되며, 현재 이미지가 마지막 이미지인 경우 슬라이더를 초기 상태로 되돌리고, currentIndex 변수를 0으로 설정한다.
setInterval() 메소드를 사용하여 sliderEffect() 함수를 주기적으로 실행하여 이미지 슬라이더를 자동으로 이동시킨다.

 

 

 

코드 보기 / Jquery

 setInterval(() => {
            const sliderClone = $(".slider__inner .slider:first-child").clone(true);
            $(".slider__inner").append(sliderClone);
            currentIndex++;
            let sliderHeight = -currentIndex * $(".slider__img").height();
            $(".slider__inner").css("transition", "all 0.5s").css("transform", `translateY(${sliderHeight}px)`);
            if (currentIndex == sliderCount) {
                setTimeout(() => {
                $(".slider__inner").css("transition", "0s").css("transform", "translateY(0px)");
                }, 500);
                currentIndex = 0;
            }
        }, sliderInterval);

 

 

setInterval(() => { ... })

: setInterval( ) 함수는 일정한 간격으로 함수를 반복 실행한다.

 

 

const sliderClone = $(".slider__inner .slider:first-child").clone(true);
: 첫번째 슬라이드를 복제하여 sliderClone 변수에 할당한다.

이 때, true를 인자로 넘겨서 하위 요소들까지 모두 복제하도록 한다.

 

 

 

$(".slider__inner").append(sliderClone);
: 복제된 슬라이드를 슬라이더 내부에 추가한다.

 

 

currentIndex++;
: 현재 슬라이드의 인덱스 값을 1 증가시킵니다.

 

 

let sliderHeight = -currentIndex * $(".slider__img").height();
: sliderHeight 변수에는 현재 슬라이드의 인덱스를 이용하여 슬라이더가 이동해야 할 거리를 계산한다.

이 값은 현재 슬라이드의 인덱스와 슬라이드 이미지의 높이를 곱한 것의 음수값으로 설정된다.

음수값을 사용하는 이유는 슬라이드가 이동해야 할 거리가 슬라이더의 방향과 반대이기 때문이다.

 

 

$(".slider__inner").css("transition", "all 0.5s").css("transform", `translateY(${sliderHeight}px)`);
: CSS transition 속성과 transform 속성을 이용하여 슬라이더가 이동하도록 한다.

transition 속성은 0.5초 동안 모든 속성에 대한 전환 효과를 설정하고, transform 속성은 translateY 함수를 사용하여 sliderHeight 값만큼 슬라이더를 이동시킨다.

 

 

if (currentIndex == sliderCount) {
: 현재 슬라이드 인덱스가 전체 슬라이드 개수와 같아졌을 때, 모든 슬라이드를 보여준 것이므로 다시 처음부터 시작해야 한다.

 

 

$(".slider__inner").css("transition", "0s").css("transform", "translateY(0px)");
: 슬라이더를 초기화하여 다시 처음부터 시작하도록 한다.

transition 속성을 0초로 설정하여 애니메이션 효과가 없도록 한다.

 

 

currentIndex = 0;
: 현재 슬라이드 인덱스 값을 0으로 초기화합니다.

 

}, sliderInterval);
: setInterval() 함수의 두번째 인자로 전달된 sliderInterval 변수는 슬라이드 전환 간격을 지정하는 값이다.

이 값은 일정한 간격으로 실행되는 함수를 정의하는 람다 함수의 실행 주기를 지정한다.

 

 

 

 

 

 

 

728x90
반응형

 

완성 화면

 

 

 

 

 

 

 

 

코드 보기 / CSS

.slider__wrap {
        width: 100%;
        height: 100vh;
        display: flex;
        justify-content: center;
        align-items: center;
      }
      .slider__img {                /*이미지가 나타나는(보이는) 영역*/
        position: relative;
        width: 800px;
        height: 450px;
        overflow: hidden;
      }
      .slider__inner {              /*이미지가 슬라이드 되는(움직이는) 영역*/
        display: flex;
        flex-wrap: wrap;
        width: 4800px;
        height: 450px;
      }
      .slider {
        position: relative;
        width: 800px;
        height: 450px;
      }

 

 

코드 보기 / HTML

 <div class="slider__wrap">
            <div class="slider__img">
                <div class="slider__inner">
                    <div class="slider s01"><img src="./img/sliderEffect01-min.jpg" alt="이미지1"></div>
                    <div class="slider s02"><img src="./img/sliderEffect03-min.jpg" alt="이미지2"></div>
                    <div class="slider s03"><img src="./img/sliderEffect05-min.jpg" alt="이미지3"></div>
                    <div class="slider s04"><img src="./img/sliderEffect07-min.jpg" alt="이미지4"></div>
                    <div class="slider s05"><img src="./img/sliderEffect09-min.jpg" alt="이미지5"></div>
                </div>
            </div>
        </div>

 

 

 

 

코드 보기 / JAVASCRIPT

const sliderWrap = document.querySelector(".slider__wrap");
        const sliderImg = sliderWrap.querySelector(".slider__img");
        const sliderInner = sliderWrap.querySelector(".slider__inner");
        const slider = sliderWrap.querySelectorAll(".slider");

        let currentIndex = 0;                   //현재 보이는 (가장 위에 있는) 이미지
        let sliderInterval = 3000;              //다음 이미지로 넘어가는 간격의 시간
        let sliderCount = slider.length;        //전체 이미지의 총 수
        let sliderWidth = sliderWrap.querySelector(".slider__img").offsetWidth;            //이미지 width 값
        let sliderClone = sliderInner.firstElementChild.cloneNode(true);        //슬라이드 이미지 중 첫번째 요소(이미지)를 복사

        
        //sliderClone을 슬라이드 될 이미지의 마지막에 추가(붙여넣기)
        sliderInner.appendChild(sliderClone);

        function sliderEffect() {
            currentIndex++;

            sliderInner.style.transition = "all 0.5s";
            sliderInner.style.transform = `translateX(-${sliderWidth*currentIndex}px)`;

            //마지막 슬라이드일 때
            if(currentIndex == sliderCount){
                setTimeout(()=>{
                    sliderInner.style.transition = "0s";
                    sliderInner.style.transform = "translateX(0px)"
                },500);
                currentIndex = 0;
            }
        }
    
        setInterval(sliderEffect, sliderInterval);

 

querySelector() 메서드를 사용하여 HTML 문서에서 이미지 슬라이드 쇼의 요소들을 가져온다.

querySelector() 메서드는 CSS 선택자를 사용하여 문서에서 요소를 선택할 수 있다.

그리고 let 키워드를 사용하여 몇 가지 변수를 정의한다. 

currentIndex 변수는 현재 보이는 이미지의 인덱스를 저장하며, sliderInterval 변수는 이미지가 전환되는 간격을 나타낸다. 

sliderCount 변수는 전체 이미지의 수를 저장하고, sliderWidth 변수는 이미지의 너비를 저장한다.

cloneNode() 메서드를 사용하여 첫 번째 이미지를 복제한 sliderClone 요소를 만들고, appendChild() 메서드를 사용하여 sliderClone 요소를 sliderInner 요소의 마지막 자식으로 추가한다. 

이렇게 하면 슬라이드가 무한으로 반복되는 효과를 구현할 수 있다.

그리고 sliderEffect() 함수를 만든다. 

이 함수는 setInterval() 메서드를 사용하여 일정한 간격으로 호출되며, currentIndex 변수를 증가시킨다. 

그리고 style 속성을 사용하여 sliderInner 요소를 이동시키는 애니메이션 효과를 적용한다. 

마지막 슬라이드일 경우 setTimeout() 메서드를 사용하여 일정 시간 후에 sliderInner 요소를 초기화시킨다.

마지막으로 setInterval() 메서드를 사용하여 sliderEffect() 함수를 일정한 간격으로 호출한다.

 

 

 

 

 

코드 보기 / GSAP

<!-- GSAP -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.5/gsap.min.js"></script>
    <script>
        sliderInner.appendChild(sliderClone);

        function sliderEffect() {
            currentIndex++;
            gsap.to(sliderInner, {
                duration: 0.5,
                x: -sliderWidth * currentIndex,
                ease: "power2.inOut",
                onComplete: () => {
                    if (currentIndex == sliderCount) {
                        gsap.set(sliderInner, { x: 0 });
                        currentIndex = 0;
                    }
                },
            });
        }
        setInterval(sliderEffect, sliderInterval);
    </script>

 

이전 코드와 거의 비슷하지만, 이 코드는 GreenSock Animation Platform (GSAP) 라이브러리를 사용하여 이미지 슬라이드 쇼를 구현하는 코드이다.

먼저 HTML 문서에서 GSAP 라이브러리를 가져오기 위해 script 요소를 추가한다. 

그리고 sliderClone 요소를 sliderInner 요소의 마지막 자식으로 추가하는 코드는 이전(위의 JAVASCRIPT)과 동일하다.

sliderEffect() 함수도 이전과 매우 유사하다.

하지만 이번에는 gsap.to() 메서드를 사용하여 sliderInner 요소를 이동시키는 애니메이션을 적용한다. 

이 메서드는 GSAP 라이브러리에서 제공하는 TweenMax 클래스의 메서드 중 하나이다.

gsap.to() 메서드는 첫 번째 매개변수로 애니메이션을 적용할 대상 요소를 받으며, 두 번째 매개변수로 애니메이션 옵션 객체를 받는데, 이 객체에는 애니메이션의 속성들을 설정할 수 있다.

여기서는 duration 속성을 사용하여 애니메이션의 지속시간을 설정하고, x 속성을 사용하여 요소를 이동시키는 값을 설정한다. 

그리고 ease 속성을 사용하여 애니메이션의 이징(easing) 방법을 설정한다. 

마지막으로 onComplete 속성을 사용하여 애니메이션이 끝난 후에 실행할 함수를 정의한다.

onComplete 속성에서는 현재 인덱스가 마지막 이미지를 가리킬 때 gsap.set() 메서드를 사용하여 sliderInner 요소의 위치를 초기하고, currentIndex 변수를 0으로 설정합니다.
마지막으로 setInterval() 메서드를 사용하여 sliderEffect() 함수를 일정한 간격으로 호출한다.

 

 

 

 

코드 보기 / Jqurey

<!-- Jquery -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.13.2/jquery-ui.min.js"></script>
    <script>
        setInterval(() => {
            const sliderClone = $(".slider__inner .slider:first-child").clone(true);
            $(".slider__inner").append(sliderClone);
            currentIndex++;
            let sliderWidth = -currentIndex * $(".slider__img").width();
            $(".slider__inner").css("transition", "all 0.5s").css("transform", `translateX(${sliderWidth}px)`);
            if (currentIndex == sliderCount) {
                setTimeout(() => {
                $(".slider__inner").css("transition", "0s").css("transform", "translateX(0px)");
                }, 500);
                currentIndex = 0;
            }
        }, sliderInterval);
    </script>

 

setInterval() 함수를 사용하여 이미지가 일정한 간격으로 슬라이드되도록 한다. 

각 슬라이드 이동 시마다, 첫 번째 이미지 요소를 복사하여 마지막 이미지 뒤에 추가한다.

그리고 currentIndex 변수를 증가시키고, 이동할 거리를 계산하여 $(".slider__inner") 요소에 CSS transform 속성을 적용하여 이동한다. 

이 때, sliderWidth 변수에 -currentIndex * $(".slider__img").width()를 할당하여 슬라이드할 거리를 계산한다.

이후, if 조건문에서 현재 이미지가 마지막 이미지인지 확인하고, 마지막 이미지에 도달하면 다시 첫 번째 이미지로 이동시키기 위해 CSS transform 속성을 초기화 시킨다.

이 방법은 간단한 jQuery 코드를 사용하여 이미지 슬라이드 쇼를 구현하며, setInterval()과 CSS transform 속성을 사용하여 이미지를 슬라이드한다.

 

 

 

 

 

 

 

 

 

728x90
반응형

 

완성 화면 (Effect01)

 

 

 

 

코드 보기 / CSS

.slider__wrap {
        width: 100%;
        height: 100vh;
        align-items: center;
        justify-content: center;
        display: flex;
       }
       .slider__img {
        position: relative;
        width: 800px;
        height: 450px;
        overflow: hidden;
       }
       .slider {
        position: absolute;
        left: 0;
        top: 0;
        opacity: 0;
        transition: all 0.5s;
       }
       .slider::before {
        position: absolute;
        left: 5px;
        top: 5px;
        background: rgba(0, 0, 0, 0.5);
        color: #fff;
        padding: 5px 10px;
       }
       .slider:nth-child(1)::before {content: '이미지1';}
       .slider:nth-child(2)::before {content: '이미지2';}
       .slider:nth-child(3)::before {content: '이미지3';}
       .slider:nth-child(4)::before {content: '이미지4';}
       .slider:nth-child(5)::before {content: '이미지5';}
       .slider:nth-child(1) {z-index: 5;}
       .slider:nth-child(2) {z-index: 4;}
       .slider:nth-child(3) {z-index: 3;}
       .slider:nth-child(4) {z-index: 2;}
       .slider:nth-child(5) {z-index: 1;}

 

 

 

코드 보기 / HTML

<body class="img01 bg01 font01">
    <header id="header">
        <h1>Javascript Slider Effect 01</h1>
        <p>슬라이드 이펙트 </p>
        <ul>
            <li class="active"><a href="sliderEffect01.html">1</a></li>
            <li><a href="sliderEffect02.html">2</a></li>
            <li><a href="sliderEffect03.html">3</a></li>
            <li><a href="sliderEffect04.html">4</a></li>
            <li><a href="sliderEffect05.html">5</a></li>
            <li><a href="sliderEffect06.html">6</a></li>
        </ul>
    </header> 
    <!-- header -->

    <main id="main">
        <div class="slider__wrap">
            <div class="slider__img">
                <div class="slider"><img src="./img/sliderEffect01-min.jpg" alt="이미지1"></div>
                <div class="slider"><img src="./img/sliderEffect02-min.jpg" alt="이미지2"></div>
                <div class="slider"><img src="./img/sliderEffect03-min.jpg" alt="이미지3"></div>
                <div class="slider"><img src="./img/sliderEffect04-min.jpg" alt="이미지4"></div>
                <div class="slider"><img src="./img/sliderEffect05-min.jpg" alt="이미지5"></div>
            </div>
        </div>
    </main>
    <!-- main -->

    <footer id="footer">
        <a href="mailto:goed0522@gmail.com">goed0522@gmail.com</a>
    </footer>
    <!-- footer -->

 

 

 

코드 보기  / JAVASCRIPT  - 기본

       const sliderWrap = document.querySelector(".slider__wrap");
        const sliderImg = sliderWrap.querySelector(".slider__img");
        const slider = sliderWrap.querySelectorAll(".slider");

        let currentIndex = 0;       //현재 보이는 (가장 위에 있는) 이미지
        let sliderCount = slider.length;        //전체 이미지의 총 수
        let sliderInterval = 3000;              //다음 이미지로 넘어가는 간격의 시간

        setInterval (()=>{
            let nextIndex = (currentIndex+1) % sliderCount;

            slider[currentIndex].style.opacity = "0";      //가장 위에 있는 이미지 숨김
            slider[nextIndex].style.opacity = "1";      //그 다음 이미지를 나타냄

            currentIndex = nextIndex;
            console.log(currentIndex);
        },sliderInterval);

 

const sliderWrap = document.querySelector(".slider__wrap"); 

: HTML에서 .slider__wrap 클래스를 가진 요소를 찾아서 sliderWrap 변수에 할당한다.


const sliderImg = sliderWrap.querySelector(".slider__img"); 

: sliderWrap 내부에서 .slider__img 클래스를 가진 요소를 찾아서 sliderImg 변수에 할당한다.


const slider = sliderWrap.querySelectorAll(".slider"); 

: sliderWrap 내부에서 .slider 클래스를 가진 모든 요소를 찾아서 slider 변수에 할당한다.


let currentIndex = 0; 

: 슬라이드의 초기값으로 가장 처음 이미지를 보이도록 한다.


let sliderCount = slider.length; 

: slider 변수에 저장된 모든 슬라이드의 개수를 구해 sliderCount 변수에 할당한다.


let sliderInterval = 3000; 

: 슬라이드가 변경되는 간격을 3초로 설정한다.


setInterval (()=> { ... },sliderInterval); 

: setInterval 함수를 사용해 일정한 시간 간격으로 함수를 반복 실행한다.


(currentIndex+1) % sliderCount 

: currentIndex에 1을 더한 후, sliderCount로 나눈 나머지 값으로 다음 슬라이드의 인덱스 값을 구한다.

이를 통해 마지막 슬라이드가 보여진 후 첫 번째 슬라이드로 돌아갈 수 있다.


slider[currentIndex].style.opacity = "0"; 

: 현재 보여지고 있는 슬라이드의 투명도(opacity) 값을 0으로 변경해 화면에서 숨긴다.


slider[nextIndex].style.opacity = "1"; 

: 다음 슬라이드의 투명도 값을 1로 변경해 화면에 나타낸다.


currentIndex = nextIndex; 

: nextIndex를 currentIndex에 할당해 다음번 슬라이드 변경 시 이전 슬라이드의 인덱스 값을 알 수 있다.

 

 

 

코드 보기  / JAVASCRIPT  - GSAP

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.5/gsap.min.js"></script>
    <script>
        gsap.set(".slider:nth-child(n+2)",{opacity:0});

         setInterval(()=>{
            let nextIndex = (currentIndex + 1) % sliderCount;

                gsap.to(`.slider:nth-child(${currentIndex+1})`,{
                   opacity: 0,
                    duration : 1,
                    ease: "power2.inOut"
                });

                gsap.to(`.slider:nth-child(${nextIndex+1})`,{
                   opacity: 1,
                    duration : 1,
                    ease: "power2.inOut"
                });

                currentIndex = nextIndex;
        },sliderInterval);
    </script>

 

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.5/gsap.min.js"></script>

:GSAP 라이브러리를 불러온다.

 

gsap.set(".slider:nth-child(n+2)",{opacity:0});

:슬라이드 쇼 이미지 중 첫 번째 이미지를 제외한 나머지 이미지를 숨긴다.

 

setInterval (()=> { ... },sliderInterval); 

: setInterval 함수를 사용해 일정한 시간 간격으로 함수를 반복 실행한다.

 

gsap.to(`.slider:nth-child(${currentIndex+1})`,{
        opacity: 0 or 1,
        duration : 1,
        ease: "power2.inOut"
    });

: gsap.to( )를 사용하여 현재 이미지와 다음 이미지를 페이드 인/ 아웃 효과와 함께 보여주고 숨긴다.

ease 속성은 애니메이션의 이징 효과를 설정한다.

power2.inOut은 느리게 시작하고 빠르게 가속하는 이징 효과이다.

 

currentIndex = nextIndex;

: 현재 이미지를 나타내는 currentIndex 변수를 업데이트 한다.

 

이렇게 구현된 코드는 기존의 setInterval( ) 함수 대신 gsap.to( ) 메서드를 사용하므로 애니메이션이 더 부드럽게 작동한다.

 

 

 

 

코드 보기  / JAVASCRIPT  - Jquery

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.13.2/jquery-ui.min.js"></script>
<script>
    setInterval(()=>{
        let nextIndex = (currentIndex+1) % sliderCount;

        $(slider[currentIndex]).animate({opacity: 0}, 500);
        $(slider[nextIndex]).animate({opacity: 1}, 500);

        currentIndex = nextIndex;
    },sliderInterval);
</script>

 

setInterval 함수를 이용하여 일정 간격으로 이미지를 변경한다.

currentIndex 변수는 현재 보이는 이미지의 인덱스를, sliderCount 변수는 전체 이미지의 개수를 저장한다. 

slider 변수는 이미지들을 나타내는 jQuery 객체이다.

jQuery의 animate 함수를 이용하여 이미지의 opacity 속성을 변경하여 이미지가 서서히 사라지거나 나타나도록 한다. 

이 때, 500은 애니메이션의 지속 시간을 나타내며, currentIndex 변수를 nextIndex로 변경하여 다음에 보일 이미지를 지정한다.

 

 

 

 

 

완성 화면 (Effect02)

 

 

 

 

 

코드 보기 / CSS

.slider__wrap {
        width: 100%;
        height: 100vh;
        display: flex;
        justify-content: center;
        align-items: center;
      }
      .slider__img {                /*이미지가 나타나는(보이는) 영역*/
        position: relative;
        width: 800px;
        height: 450px;
        overflow: hidden;
      }
      .slider__inner {              /*이미지가 슬라이드 되는(움직이는) 영역*/
        display: flex;
        flex-wrap: wrap;
        width: 4000px;
        height: 450px;
      }
      .slider {
        position: relative;
        width: 800px;
        height: 450px;
      }

 

 

 

코드 보기 / HTML

<div class="slider__wrap">
            <div class="slider__img">
                <div class="slider__inner">
                    <div class="slider"><img src="./img/sliderEffect06-min.jpg" alt="이미지1"></div>
                    <div class="slider"><img src="./img/sliderEffect07-min.jpg" alt="이미지2"></div>
                    <div class="slider"><img src="./img/sliderEffect08-min.jpg" alt="이미지3"></div>
                    <div class="slider"><img src="./img/sliderEffect09-min.jpg" alt="이미지4"></div>
                    <div class="slider"><img src="./img/sliderEffect10-min.jpg" alt="이미지5"></div>
                </div>
            </div>
        </div>

 

 

 

코드 보기  / JAVASCRIPT  - 기본

      const sliderWrap = document.querySelector(".slider__wrap");
        const sliderImg = sliderWrap.querySelector(".slider__img");
        const sliderInner = sliderWrap.querySelector(".slider__inner");
        const slider = sliderWrap.querySelectorAll(".slider");

        let currentIndex = 0;                   //현재 보이는 (가장 위에 있는) 이미지
        let sliderCount = slider.length;        //전체 이미지의 총 수
        let sliderInterval = 3000;              //다음 이미지로 넘어가는 간격의 시간

        sliderInner.style.transition = "all 0.8s";
        setInterval(()=>{
            currentIndex = (currentIndex+1) % sliderCount;
        
            sliderInner.style.transform = "translateX("+ -800 * currentIndex + "px)";
        },sliderInterval);

 

slider__wrap 클래스를 가진 요소 안에 여러 개의 이미지(slider 클래스)가 존재하며, slider__inner 클래스를 가진 요소를 사용해 이미지를 좌우로 이동시키는 방식으로 슬라이더를 구현한다.

currentIndex 변수는 현재 보이는(가장 위에 있는) 이미지의 인덱스를 저장한다.

sliderCount 변수는 전체 이미지의 총 개수를 나타내며, sliderInterval 변수는 다음 이미지로 넘어가는 간격의 시간을 나타낸다.

sliderInner.style.transition 속성은 슬라이드 이동 시 애니메이션 효과를 부드럽게 하기 위해 추가된 코드이다. 

setInterval() 함수를 사용하여 일정 시간마다 다음 이미지로 넘어가도록 구현되어 있으며, translateX() 함수를 사용하여 현재 이미지의 위치를 이동시키고, currentIndex 변수를 이용하여 다음 이미지로 인덱스를 변경한다.



 

 

코드 보기  / JAVASCRIPT  - GSAP

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.5/gsap.min.js"></script>
    <script>
        setInterval(()=>{
            currentIndex = (currentIndex + 1) % sliderCount;

            gsap.to(".slider__inner",{
                x : -800 * currentIndex,
                duration : 1,
                ease: "elastic.out(1, 0.5)"
            })
        },sliderInterval);
    </script>

 

 GSAP의 gsap.to() 메소드를 사용하여 이미지들의 opacity 속성을 변경해 슬라이드 애니메이션을 구현한다. 

초기에는 첫 번째 슬라이드만 보이도록 gsap.set() 메소드를 사용하여 나머지 슬라이드들의 opacity 속성을 0으로 설정합니다. 그리고 setInterval() 함수를 사용하여 일정한 시간 간격으로 gsap.to() 메소드를 실행한다.

gsap.to() 메소드는 첫 번째 매개변수로 애니메이션을 적용할 요소를 선택하고, 두 번째 매개변수로 애니메이션을 적용할 속성을 객체 형태로 전달한다.

이 코드에서는 슬라이드의 opacity 속성을 변경하도록 지정했습니다. 세 번째 매개변수로는 애니메이션 실행 시간(duration)을 전달하고, 네 번째 매개변수로는 애니메이션의 이징(ease)을 전달한다.

이 코드에서는 GSAP의 ease 중 하나인 power2.inOut를 사용하여 슬라이드 애니메이션을 부드럽게 만들었다. 

이 외에도 여러 가지 이징 중에서 선택할 수 있다.

 

 

코드 보기  / JAVASCRIPT  - Jquery

<!-- jquery -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.13.2/jquery-ui.min.js"></script>
    <script>
        setInterval(()=>{
            currentIndex = (currentIndex+1) % $(".slider").length;

            $(".slider__inner").css("position", "relative");
            $(".slider__inner").animate({left : -800 * currentIndex},700,"easeOutElastic");

            
        },sliderInterval);
    </script>

 

setInterval() 함수는 일정한 시간 간격으로 이미지 슬라이더를 동작시키는 함수입니다. 

슬라이더의 현재 이미지를 나타내는 변수 currentIndex와 슬라이더 이미지의 총 개수를 나타내는 변수 sliderCount는 이미 이전 코드에서 정의되어 있다.

$(".slider__inner")은 클래스가 slider__inner인 HTML 요소를 선택한다.

이 요소는 전체 이미지가 담겨 있는 부모 요소이다.

.css() 함수는 해당 요소의 CSS 스타일 속성을 설정합니다. 이 코드에서는 .slider__inner 요소의 position 속성을 relative로 설정합니다.

.animate() 함수는 해당 요소를 애니메이션으로 움직이게 한다. 

이 코드에서는 .slider__inner 요소의 left 속성을 -800(이미지의 width값) * currentIndex로 설정한다. 

이렇게 함으로써, .slider__inner 요소가 좌우로 슬라이딩되며, 보여지는 이미지가 바뀌게 된다.

애니메이션의 지속시간은 700ms로 설정되어 있으며, easeOutElastic 이라는 이징 함수를 사용하여 자연스러운 슬라이딩 효과를 적용하고 있다.

 

 

 

완성 화면 (Effect03)

 

 

 

 

 

CSS는 가로와 세로만 차이가 있기 때문에 생략. 

(좌측으로 슬라이드 할 때는 width값 800 * 이미지 5장 = 4000이었지만 상측으로 슬라이드 시에는 height값 450 * 이미지 5장 이므로 = 2250이 된다.)

 

HTML 역시 구조가 완전히 같기 때문에 생략.

 

 

 

코드 보기  / JAVASCRIPT  - 기본

const sliderWrap = document.querySelector(".slider__wrap");
        const sliderImg = sliderWrap.querySelector(".slider__img");
        const sliderInner = sliderWrap.querySelector(".slider__inner");
        const slider = sliderWrap.querySelectorAll(".slider");

        let currentIndex = 0;                   //현재 보이는 (가장 위에 있는) 이미지
        let sliderCount = slider.length;        //전체 이미지의 총 수
        let sliderInterval = 3000;              //다음 이미지로 넘어가는 간격의 시간

        sliderInner.style.transition = "all 0.8s";
        setInterval(()=>{
            currentIndex = (currentIndex+1) % sliderCount;
        
            sliderInner.style.transform = "translateY("+ -450 * currentIndex + "px)";
        },sliderInterval);

 

sliderWrap 

: 슬라이더를 감싸는 div 엘리먼트를 참조한다.


sliderInner 

: 슬라이더 이미지들을 감싸는 div 엘리먼트를 참조한다.


slider 

: 슬라이더 이미지들을 참조한다.


currentIndex 

: 현재 보이는 (가장 위에 있는) 이미지의 인덱스를 저장한다.


sliderCount 

: 전체 이미지의 총 개수를 저장한다.


sliderInterval 

: 다음 이미지로 넘어가는 간격의 시간을 저장한다.


sliderInner.style.transition 

: 슬라이더 이동에 대한 CSS transition 속성을 설정한다.


setInterval() 

: 일정 시간 간격마다 실행되는 함수입니다. 슬라이더의 이미지를 변경하고 애니메이션을 적용한다.


currentIndex = (currentIndex+1) % sliderCount 

: 현재 인덱스에서 1을 더하고, 전체 이미지의 개수로 나누어 나머지를 구함으로써, 이미지 인덱스가 계속해서 순환하도록 한다.


sliderInner.style.transform = "translateY("+ -450 * currentIndex + "px)" 

: 현재 이미지 인덱스에 따라 sliderInner의 CSS transform 속성을 변경하여 슬라이더 이미지를 움직인다. 

여기서 -450은 이미지가 움직이는 거리이다. 

즉, currentIndex가 1 증가할 때마다 이미지가 450px(이미지의 height값)만큼 위로 이동한다.

 

 

 

 

 

코드 보기  / JAVASCRIPT  - GSAP

 <!--GSAP-->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.5/gsap.min.js"></script>
    <script>
        setInterval(()=>{
            currentIndex = (currentIndex + 1) % sliderCount;

            gsap.to(".slider__inner",{
                y : -450 * currentIndex,
                duration : 1,
                ease: "elastic.out(1, 0.5)"
            })
        },sliderInterval);
    </script>

 

 

슬라이더 이미지를 세로 방향으로 이동시키기 위해 translateY를 사용하였다.

이 부분을 GSAP에서 제공하는 y 속성으로 변경하여, GSAP의 애니메이션 기능을 이용하여 세로 방향으로 이동하도록 만들었다.

애니메이션 기능을 사용하기 위해서는 먼저 GSAP 라이브러리를 불러와야 하며, 이후 gsap.to() 메소드를 이용하여 원하는 요소를 선택하여 애니메이션을 적용할 수 있다.

 duration 속성을 이용하여 애니메이션이 진행되는 시간을 조절할 수 있으며, ease 속성을 이용하여 애니메이션의 가속도와 감속도를 조절할 수 있다.

위 코드에서는 elastic.out(1, 0.5) 값을 ease 속성으로 설정하여, 애니메이션의 마지막 부분에서 탄력적인 효과가 발생하도록 만들었다. 

 

 

 

 

코드 보기  / JAVASCRIPT  - Jquery

<!-- jquery -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.13.2/jquery-ui.min.js"></script>
    <script>
        setInterval(()=>{
            currentIndex = (currentIndex+1) % $(".slider").length;

            $(".slider__inner").css("position", "relative");
            $(".slider__inner").animate({top : -450 * currentIndex},700,"easeOutElastic");

        },sliderInterval);

 

querySelector와 querySelectorAll 함수를 사용하여 HTML 문서에서 각각 .slider__wrap, .slider__img, .slider__inner, .slider 클래스를 가진 요소들을 선택한다.

이들은 모두 슬라이드 컨테이너와 이미지, 이미지가 포함된 내부 컨테이너, 그리고 각 이미지를 나타내는 요소들이다.

그리고 나서 currentIndex를 0으로 초기화하고, sliderCount는 .slider 클래스를 가진 요소들의 수로 설정한다. 

sliderInterval은 3000ms로 설정되어 있다.
그 다음 sliderInner의 transition 속성을 "all 0.8s"로 설정하여, 슬라이드 전환시 0.8초 동안 애니메이션이 일어나도록 한다.

마지막으로 setInterval 함수를 사용하여 sliderInterval 간격으로 슬라이드가 전환되도록 한다. 

이때 currentIndex를 업데이트하여 다음에 보여질 이미지의 인덱스를 결정하고, sliderInner 요소의 transform 속성을 사용하여 슬라이드를 이동시킨다. 

이 코드에서는 translateX 또는 translateY 함수를 사용하여 각각 가로 방향 또는 세로 방향으로 이동시키도록 한다.



 

 

 

 

 

 

 

 

 

 

728x90
반응형

 

완성 화면

 

 

 

 

 

 

코드 보기 / HTML

 <main id="main">
        <div class="quiz__wrap__cbt">
            <div class="cbt__header">
                <h2>2020년 1회 정보처리기능사 기출 문제 </h2>
            </div>
            <div class="cbt__content">
                <div class="cbt__quiz">
                    <!-- <div class="cbt">
                        <div class="cbt__question"><span>1. </span>객체지향 프로그램에서 데이터를 추상화하는 단위는?</div>
                        <div class="cbt__question__img"><img src="img/gineungsaJC2020-02.jpg" alt=""></div>
                        <div class="cbt__selects">
                        <div>
                                <input type="radio" id="select1">
                                <label for="select1"><span>클래스</span></label>
                                <input type="radio" id="select2">
                                <label for="select2"><span>메서드</span></label>
                                <input type="radio" id="select3">
                                <label for="select3"><span>상속</span></label>
                                <input type="radio" id="select4">
                                <label for="select4"><span>메시지</span></label>
                        </div>
                        <div class="cbt__desc">객체지향프로그램</div>
                        <div class="cbt__keyword">추상화 어쩌구</div>
                    </div> -->
                    <div class="cbt">
                        <div class="cbt__question"><span>1. </span>객체지향 프로그램에서 데이터를 추상화하는 단위는?</div>
                        <div class="cbt__question__desc">객체 지향 언어는  ___________ 이다.</div>
                        <div class="cbt__selects">
                            <div>
                                <input type="radio" id="select1">
                                <label for="select1"><span>클래스</span></label>
                                <input type="radio" id="select2">
                                <label for="select2"><span>메서드</span></label>
                                <input type="radio" id="select3">
                                <label for="select3"><span>상속</span></label>
                                <input type="radio" id="select4">
                                <label for="select4"><span>메시지</span></label>
                            </div>
                        </div>
                        <div class="cbt__desc">객체지향프로그램</div>
                        <div class="cbt__keyword">추상화 어쩌구</div>
                    </div>
                </div>
            </div>
            <div class="cbt__aside">
                <div class="cbt__info">
                    <div>
                        <span class="cbt__time">남은 시간 :<em></em></span>
                        <button class="cbt__submit">답안지 제출</button>
                    </div>
                    <div>
                        <div class="cbt__title">수험자 : <em class="cbt__name"></em></div>
                        <div class="cbt__score">
                            <span> 전체 문항 수 : <em class="cbt__length"></em>문항</span> <br>
                            <span> 잔여 문항 수 : <em class="cbt__count"></em>문항</span> <br>
                        </div>
                    </div>
                </div>
                    <div class="cbt__omr">
                        <!-- <div class="omr">
                            <strong>1</strong>
                            <input type="radio" id="omr0_1">
                            <label for="omr0_1">
                                <span class="label-inner">1</span>
                            </label>
                            <input type="radio" id="omr0_2">
                            <label for="omr0_2">
                                <span class="label-inner">2</span>
                            </label>
                            <input type="radio" id="omr0_3">
                            <label for="omr0_3">
                                <span class="label-inner">3</span>
                            </label>
                            <input type="radio" id="omr0_4">
                            <label for="omr0_4">
                                <span class="label-inner">4</span>
                            </label>
                        </div> -->
                    </div>
            </div>
        </div>
        <div class="cbt__start">
            <div class="cbt__modal__start">
                <h2>종목 선택</h2>
                <div class="cbt__items">
                    <select name="cbtTime" id="cbtTime" class="items1" onchange="changeSelect(this)">
                        <option value="gineungsaJC2005_02">정보처리기능사 2005년 2회</option>
                        <option value="gineungsaJC2005_04">정보처리기능사 2005년 4회</option>
                        <option value="gineungsaJC2005_05">정보처리기능사 2005년 5회</option>
                        <option value="gineungsaJC2006_01">정보처리기능사 2006년 1회</option>
                        <option value="gineungsaJC2006_02">정보처리기능사 2006년 2회</option>
                        <option value="gineungsaJC2006_03">정보처리기능사 2006년 3회</option>
                        <option value="gineungsaJC2006_05">정보처리기능사 2006년 5회</option>
                        <option value="gineungsaJC2007_01">정보처리기능사 2007년 1회</option>
                        <option value="gineungsaJC2007_02">정보처리기능사 2007년 2회</option>
                        <option value="gineungsaJC2007_05">정보처리기능사 2007년 5회</option>
                        <option value="gineungsaJC2008_01">정보처리기능사 2008년 1회</option>
                        <option value="gineungsaJC2008_02">정보처리기능사 2008년 2회</option>
                        <option value="gineungsaJC2008_04">정보처리기능사 2008년 4회</option>
                        <option value="gineungsaJC2008_05">정보처리기능사 2008년 5회</option>
                        <option value="gineungsaJC2009_01">정보처리기능사 2009년 1회</option>
                        <option value="gineungsaJC2009_05">정보처리기능사 2009년 5회</option>
                        <option value="gineungsaJC2010_02">정보처리기능사 2010년 2회</option>
                        <option value="gineungsaJC2010_05">정보처리기능사 2010년 5회</option>
                        <option value="gineungsaJC2011_01">정보처리기능사 2011년 1회</option>
                        <option value="gineungsaJC2011_02">정보처리기능사 2011년 2회</option>
                        <option value="gineungsaJC2011_04">정보처리기능사 2011년 4회</option>
                        <option value="gineungsaJC2011_05">정보처리기능사 2011년 5회</option>
                    </select>
                    <select name="cbtTime" id="cbtTime" class="items2" onchange="changeSelect(this)">
                        <option value="gineungsaWD2009_05">웹디자인기능사 2009년 5회</option>
                        <option value="gineungsaWD2010_01">웹디자인기능사 2010년 1회</option>
                        <option value="gineungsaWD2010_02">웹디자인기능사 2010년 2회</option>
                        <option value="gineungsaWD2010_04">웹디자인기능사 2010년 4회</option>
                        <option value="gineungsaWD2010_05">웹디자인기능사 2010년 5회</option>
                        <option value="gineungsaWD2011_01">웹디자인기능사 2011년 1회</option>
                        <option value="gineungsaWD2011_02">웹디자인기능사 2011년 2회</option>
                        <option value="gineungsaWD2011_04">웹디자인기능사 2011년 4회</option>
                        <option value="gineungsaWD2011_05">웹디자인기능사 2011년 5회</option>
                        <option value="gineungsaWD2012_02">웹디자인기능사 2012년 2회</option>
                        <option value="gineungsaWD2012_04">웹디자인기능사 2012년 4회</option>
                        <option value="gineungsaWD2012_05">웹디자인기능사 2012년 5회</option>
                        <option value="gineungsaWD2013_02">웹디자인기능사 2013년 2회</option>
                        <option value="gineungsaWD2013_04">웹디자인기능사 2013년 4회</option>
                        <option value="gineungsaWD2013_05">웹디자인기능사 2013년 5회</option>
                        <option value="gineungsaWD2014_01">웹디자인기능사 2014년 1회</option>
                        <option value="gineungsaWD2014_04">웹디자인기능사 2014년 4회</option>
                        <option value="gineungsaWD2014_05">웹디자인기능사 2014년 5회</option>
                        <option value="gineungsaWD2015_01">웹디자인기능사 2015년 1회</option>
                        <option value="gineungsaWD2015_03">웹디자인기능사 2015년 3회</option>
                        <option value="gineungsaWD2015_04">웹디자인기능사 2015년 4회</option>
                        <option value="gineungsaWD2015_05">웹디자인기능사 2015년 5회</option>
                        <option value="gineungsaWD2016_01">웹디자인기능사 2016년 1회</option>
                        <option value="gineungsaWD2016_04">웹디자인기능사 2016년 4회</option>
                    </select>
                </div>
                <div class="cbt__view">
                    수험자의 성함은 <input type="text" class="name"> 입니다. <br>
                    종목은 <span class="subject"></span>가 선택 되었습니다.
                </div>
                <div data-lit-hue="20" data-lit-count="100" class="lit-container">
                    <button id="startbtn" class="minimal">시험 시작</button>
                </div>
            </div>
        </div>
        <div class="cbt__end">
            <div class="cbt__modal__end">
                <h2>🎊 수고하셨습니다.</h2>
                <div class="cbt__endscore"></div>
                <button class="return">시험지로 돌아가기 </button>
            </div>
        </div>
        
    </main>

 

 

 

코드 보기 / JAVASCRIPT

const cbtHeader = document.querySelector(".cbt__header h2");
        const cbtStart = document.querySelector(".cbt__start");
        const cbtEnd = document.querySelector(".cbt__end");
        const cbtStartBtn = document.querySelector(".cbt__start .minimal");
        const cbtEndBtn = document.querySelector(".cbt__end .return");
        const cbtTime = document.querySelector(".cbt__time em");
        const cbtEndscore = document.querySelector(".cbt__endscore");

        let questionAll = [];                          //모든 문제 정보
        let quizLength = 0;                     //전체 문항 수
        let questioncount = quizLength;         //남은 문항 수

        //시험 결과 관련 변수
        let truescore = 0;                  //맞은 갯수
        let falsescore = 0;                 //틀린 갯수

        //제한시간 변수
        let TimeLimit = "";
        let RemainTime = "3600";

        cbtEnd.classList.add("hide");
        //시험 시작
        const startQuiz = () => {
            cbtStart.classList.add("hide");          //modal 제거

            const inputEl = document.querySelector('.name');            // input에서 입력받은 요소 선택
            const userName = document.querySelector(".cbt__title em");  // em 요소 선택
            const inputValue = inputEl.value;                           //input에서 입력된 값 가져오기

            userName.textContent = inputValue;                          // em 요소에 입력된 값 넣기

            //제한시간
            TimeLimit = setInterval(reduceTime, 3600);
        }
        
        
        -----------<정답 확인 시>-------
        const cbtSelects = document.querySelectorAll(".cbt__selects");
                let Score = 0;

                questionAll.forEach((question, number) => {
                    const quizSelectsWrap = cbtSelects[number];
                    const userSelector = `input[name=select${number}]:checked`;
                    const userAnswer = (quizSelectsWrap.querySelector(userSelector) || {}).value;
                    const numberAnswer = userAnswer ? userAnswer.slice(-1) : undefined;


                    if(numberAnswer == question.answer) {
                        console.log("정답")
                        truescore++;
                        cbtSelects[number].parentElement.classList.add("good");
                    } else {
                        console.log("오답")
                        falsescore++;
                        cbtSelects[number].parentElement.classList.add("bad");

                        //오답일 경우 정답 표시
                        const label = cbtSelects[number].querySelectorAll("label");
                        label[question.answer-1].classList.add("correct");
                    }
                    
                    //설명 숨기기
                    const quizDesc = document.querySelectorAll(".cbt__desc");
                    
                    if(quizDesc[number].innerText == "undefined"){
                        quizDesc[number].classList.add("hide");
                    } else {
                        quizDesc[number].classList.remove("hide");
                    }
                    
                });
                Score = Math.round((Score/questionAll.length)*100);
                cbtEnd.classList.remove("hide");
                cbtEndscore.innerHTML = "총"+truescore+"문항을 맞추셨습니다.<br> 틀린 문항 수는 "+falsescore+"개 이며, 총점은 "+Score+"입니다.";

            }
            
            ------------<제한시간>------------------
            //제한시간 설정
        const reduceTime = () => {
            RemainTime--;

            if(RemainTime == 0) endQuiz();

            cbtTime.innerText = displayTime();
        }

        //제한 시간 표시 
        const displayTime = () => {
            if(RemainTime <= 0){
                return "0분 00초";
            } else {
                let minutes = Math.floor(RemainTime / 60);
                let seconds = RemainTime % 60;

                //초의 단위가 한자리 수가 되면 앞에 0을 붙여주기
                if(minutes.toString().length == 1){
                    minutes = "0"+minutes;
                }

                if(seconds.toString().length == 1){
                    seconds = "0"+seconds;
                }

                return minutes + "분" + seconds + "초";
            }
        }
        
        
      -----------------<종목 선택>---------------
      //종목 선택
        const changeSelect = (ev) => {
            let selectValue = ev.value;
            let selectText = ev.options[ev.selectedIndex].text;

            cbtViewSubject.innerText = selectText;
            cbtHeader.innerText = selectText;

            dataQuestion(selectValue);
        }

 

 

 

 

 

제한 시간

 ----------<제한시간 변수>-----------
        let TimeLimit = "";
        let RemainTime = "3600";
        
        
        TimeLimit = setInterval(reduceTime, 3600);
        
        
        -------<제한시간 설정>-----------
           const reduceTime = () => {
            RemainTime--;

            if(RemainTime == 0) endQuiz();

            cbtTime.innerText = displayTime();
        }
        
        -----<제한 시간을 화면에 표시>---------
         const displayTime = () => {
            if(RemainTime <= 0){
                return "0분 00초";
            } else {
                let minutes = Math.floor(RemainTime / 60);
                let seconds = RemainTime % 60;

                //초의 단위가 한자리 수가 되면 앞에 0을 붙여주기
                if(minutes.toString().length == 1){
                    minutes = "0"+minutes;
                }

                if(seconds.toString().length == 1){
                    seconds = "0"+seconds;
                }

                return minutes + "분" + seconds + "초";
            }
        }

 

TimeLimit 변수의 초기값은 빈 문자열이며, RemainTime 변수의 초기값은 3600으로 설정된다.

이는 1시간에 해당하는 초수를 나타낸다.

setInterval 함수를 사용하여 reduceTime 함수를 3600밀리초(즉, 1시간)마다 호출하도록 설정된다.

reduceTime 함수는 남은 시간(RemainTime)을 1초씩 줄이고, 남은 시간이 0이 되면 endQuiz 함수를 호출한다.

또한 cbtTime 요소의 텍스트를 displayTime 함수가 반환하는 포맷된 문자열로 업데이트한다.

displayTime 함수는 남은 시간(RemainTime)을 입력으로 받아 카운트다운의 남은 분과 초를 계산하여, "mm분 ss초" 형식으로 남은 시간을 나타내는 포맷된 문자열을 반환한다.

여기서 "mm"은 분을, "ss"는 초를 나타냅니다. 남은 시간이 0보다 작거나 같으면 함수는 "0분 00초"를 반환한다.

reduceTime 함수에서 displayTime 함수를 호출하여 cbtTime 요소의 텍스트를 업데이트한다.

 

 

 

 

종목 선택

const changeSelect = (ev) => {
            let selectValue = ev.value;
            let selectText = ev.options[ev.selectedIndex].text;

            cbtViewSubject.innerText = selectText;
            cbtHeader.innerText = selectText;

            dataQuestion(selectValue);
        }

 

changeSelect 함수를 정의하는 것으로, 이 함수는 select 요소에서 옵션을 선택하면 호출된다.

ev는 이벤트 객체를 나타내며, 선택된 옵션의 값을 가져와 selectValue 변수에 할당하며, 선택된 옵션의 텍스트를 가져와 selectText 변수에 할당한다.
cbtViewSubject 요소의 텍스트와 cbtHeader 요소의 텍스트를 선택된 옵션의 텍스트로 변경한다.
마지막으로, dataQuestion 함수를 호출하여 선택된 옵션의 값을 인수로 전달한다.

이 함수는 이 값을 기반으로 데이터를 가져와 해당 데이터를 사용하여 문제를 표시하는 작업을 수행한다.

 

 

 

 

 

정답 확인 버튼 클릭 시, 점수와 틀린 문항과 맞은 문항 표시

 const answerQuiz = () => {
                const cbtSelects = document.querySelectorAll(".cbt__selects");
                let Score = 0;

                questionAll.forEach((question, number) => {
                    const quizSelectsWrap = cbtSelects[number];
                    const userSelector = `input[name=select${number}]:checked`;
                    const userAnswer = (quizSelectsWrap.querySelector(userSelector) || {}).value;
                    const numberAnswer = userAnswer ? userAnswer.slice(-1) : undefined;


                    if(numberAnswer == question.answer) {
                        console.log("정답")
                        truescore++;
                        cbtSelects[number].parentElement.classList.add("good");
                    } else {
                        console.log("오답")
                        falsescore++;
                        cbtSelects[number].parentElement.classList.add("bad");

                        //오답일 경우 정답 표시
                        const label = cbtSelects[number].querySelectorAll("label");
                        label[question.answer-1].classList.add("correct");
                    }
                    
                    //설명 숨기기
                    const quizDesc = document.querySelectorAll(".cbt__desc");
                    
                    if(quizDesc[number].innerText == "undefined"){
                        quizDesc[number].classList.add("hide");
                    } else {
                        quizDesc[number].classList.remove("hide");
                    }
                    
                });
                FinalScore = Math.round((truescore/questionAll.length)*100);
                cbtEnd.classList.remove("hide");
                cbtEndscore.innerHTML = "총"+truescore+"문항을 맞추셨습니다.<br> 틀린 문항 수는 "+falsescore+"개 이며, 총점은 "+FinalScore+"입니다.";

            }

 

 

시험이 종료된 후 사용자의 답을 검사하고, 사용자가 맞춘 문제 수와 틀린 문제 수, 총점을 계산하여 화면에 출력하는 역할을 한다.


함수 내부에서는 우선 document.querySelectorAll(".cbt__selects")를 사용하여 HTML 문서 내의 모든 문제 선택지를 가져온다.

그 후,  questionAll 배열에 저장된 모든 문제에 대해 반복문을 실행하면서, 각 문제의 정답과 사용자의 선택지를 비교하고, 맞은 문제와 틀린 문제의 수를 카운트한다.

 

이 때, 맞은 문제일 경우 cbtSelects[number].parentElement.classList.add("good")를 사용하여 동그라미 표를 나타내고, truescore 변수를 1씩 증가시키며,  틀린 문제일 경우 cbtSelects[number].parentElement.classList.add("bad")를 사용하여 틀렸다는 표시인 붉은 사선을 나타내고, 맞는 선택지를 빨간색으로 바꾸어 정답을 표시하고 falsescore 변수를 1씩 증가시킨다.

또한, 각 문제에 대해 quizDesc를 사용하여 문제에 대한 설명을 가져와, 이 설명이 있는 경우에는 이를 화면에 보여주고, 없는 경우에는 숨긴다.

 

truescore 변수는 카운트 된 정답 수이며, falsescore 변수는 카운트 된 오답 수이다.

이 두 변수를 사용하여, 맞춘 문항 수와 틀린 문항 수를 화면에 출력한다.

FinalScore 변수는 총점을 계산한다.

총점은 맞춘 문항 수(truescore)를 전체 문항 수(questionAll)로 나눈 후, 100을 곱한 값입니다. Math.round() 함수를 사용하여 소수점 이하를 반올림하고, cbtEndscore.innerHTML을 사용하여 최종 결과를 화면에 출력한다.

 

 

 

 

 

 

시험 시작 버튼 클릭 시, OMR 답지 위에 입력 된 수험자의 이름을 표시

const startQuiz = () => {
            cbtStart.classList.add("hide");          //modal 제거

            const inputEl = document.querySelector('.name');            // input에서 입력받은 요소 선택
            const userName = document.querySelector(".cbt__title em");  // em 요소 선택
            const inputValue = inputEl.value;                           //input에서 입력된 값 가져오기

            userName.textContent = inputValue;                          // em 요소에 입력된 값 넣기

            //제한시간
            TimeLimit = setInterval(reduceTime, 3600);
        }

 

 

사용자가 입력한 이름을 가져와서 해당 이름을 모달 창의 제목에 넣는 역할을 하는 코드이다.

먼저 document.querySelector('.name')을 사용하여 HTML에서 name 클래스를 가진 요소를 찾는다.

그리고 inputValue 변수에 해당 요소의 값(value)을 할당한다.

그 후, document.querySelector(".cbt__title em")를 사용하여 HTML에서 cbt__title 클래스를 가진 요소의 자식 요소인 em 요소를 찾아 textContent를 사용하여 inputValue를 할당하여 OMR 답안지 위에 수험자의 이름을 표시한다.

 

 

 

 

 

 

 

 

 

 

 

 

 

728x90
반응형

 

 

완성 화면 (링크)

 

https://fitalux.github.io/web2023/javascript/quiz/quizEffect07.html

 

퀴즈 이펙트 07

1. 객체지향 프로그램에서 데이터를 추상화하는 단위는? 객체 지향 언어는 ___________ 이다.

fitalux.github.io

 

 

 

 

 

코드 보기 / JAVASCRIPT

const cbt = document.querySelectorAll(".cbt");
        const cbtQuiz = document.querySelector(".cbt__quiz");
        const cbtOmr = document.querySelector(".cbt__omr");
        const cbtSubmit = document.querySelector(".cbt__submit");
        const cbtCount = document.querySelector(".cbt__count");
        const cbtLength = document.querySelector(".cbt__length");
        const cbtStart = document.querySelector(".cbt__start");

        let questionAll = [];                          //모든 문제 정보
        let quizLength = 0;                     //전체 문항 수
        let questioncount = quizLength;         //남은 문항 수

        //데이터 가져오기
        const dataQuestion = () => {
            fetch("json/gisa2020_01.json")
            .then(res => res.json())
            .then(items => {
                questionAll = items.map((item, index)=>{
                    const formattedQuestion = {
                        question: item.question,
                        number: index + 1
                    };
                    const answerChoices = [...item.incorrect_answers];          //오답 불러오기
                    formattedQuestion.answer = Math.round(Math.random() * answerChoices.length)+1;
                    answerChoices.splice(formattedQuestion.answer - 1, 0, item.correct_answer);

                    //보기 추가
                    answerChoices.forEach((choice, index) => {
                        formattedQuestion["choice" + (index+1)] = choice;
                    });

                    //문제에 대한 해설 출력
                    if(item.hasOwnProperty("question_desc")) {
                        formattedQuestion.question_desc = item.question_desc;
                    }

                    //문제에 대한 이미지 출력
                    if(item.hasOwnProperty("question_img")) {
                        formattedQuestion.question_img = item.question_img;
                    }

                    //해설 출력
                    if(item.hasOwnProperty("desc")) {
                        formattedQuestion.desc = item.desc;
                    }

                    // console.log(formattedQuestion);
                    return formattedQuestion;
                });
                newQuestion();          //문제 만들기

                
                //modal창 제거
                document.getElementById("startbtn").addEventListener("click",() => {
                    cbtStart.style.display = "none";
                });

                //전체 문항 수
                quizLength = questionAll.length;
                cbtLength.innerHTML = quizLength;
                cbtCount.innerHTML = quizLength;
            })   
            .catch((err) => console.log(err));
        }
    
            //문제 만들기 
            const newQuestion = () => {
                const exam = [];
                const omr = [];

                questionAll.forEach((question, number) => {
                    exam.push(`
                        <div class="cbt">
                            <div class="cbt__question"><span>${question.number}</span>. ${question.question}</div>
                            ${question.question_img ?`<div class="cbt__question__img">${question.question_img}</div>` : ''}
                            ${question.question_desc ? `<div class="cbt__question__desc">${question.question_desc}</div>` : ''}
                            <div class="cbt__selects">
                                <input type="radio" id="select${number}_1" name="select${number}" value="${number}_1" onclick="answerSelect2(this)">
                                <label for="select${number}_1"><span>${question.choice1}</span></label>
                                <input type="radio" id="select${number}_2" name="select${number}" value="${number}_2" onclick="answerSelect2(this)">
                                <label for="select${number}_2"><span>${question.choice2}</span></label>
                                <input type="radio" id="select${number}_3" name="select${number}" value="${number}_3" onclick="answerSelect2(this)">
                                <label for="select${number}_3"><span>${question.choice3}</span></label>
                                <input type="radio" id="select${number}_4" name="select${number}" value="${number}_4" onclick="answerSelect2(this)">
                                <label for="select${number}_4"><span>${question.choice4}</span></label>
                                </div>
                            <div class="cbt__desc hide">${question.desc}</div>
                        </div>
                    `);

                    omr.push(`
                    <div class="omr">
                        <strong>${question.number}</strong>
                        <input type="radio" name="omr${number}" id="omr${number}_1" value="${number}_1" onclick="answerSelect(this)">
                        <label for="omr${number}_1"><span class="label-inner">1</span></label>
                        <input type="radio" name="omr${number}" id="omr${number}_2" value="${number}_2" onclick="answerSelect(this)">
                        <label for="omr${number}_2"><span class="label-inner">2</span></label>
                        <input type="radio" name="omr${number}" id="omr${number}_3" value="${number}_3" onclick="answerSelect(this)">
                        <label for="omr${number}_3"><span class="label-inner">3</span></label>
                        <input type="radio" name="omr${number}" id="omr${number}_4" value="${number}_4" onclick="answerSelect(this)">
                        <label for="omr${number}_4"><span class="label-inner">4</span></label>
                    </div>
                `)
            });                                                 
                cbtQuiz.innerHTML = exam.join('');
                cbtOmr.innerHTML = omr.join('');
        }

            //정답 확인
            const answerQuiz = () => {
                const cbtSelects = document.querySelectorAll(".cbt__selects");

                questionAll.forEach((question, number) => {
                    const quizSelectsWrap = cbtSelects[number];
                    const userSelector = `input[name=select${number}]:checked`;
                    const userAnswer = (quizSelectsWrap.querySelector(userSelector) || {}).value;
                    const numberAnswer = userAnswer ? userAnswer.slice(-1) : undefined;


                    if(numberAnswer == question.answer) {
                        console.log("정답")
                        cbtSelects[number].parentElement.classList.add("good");
                    } else {
                        console.log("오답")
                        cbtSelects[number].parentElement.classList.add("bad");

                        //오답일 경우 정답 표시
                        const label = cbtSelects[number].querySelectorAll("label");
                        label[question.answer-1].classList.add("correct");
                    }

                    //설명 숨기기
                    const quizDesc = document.querySelectorAll(".cbt__desc");

                    if(quizDesc[number].innerText == "undefined"){
                        quizDesc[number].classList.add("hide");
                    } else {
                        quizDesc[number].classList.remove("hide");
                    }
                });
            }

            //선택지 체크
        const answerSelect2 = (el) => {
            const answer = el.value;
            const answerNum = answer.split("_");
            
            const select = document.querySelectorAll(".cbt__omr .omr");             //전체 문항수 
            //cbt 문제 (즉, content -> omr로 체크)
            const label = select[answerNum[0]].querySelectorAll("input");           //선택지 갯수 (4)
            label[answerNum[1]-1].checked = true;

            const answerInput = document.querySelectorAll(".cbt__selects input:checked");
            cbtCount.innerHTML = quizLength - answerInput.length;
        }

        const answerSelect = (el) => {
            const answer = el.value;
            const answerNum = answer.split("_");
            
            const select = document.querySelectorAll(".cbt__quiz .cbt");             //전체 문항수
            //omr -> content 박스로 체크
            const label = select[answerNum[0]].querySelectorAll("input");           //선택지 갯수 (4)
            label[answerNum[1]-1].checked = true;

            const answerInput = document.querySelectorAll(".cbt__selects input:checked");
            cbtCount.innerHTML = quizLength - answerInput.length;
        }

        cbtSubmit.addEventListener("click", answerQuiz);
        dataQuestion();


        const bubbleBttn = () => {
                const clip = (v, min, max = Infinity) => {
                    if (v < min) return min;
                    else if (v > max) return max;
                    else return v;
                };

                // generated random value from given range
                const randRange = (min, max) => Math.random() * max + min;

                // create bubble on x and y position inside target with given hue theme
                function bubble(x, y, rect, hue, target) {

                    // 변수 설정
                    const size = randRange(20, rect.width / 5);
                    const circleHue = hue + randRange(-20, 20);
                    const animDuration = randRange(clip(size ** 2/1000, 1), 6) 
                    const zIndex = Math.random() < 0.1 ? 2 : -1;


                    // 원
                    const circle = document.createElement("span");
                    circle.className = "lit";
                    circle.style.left = x + "px";
                    circle.style.top = y + "px";
                    circle.style.width = size + "px";
                    circle.style.height = size + "px";
                    circle.style.background = `hsl(${circleHue}deg, 100%, 60%)`;
                    circle.style.zIndex = zIndex
                    circle.style.animationDuration = animDuration + "s";
                    target.appendChild(circle);
                }

                document.querySelectorAll("[data-lit-hue]").forEach((target) => {
                    const rect = target.getBoundingClientRect();
                    const hue = Number(target.getAttribute("data-lit-hue"));
                    const count = Number(target.getAttribute("data-lit-count") || 50);

                    for (let i = 0; i < count; i++) {
                        const x = randRange(0, rect.width);
                        const y = randRange(0, rect.height);
                        bubble(x, y, rect, hue, target);
                    }
            });
        };

        bubbleBttn();

 

 

 

 

answerSelect, answerSelect2

answerSelect 함수와 answerSelect2 함수는 두 가지 다른 형식의 답안지를 처리하는 데 사용된다. 

answerSelect 함수는 omr 답안지에서 번호를 체크했을 때, 문제 선택지에 같은 번호를 체크 하는데 사용되며, answerSelect2 함수는 문제 선택지에서 번호를 체크했을 때, omr 답안지에 같은 번호를 체크 하는데 사용된다.

두 함수는 비슷한 구조를 가지고 있다.

먼저 선택한 답안의 값을 가져와서 해당 값을 사용하여 문제와 답안의 위치를 찾은 후 해당 위치의 라벨에서 선택지를 선택한다.

마지막으로, 응시자가 선택한 답안의 수를 계산하여 응시자가 아직 선택하지 않은 문제 수를 계산하고 이를 사이드에 있는 박스에  표시한다.

코드에서 querySelectorAll 함수를 사용하여 DOM 요소를 선택하며, querySelectorAll 함수는 CSS 선택자를 사용하여 요소를 선택한다.

이 함수를 사용하여 문제와 답안의 위치를 찾는다.

querySelectorAll 함수의 결과는 NodeList이며, 이 NodeList에서 요소를 선택하여 문제와 답안의 위치를 찾는다.

마지막으로, 이 코드는 innerHTML 속성을 사용하여 화면에 표시될 값들을 업데이트한다.

innerHTML 속성을 사용하면 HTML 태그를 사용하여 텍스트와 함께 다른 요소도 표시할 수 있다.

 

 

 

 

 

modal 창 제거

 

document.getElementById("startbtn").addEventListener("click",() => {
                    cbtStart.style.display = "none";
                }); 는 시험 시작 버튼(startbtn)을 클릭하면 모달 창(cbtStart)을 제거하는 역할을 한다.

document.getElementById() 함수는 문서에서 해당 ID를 가진 요소를 찾아서 반환한다.

(따로 선택자를 주지 않고 button에 지정된 ID를 찾도록 하였다.)

addEventListener() 함수는 해당 요소에 click 이벤트가 추가한다.
이벤트가 발생하면 () => { cbtStart.style.display = "none"; } 코드 블록이 실행되어 cbtStart 요소의 display 속성 값을 "none"으로 변경하여 모달 창이 화면에서 사라지도록 한다.

 

 

 

 

 

 

728x90
반응형

 

 

 

 

완성 화면

 

 

 

 

 

 

 

코드 보기 / CSS

 

.quiz__wrap__cbt {
    padding:  0 20px;
    font-family: 'ElandChoice';
}
.cbt__content {
    width: calc(100% - 300px);
    background-color: #fff;
}
.cbt__header {
    width: calc(100% - 300px);
    background-color: #f0d2fa;
    border: 8px ridge #440460;
    margin-bottom: 20px;
    padding: 10px 20px;
    background-color: #f1d8fe;
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.cbt__aside {
    position: fixed;
    right: 20px;
    top: 180px;
    height: calc(100vh - 140px);
    width: 280px;
    background-color: #fff;
    border: 8px ridge #440460;
    overflow-y: auto;
}
.cbt__quiz {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
}
.cbt__quiz .cbt {
    width: 49%;
    border: 8px ridge #440460;
    margin-bottom: 10px;
    padding: 20px;
}
.cbt__quiz .cbt {
    position: relative;
}
.cbt__quiz .cbt.good::after {
    content: '';
    background-image: url(../img/O.png);
    background-size: contain;
    background-repeat: no-repeat;
    width: 200px;
    height: 200px;
    position: absolute;
    left: 0;
    top: 0;
}
.cbt__quiz .cbt.bad::after {
    content: '';
    background-image: url(../img/X.png);
    background-size: contain;
    background-repeat: no-repeat;
    width: 200px;
    height: 200px;
    position: absolute;
    left: 0;
    top: 0;
}
.cbt__info {
    background-color: #f1d8fe;
}
.cbt__info > div {
    border-bottom: 5px ridge #440460;
}
.cbt__info > div:first-child {
    background-color: #f1d8fe;
    color: #000;
    padding: 10px 20px;
    text-align: center;
}
.cbt__time {    
    position: fixed;
    right: 180px;
    top:70px;
    padding-left: 20px;
    background: #440460;
    padding: 10px 27px 10px 44px;
    border-radius: 15px;
    color: #fff;
    margin: 10px 0;
}
.cbt__time::before{
    content: '';
    position: absolute;
    top: 5px;
    left: 10px;
    width: 22px;
    height: 22px;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='icon icon-tabler icon-tabler-alarm' width='24' height='24' viewBox='0 0 24 24' stroke-width='2' stroke='%23ffffff' fill='none' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath stroke='none' d='M0 0h24v24H0z' fill='none'/%3E%3Ccircle cx='12' cy='13' r='7' /%3E%3Cpolyline points='12 10 12 13 14 13' /%3E%3Cline x1='7' y1='4' x2='4.25' y2='6' /%3E%3Cline x1='17' y1='4' x2='19.75' y2='6' /%3E%3C/svg%3E");
}
.cbt__submit {
    position: fixed;
    right: 175px;
    top:100px;
    padding-left: 20px;
    background: #440460;
    cursor: pointer;
    padding: 10px 27px 10px 44px;
    border-radius: 15px;
    color: #fff;
    margin: 25px 0;
}
.cbt__submit::before{
    content: '';
    position: absolute;
    top: 5px;
    left: 10px;
    width: 22px;
    height: 22px;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='icon icon-tabler icon-tabler-file-text' width='24' height='24' viewBox='0 0 24 24' stroke-width='2' stroke='%23ffffff' fill='none' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath stroke='none' d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M14 3v4a1 1 0 0 0 1 1h4' /%3E%3Cpath d='M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2z' /%3E%3Cline x1='9' y1='9' x2='10' y2='9' /%3E%3Cline x1='9' y1='13' x2='15' y2='13' /%3E%3Cline x1='9' y1='17' x2='15' y2='17' /%3E%3C/svg%3E");
}
.cbt__info > div:last-child {
    padding: 20px;
}
.cbt__title {
    text-decoration: underline;
    text-underline-offset: 5px;
    margin-bottom: 5px;
}
.cbt__info span {
    display: inline-block;
}
.cbt__omr {
    padding: 20px;
}
.cbt__omr .omr {
    margin: 5px 0;
    display: grid;
    grid-template-columns: 50px 38px 38px 38px 38px;
    grid-template-rows: 20px;
    align-items: center;
}
.cbt__omr .omr input {
    opacity: 0;
    position: absolute;
    width: 0;
    height: 0;
}
.cbt__omr .omr strong{
    display: inline-block;
    text-align: center;
    padding: 2px;
    background-color: #eabcfb;
    font-family: 'Helvetica Neue';
    margin-right: 10px;
}
.cbt__omr .omr label {
    box-shadow: 0 0 0 1px #eabcfb;
    cursor: pointer;
    line-height: 0.5;
    text-align: center;
    width: 28px;
    height: 8px;
    font-family: 'Helvetica Neue';
    position: relative;
}
.cbt__omr .omr label::after {
    background-color: #555;
    content: "";
    display: block;
    position: absolute;
    top: 0;
    left: 0;
    width: 0;
    height: 100%;
    z-index: 1;
    transition: width 0.1s linear;
}
.cbt__omr .omr input[type=radio]:checked + label::after {
    width: 100%;
}
.cbt__omr .omr .label-inner{
    background-color: #fff;
    padding: 0.25em 0.13em;
    transform: translateY(-0.25em);
    width: 20px;
    color: #440460;
}
.cbt__question {
    font-size: 1.4rem;
    margin-bottom: 10px;
}
.cbt__question__img img {
    max-width: 400px;
    margin-bottom: 15px;
}
.cbt__question__desc {
    border: 2px solid #cacaca;
    padding: 10px;
    margin-bottom: 15px;
}
.cbt__selects {
    margin-bottom: 15px;
}
.cbt__selects label {
    display: flex;
}
.cbt__selects label span {
    font-size: 1rem;
    padding: 10px 10px 10px 30px;
    cursor: pointer;
    color: #555;
    position: relative;
}
.cbt__selects label span::before {
    position: absolute;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    width: 20px;
    height: 20px;
    border: 1px solid #333;
    border-radius: 30%;
    text-align: center;
    font-family: 'Helvetia Neue';
    font-weight: bold;
    line-height: 1.3;
    font-size: 0.83em;
    transition: all 0.5s;
}
.cbt__selects label:nth-of-type(1) span::before {
    content: '1';
}
.cbt__selects label:nth-of-type(2) span::before {
    content: '2';
}
.cbt__selects label:nth-of-type(3) span::before {
    content: '3';
}
.cbt__selects label:nth-of-type(4) span::before {
    content: '4';
}
.cbt__selects input {
    position: absolute;
   left: -99999px;
}
.cbt__selects input:checked + label span::before {
    color: #fff;
    box-shadow: inset 0 0 0 10px #000;
    border-color: #000;
}
.cbt__selects label.correct span::before {
    border-color: red;
    box-shadow: inset 0 0 0 10px red;
    color: #fff;
}
.cbt__desc {
    background-color: #eabcfb;
    padding: 10px 20px 10px 40px;
    margin-bottom: 5px;
    position: relative;
    border-radius: 15px;
}
.cbt__desc.hide{
    display: none;
}
.cbt__desc::before {
    content: '';
    position: absolute;
    left: 10px;
    top:5px;
    width: 24px;
    height: 24px;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='icon icon-tabler icon-tabler-pencil' width='24' height='24' viewBox='0 0 24 24' stroke-width='2.5' stroke='%23ff4500' fill='none' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath stroke='none' d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M4 20h4l10.5 -10.5a1.5 1.5 0 0 0 -4 -4l-10.5 10.5v4' /%3E%3Cline x1='13.5' y1='6.5' x2='17.5' y2='10.5' /%3E%3C/svg%3E");
}
.cbt__keyword {
    background-color: #eabcfb;
    padding: 10px 20px 10px 40px;
    margin-bottom: 5px;
    position: relative;
    display: inline-block;
    border-radius: 35px;
}
.cbt__keyword::before {
    content: '';
    position: absolute;
    left: 16px;
    top: 10px;
    width: 20px;
    height: 20px;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='icon icon-tabler icon-tabler-notebook' width='20' height='20' viewBox='0 0 24 24' stroke-width='2.5' stroke='%23000000' fill='none' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath stroke='none' d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M6 4h11a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-11a1 1 0 0 1 -1 -1v-14a1 1 0 0 1 1 -1m3 0v18' /%3E%3Cline x1='13' y1='8' x2='15' y2='8' /%3E%3Cline x1='13' y1='12' x2='15' y2='12' /%3E%3C/svg%3E");
}

 

 

 

 

 

 

코드 보기 / JAVASCRIPT

 

const cbt = document.querySelectorAll(".cbt");
            const cbtQuiz = document.querySelector(".cbt__quiz");
            const cbtOmr = document.querySelector(".cbt__omr");
            const cbtSubmit = document.querySelector(".cbt__submit");
    
            let questionAll = [];                          //모든 문제 정보
    
            //데이터 가져오기
            const dataQuestion = () => {
                fetch("json/gisa2020_01.json")
                .then(res => res.json())
                .then(items => {
                    questionAll = items.map((item, index)=>{
                        const formattedQuestion = {
                            question: item.question,
                            number: index+1
                        }
                        const answerChoices = [...item.incorrect_answers];          //오답 불러오기
                        formattedQuestion.answer = Math.floor(Math.random() * answerChoices.length)+1;
                        answerChoices.splice(formattedQuestion.answer - 1, 0, item.correct_answer);
    
                        //보기 추가
                        answerChoices.forEach((choice, index)=>{
                            formattedQuestion["choice" + (index+1)] = choice;
                        });
    
                        //문제에 대한 해설 출력
                        if(item.hasOwnProperty("question_desc")) {
                            formattedQuestion.questionDesc = item.question_desc;
                        }
    
                        //문제에 대한 이미지 출력
                        if(item.hasOwnProperty("question_img")) {
                            formattedQuestion.questionImg = item.question_img;
                        }
    
                        //해설 출력
                        if(item.hasOwnProperty("desc")) {
                            formattedQuestion.desc = item.desc;
                        }
    
                        // console.log(formattedQuestion);
                        return formattedQuestion;
                    });
                    newQuestion();          //문제 만들기
                })   
                .catch((err) => console.log(err));
        }
    
            //문제 만들기 
            const newQuestion = () => {
                const exam = [];
                const omr = [];
    
                questionAll.forEach((question, number) => {
                    exam.push(`
                        <div class="cbt">
                            <div class="cbt__question"><span>${question.number}</span>. ${question.question}</div>
                            <div class="cbt__question__img"></div>
                            <div class="cbt__selects">
                                <input type="radio" id="select${number}_1"  name="select${number}" value="${number+1}_1" onclick="answerSelect(this)">
                                <label for="select${number}_1"><span>${question.choice1}</span></label>
                                <input type="radio" id="select${number}_2"  name="select${number}" value="${number+1}_2" onclick="answerSelect(this)">
                                <label for="select${number}_2"><span>${question.choice2}</span></label>
                                <input type="radio" id="select${number}_3"  name="select${number}" value="${number+1}_3" onclick="answerSelect(this)">
                                <label for="select${number}_3"><span>${question.choice3}</span></label>
                                <input type="radio" id="select${number}_4"  name="select${number}" value="${number+1}_4" onclick="answerSelect(this)">
                                <label for="select${number}_4"><span>${question.choice4}</span></label>
                                </div>
                            <div class="cbt__desc hide">${question.desc}</div>
                        </div>
                    `);
                    
                    omr.push(`
                        <div class="omr">
                            <strong>${question.number}</strong>
                            <input type="radio" name="omr${number}" id="omr${number}_1" value="${number}_0">
                            <label for="omr${number}_1"><span class="label-inner">1</span></label>
                            <input type="radio" name="omr${number}" id="omr${number}_2" value="${number}_1">
                            <label for="omr${number}_2"><span class="label-inner">2</span></label>
                            <input type="radio" name="omr${number}" id="omr${number}_3" value="${number}_2">
                            <label for="omr${number}_3"><span class="label-inner">3</span></label>
                            <input type="radio" name="omr${number}" id="omr${number}_4" value="${number}_3">
                            <label for="omr${number}_4"><span class="label-inner">4</span></label>
                        </div>
                    `)
            });                                                 
                cbtQuiz.innerHTML = exam.join('');
                cbtOmr.innerHTML = omr.join('');
        }
        
            //정답 확인
            const answerQuiz = () => {
                const cbtSelects = document.querySelectorAll(".cbt__selects");
    
                questionAll.forEach((question, number) => {
                    const quizSelectsWrap = cbtSelects[number];
                    const userSelector = `input[name=select${number}]:checked`;
                    const userAnswer = (quizSelectsWrap.querySelector(userSelector) || {}).value;
                    const numberAnswer = userAnswer ? userAnswer.slice(-1) : undefined;
    
    
                    if(numberAnswer == question.answer) {
                        console.log("정답")
                        cbtSelects[number].parentElement.classList.add("good");
                    } else {
                        console.log("오답")
                        cbtSelects[number].parentElement.classList.add("bad");
    
                        //오답일 경우 정답 표시
                        const label = cbtSelects[number].querySelectorAll("label");
                        label[question.answer-1].classList.add("correct");
                    }
    
                    //설명 숨기기
                    const quizDesc = document.querySelectorAll(".cbt__desc");
    
                    if(quizDesc[number].innerText == "undefined"){
                        quizDesc[number].classList.add("hide");
                    } else {
                        quizDesc[number].classList.remove("hide");
                    }
                });
            }
        
            const answerSelect = () => {
    
            }
        
        cbtSubmit.addEventListener("click", answerQuiz);
        dataQuestion();

 

 

 

데이터 가져오기

 

dataQuestion 함수는 fetch() 메서드를 사용하여 "json/gisa2020_01.json"에 위치한 JSON 파일로 HTTP 요청을 수행한다.

그 후 응답 객체에 대해 then() 메서드가 호출된다.

then() 메서드는 응답이 수신되면 실행되는 콜백 함수를 취하며, 응답 데이터는 json() 메서드를 사용하여 JSON 형식으로 변환되고, 결과 Promise에 대해 또 다른 then() 메서드가 호출됩니다. then() 메서드는 다시 JSON 데이터를 매개변수로 받는 콜백 함수를 취한다.


이 콜백 함수 내부에서는 JSON 데이터에 대해 map() 메서드가 호출된다.

이 메서드는 JSON 데이터 배열의 각 항목을 반복하고 제공된 콜백 함수를 실행하여 새로운 배열을 만들며, 콜백 함수는 두 개의 매개변수를 취하는데 그 매개변수는 현재 반복 중인 항목과 그 인덱스가 된다.

map() 콜백 함수 내부에서는 formattedQuestion이라는 새로운 객체가 생성된다.

이 객체는 question 속성을 포함하고 있으며 현재 반복 중인 항목의 question 속성으로 설정되며 number 속성도 포함하고 있으며 현재 인덱스에 1을 더한 값으로 설정된다.

그런 다음 전체 항목에서 올바르지 않은 답변 선택지로 구성된 answerChoices 배열이 만들어지고 전개 연산자(...)를 사용하여 채워진다.

올바른 답변 선택지는 splice() 메서드를 사용하여 answerChoices 배열에 무작위로 인덱스에 삽입되며, 올바른 답변이 삽입되는 인덱스는 Math.floor() 및 Math.random() 메서드를 사용하여 무작위 숫자를 생성하여 결정된다.

다음으로, answerChoices 배열의 각 답변 선택지를 반복하는 루프가 실행된다.

forEach루프 내부에서는 대괄호 표기법을 사용하여 동적으로 생성된 속성 키를 사용하여 formattedQuestion 객체에 새 속성이 추가된다.

속성 키는 현재 반복 중인 답변 선택지의 인덱스를 기반으로 동적으로 생성되며, 속성 값은 답변 선택지 자체로 설정된다.

현재 반복 중인 항목에 desc 속성이 있다면, formattedQuestion 객체는 desc 속성을 포함하도록 업데이트 되며, 이 속성은 desc 속성의 값으로 설정된다.
그리고 map() 콜백 함수에 의해 formattedQuestion 객체가 반환되고, 이 객체는 questionAll 배열에 추가된다.

 JSON 데이터 배열의 모든 항목이 반복되면, newQuestion() 함수가 호출된다.

 

 

 

 

 

fetch() 메서드

: JavaScript에서 네트워크 요청을 보내고 응답을 받아오는 기능을 담당한다.

일반적으로 웹 애플리케이션에서 API 호출이나 서버로부터 데이터를 가져올 때 사용되며,  Promise 객체를 반환한다.

Promise 객체는 비동기 작업을 처리하기 위한 객체로, 작업이 완료되면 이행(resolve) 상태나 거부(reject) 상태가 되며, fetch() 메서드가 반환하는 Promise 객체는 HTTP 응답을 나타내는 Response 객체를 이행(resolve)한다.

 

 

 

 


then() 메서드

:Promise 객체의 상태가 이행(resolve) 상태가 되었을 때 호출되며, 이때 Response 객체를 인자로 받는다.

then() 메서드 안에서는 Response 객체를 가지고 데이터를 추출하고 처리할 수 있다.

 

 

 

 

문제 만들기


newQuestion 함수는 questionAll 배열의 모든 질문 객체를 반복하면서 HTML 요소들을 동적으로 생성하고 cbtQuiz 및 cbtOmr 요소의 내부 HTML로 추가한다.

각 질문 객체의 number, question, choice1 ~ choice4, 그리고 desc 속성 값을 이용하여 각 질문에 대한 HTML 문서 객체를 만든다.

exam 배열에는 cbtQuiz 요소에 들어갈 질문 HTML 코드가 추가되고, omr 배열에는 cbtOmr 요소에 들어갈 OMR HTML 코드가 추가되며, 마지막으로 cbtQuiz 및 cbtOmr 요소의 내부 HTML을 배열 요소를 구분자로 구분하여 exam 및 omr 배열의 값을 이용하여 대체한다.

 

 

 

 

정답 확인하기


answerQuiz 함수는 cbt__selects 클래스를 가진 HTML 요소를 찾아 각 문제의 사용자 선택지와 정답을 비교한다.
사용자의 선택지가 정답과 일치하면 해당 문제의 부모 요소에 good 클래스를 추가하고, 사용자의 선택지가 정답과 일치하지 않으면 해당 문제의 부모 요소에 bad 클래스를 추가하여 정답을 표시하며
해당 문제의 설명을 숨기거나 보여준다.


answerSelect 함수는 onclick 이벤트 시에 사용되며,
사용자가 선택지를 클릭하면 실행된다.
또한 cbtSubmit 버튼(답안지를 제출하는 버튼)에 click 이벤트 리스너를 추가하고 dataQuestion() 함수를 호출한다.

 

 

 

 

 

 

 

728x90
반응형

 

 

 

 

완성 화면

 

 

 

 

 

 

 

 

 

코드 보기 / JAVASCRIPT

 

//선택자
        const quizWrap = document.querySelector(".quiz__wrap");
        const quizTitle = quizWrap.querySelector(".quiz__title");
        const quizQuestion = quizWrap.querySelector(".quiz__question");
        const quizChoice = quizWrap.querySelector(".quiz__choice");
        const dogWrap = quizWrap.querySelector(".dog__wrap");
        const quizAnswer = quizWrap.querySelector(".quiz__answer");
        const quizNext = quizWrap.querySelector(".quiz__answer .next");
        const quizDesc = quizWrap.querySelector(".quiz__desc");

        let questionCount = 0;
        let quizCount = 0;
        let quizScore = 0;

        //문제출력
        const updateQuiz = (index) => {
            let TypeTag = `
                <span>${quizInfo[index].infoType}</span>
                <em>${quizInfo[index].infoTime}</em>
            `;

            let QuestionTag = `
                <em>${index+1}</em>.
                <span>${quizInfo[index].infoQuestion}</span>
            `;

            let ChoiceTag = `
                <label for="choice1">
                    <input type="radio" id="choice1" name="choice" value="1">
                    <span>${quizInfo[index].infoChoice[0]}</span>
                </label>
                <label for="choice2">
                    <input type="radio" id="choice2" name="choice" value="2">
                    <span>${quizInfo[index].infoChoice[1]}</span>
                </label> 
                <label for="choice3">
                    <input type="radio" id="choice3" name="choice" value="3">
                    <span>${quizInfo[index].infoChoice[2]}</span>
                </label> 
                <label for="choice4">
                    <input type="radio" id="choice4" name="choice" value="4">
                    <span>${quizInfo[index].infoChoice[3]}</span>
                </label> 
            `;

            let DescTag = `
                정답은 ${quizInfo[index].infoAnswer}.
                ${quizInfo[index].infoDesc}
            `;

            quizTitle.innerHTML = TypeTag;
            quizQuestion.innerHTML = QuestionTag;
            quizChoice.innerHTML = ChoiceTag;
            quizDesc.innerHTML = DescTag;


            //객관식 보기 선택자
            const quizChoiceSpan = quizWrap.querySelectorAll(".quiz__choice span");
            const quizChoiceInput = quizWrap.querySelectorAll(".quiz__choice input");
            
            // quizChoiceSpan.forEach((span, num)=>{
            //     span.setAttribute("onclick", "choiceSelected(this)");
            // });

            for(i=0; i<quizChoiceSpan.length; i++){
                quizChoiceSpan[i].setAttribute("onclick","choiceSelected(this)");
                quizChoiceInput.disabled = "true";
            }

            //다음 버튼 숨기기
            quizAnswer.style.display = "none";
            quizDesc.style.display = "none";

        };
        updateQuiz(quizCount);

        
        function choiceSelected(answer){
            let userAnswer = answer.textContent;                            //사용자 정답
            let currentAnswer = quizInfo[quizCount].infoAnswer;             //문제 정답
            
            if(userAnswer == currentAnswer) {
                console.log("O");
                dogWrap.classList.add("like");
            } else {
                console.log("X");
                dogWrap.classList.add("dislike");
            }

            //나타내기
            quizAnswer.style.display = "block";
            quizDesc.style.display = "block";
        }
        

        //정답 확인
        quizNext.addEventListener("click",()=>{
            quizCount++;
            updateQuiz(quizCount);
            dogWrap.classList.remove("like","dislike")
        });

 

 

 

 

 

const updateQuiz = (index) => { ...... }

 

:  updateQuiz 함수는 'index' 라는 매개변수를 받아들인다.

이 index는 quizInfo 배열에서 몇 번째 퀴즈 정보를 가져올지를 결정하는 인덱스 값이다.

 

 

 

 

 

let TypeTag = `

                           <span>${quizInfo[index].infoType}</span>

                           <em>${quizInfo[index].infoTime}</em>

                        `;

 

: TypeTag 변수는 HTML 태그 문자열을 저장하는 변수이다.

이 문자열은 quizInfo 배열에서 index에 해당하는 퀴즈 정보의 종류와 시간을 표시하는 HTML 태그 문자열이다.

 

 

 

 

 

let QuestionTag  = `

                           <span>${index+1}</span>

                           <em>${quizInfo[index].infoQuestion}</em>

                        `;

 

:QuestionTag 변수는 또 다른 HTML 태그 문자열을 저장하는 변수이다.

이 문자열은 quizInfo 배열에서 index에 해당하는 퀴즈 정보의 문제를 표시하는 HTML 태그 문자열이다.

index+1은 배열의 키가 0으로 시작한다는 것을 고려하여 실제 문제 번호와 맞추기 위해 1을 더해준 것이다.

 

 

 

 

let ChoiceTag = `
                <label for="choice1">
                    <input type="radio" id="choice1" name="choice" value="1">
                    <span>${quizInfo[index].infoChoice[0]}</span>
                </label>
                <label for="choice2">
                    <input type="radio" id="choice2" name="choice" value="2">
                    <span>${quizInfo[index].infoChoice[1]}</span>
                </label> 
                <label for="choice3">
                    <input type="radio" id="choice3" name="choice" value="3">
                    <span>${quizInfo[index].infoChoice[2]}</span>
                </label> 
                <label for="choice4">
                    <input type="radio" id="choice4" name="choice" value="4">
                    <span>${quizInfo[index].infoChoice[3]}</span>
                </label> 
            `;

 

: choiceTag 변수는 또 다른 HTML 태그 문자열을 저장하는 변수이다.

이 문자열은 quizInfo 배열에서 index에 해당하는 퀴즈 정보의 선택지를 표시하는 HTML 태그 문자열이다.

for  속성은 해당 선택지를 클릭하면 id 속성이 일치하는 input 요소가 선택되도록 한다.

 

 

 

 

let DescTag = `
                정답은 ${quizInfo[index].infoAnswer}.
                ${quizInfo[index].infoDesc}
            `;

 

: updateQuiz 함수 외부에서 호출되는 코드로, updateQuiz 함수에서 반환된  HTML 태그 문자열을 실제 문서에 삽입하는 역할을 한다.

DescTag 변수는 해당 문제의 정답과 설명을 담은 HTML 태그 문자열을 저장한다.

 

 

 

 

 

quizTitle.innerHTML = TypeTag;

quizQuestion.innerHTML = QuestionTag;

quizChoice.innerHTML = ChoiceTag;

quizDesc.innerHTML = DescTag;

 

: quizTitle, quizQuestion, quizChoice, quizDesc는 문서에서 해당 요소의 ID를 가진 HTML 태그들을 가리키는 변수이다.

이 변수들에 innerHTML 속성을 이용해서 updateQuiz 함수에서 반환된 태그 문자열을 삽입하면, 해당 페이지에서는 동적으로 생성된 퀴즈가 보여지게 된다.

즉, quizTitle에는 퀴즈의 종류와 시간을 표시하는 태그 문자열이, quizQuestion에는 문제를 표시하는 태그 문자열이, quizChoice에는 선택지를 표시하는 태그 문자열이, quizDesc에는 정답과 설명을 표시하는 태그 문자열이 삽입되게 된다.

 

 

 

 

const quizChoiceSpan = quizWrap.querySelectorAll(".quiz__choice span");
const quizChoiceInput = quizWrap.querySelectorAll(".quiz__choice input");

 

: 첫째 quizWrap 요소에서 quiz_choice span과 quiz__choice input을 모두 선택해서 각각  quizChoiceSpan과 quizChoiceInput 변수에 저장한다.

 

 

 

 

for (i = 0; i < quizChoiceSpan.length; i++) {
  quizChoiceSpan[i].setAttribute("onclick", "choiceSelected(this)");
  quizChoiceInput.disabled = "true";
}

 

: 반복문을 이용해  quizChoiceSpan의 모든 요소에 onclick 요소를 추가한다

setAttribute 메서드는 HTML 요소에 속성을 추가하거나 수정하는 메서드이다.

여기에서는 onclick 속성을 추가하고, 값으로 choiceSelected(this)를 할당한다.

this는 현재 클릭한 요소를 가리킨다.

따라서 choiceSelected 함수를 호출할 때, 해당 선택지에 대한 정보를 인자로 전달할 수 있다.

 

또한 quizChoiceInput.disabled = "true";는 선택지에 대응하는 input 요소를 비활성화 한다.

이는 선택지를 클릭했을 때, 선택지를 다시 클릭할 수 없도록 만드는 역할을 한다.

 

 

 

 

 

quizAnswer.style.display = "none";

quizDeac.style.display = "none";

};

        updateQuiz(quizCount);

 

:quizAnswer와 quizDesc 요소를 화면에서 숨긴다.

style 속성의 display 값으로 none을 지정함으로써 해당 요소를 화면에서 숨길 수 있다.

이 구문을 추가함으로써 초기 상태에서는 정답과 설명이 보이지 않는다.

 

updateQuiz 함수를 호출하고 quizCount 값을 전달한다.

quizCount는 현재 문제의 인덱스를 저장하는 변수이며, 페이지가 로드 되었을 때 첫 번째 문제가 화면에 나타나게 된다.

 

 

 

 

function choiceSelected(answer){
         
let userAnswer = answer.textContent;                                               // 사용자가 선택한 답변을 가져옴.    

          let currentAnswer = quizInfo[quizCount].infoAnswer;                         // 현재 문제의 정답을 가져옵니다.

 

 if(userAnswer == currentAnswer) {

        console.log("O");

       dogWrap.classList.add("like");

} else {

        console.log("X");

        dogWrap.classList.add("dislike");

}

    quizAnswer.style.display = "block";

    quizDesc.style.display = "block";

 

: answer 매개변수는 사용자가 선택한 답변을 나타내는 HTML 요소이다.

answer.textContent를 사용하여 사용자가 선택한 답변의 내용을 가져와 userAnswer 변수에 저장한다.

 

그리고 quizInfo[quizCount].infoAnswer를 사용하여 현재 문제의 정답을 가져와  currentAnswer 변수에 저장한다.

 

그 다음 if문을 사용하여 unswerAnswe와 currentAnswer를 비교한다.

만약 두 값이 같다면 "O"를 콘솔에 출력하고 강아지 이미지에 "like"  클래스를 추가한다.

두 값이 다르다면 "X"를 콘솔에 출력하고 강아지 이미지에 "dislike" 클래스를 추가한다.

 

quizAnswer와 quizDesc 요소의 display 요소의 속성을 block으로 변경하여 정답과 해설을 보여준다.

 

 

 

 

 

 

728x90
반응형

 

 

 

 

 

완성 화면

 

 

 

 

 

코드 보기 / CSS

.quiz__choice{
    padding: 20px;
    border-bottom: 6px ridge #a12cd3;
    font-family: 'ElandChoice';
}
.quiz__choice label {
    display: flex;
}
.quiz__choice label input {
    position: absolute;
    clip: rect(0 0 0 0);
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
}
.quiz__choice label span {
    font-size: 20px;
    line-height: 1.5;
    padding: 6px;
    display: flex;
    cursor: pointer;
    margin: 2px 0;
}
.quiz__choice label span::before {
    content: '';
    width: 26px;
    height: 26px;
    border-radius: 50%;
    margin-right: 15px;
    background-color: #fff;
    box-shadow: inset 0 0 0 4px #63068a;
    transition: all 0.2s;
    flex-shrink: 0;
}
.quiz__choice label input:checked + span {
    background-color: #f6ecfa;
}
.quiz__choice label input:checked + span::before {
    box-shadow: inset 0 0 0 8px #63068a;
}
.quiz__check {
    position: fixed;
    right: 20px;
    bottom: 20px;
    width: 110px;
    height: 110px;
    line-height: 110px;
    border-radius: 50%;
    z-index: 1000;
    background-color: #9d07f4;
    text-align: center;
    font-family: 'ElandChoice';
    color: #fff;
    cursor: pointer;
}
.quiz__info {
    position: fixed;
    width: 130px;
    height: 50px;
    border-radius: 10px;
    line-height: 50px;
    right: 20px;
    bottom: 170px;
    background-color: #9d07f4;
    text-align: center;
    font-family: 'ElandChoice';
    color: #fff;
}
.quiz__info::after {
    content: '';
    position: absolute; 
    left: 50%;
    bottom: -10px;
    border-top: 10px solid #9d07f4;
    border-left: 10px solid transparent;
    border-right: 10px solid transparent;
}

 

 


 

quiz__ choice 

해당 클래스를 갖는 div 요소에 스타일을 적용한다.

이 div는 객관식 퀴즈에서 각 선택지를 감싸는 컨테이너로 사용된다.

 

padding: 20px

: 컨테이너의 안쪽 여백을 20px만큼 설정한다.

 

border-bottom: 6px ridge #a12 cd3

: 컨테이너의 아래쪽  경계선을 지정한다.

ridge는 경계선이 일반 실선이 아니라 옆에서 봤을 때 △ 모양으로 가운데가 튀어나온 스타일이다.

#a12cd3은 경계선의 색상(보라색)이다.

 

 


 

 

quiz__choice lable

:각 선택지의 라벨을 감싸는 label 요소.

 

display: flex

: 라벨 내부의 요소들을 수평으로 정렬한다.

 

 


 

 

quiz__choice label input

: 라벨 요소 내부에 위치한 라디오 버튼 (input 요소)에 스타일을 적용한다.

 

position: absolute

: 라디오 버튼을 페이지의 다른 요소와 겹치지 않도록 한다.

 

clip: rest(0 0 0 0)

: 라디오 버튼의 크기를 0으로 설정하여, 화면에 보이지 않게 한다.

 

 

width, height, margin, overflow

: 라디오 버튼과 라벨 텍스트를 정렬하기 위한 것이다.

 

 


 

 

quiz__choice label span 

: 라디오 버튼에 대응하는 라벨 텍스트(span 요소)에 스타일을 적용한다.

 

font-size: 20px

:텍스트의 크기를 설정한다.

 

line-height: 1.5

:텍스트의 높이를 설정한다.

 

padding: 6px

:텍스트 내부 여백을  6px만큼 설정한다.

 

display: flex

:라벨 내부의 요소들을 수평으로 정렬한다.

 

cursor: pointer

:마우스 커서가 텍스트 위에 올라가면 포인터 모양으로 변경한다.

 

 


 

 

quiz__choice label span::before

: 선택지의 선택 항목에서 선택되지 않은 원형 요소를 생성한다.

 

content: ' ';

:내용을 없앤다.

 

width, height, border-radius 

:가로 세로가 26px인 원형 모양을 만든다.

 

magin-right,

background-color,

box-shadow

:  내부 여백을 주고, 배경색을 설정하고 박스에 그림자 속성을 준다.

 

flex-shrink

:flex box 컨테이너 내에서 해당 요소가 줄어들지 않도록 한다.

 

 


 

 

quiz__choice label input:cheked + span

: 선택지의 선택 항목이 선택되었을 때의 스타일 지정한다.

 

input:cheked 

:선택된 항목의 span 요소 스타일을 변경한다.

 

background-color

: 선택되었을 때의 배경색을 변경.

 

 


 

 

quiz__choice lable input:checked + span::before

: 선택된 input 요소에 인접한 span 요소의 전/ 배경 색상이 변경되도록 한다.

 

box-shadow: inset 0 0 0 8px #63068a;

:그림자 효과를 만든다. 

inset은 그림자가 내부에 위치하도록 한다.

즉, span 요소의 내부에 테두리를 그린다.

그림자의 크기는 0 0 이며, 테두리의 두께는 8px이다.

그림자의 색상은 #63068a이다.

 

 

 

 

 


 

코드 보기 / JAVASCRIPT

//문제 정보
        const quizInfo = [
        {
                infoType: "정보처리 기능사",
                infoTime: "2010년 1회 (1월 31일)",
                infoNumber : "20100101",
                infoQuestion: "스택 연산에서 데이터를 삽입하거나 삭제하는 동작을 나타내는 연산으로 바르게 나타낸 것은?",
                infoChoice: {
                    1:"ADD, SUB",
                    2:"LOAD, STORE",
                    3:"PUSH, POP",
                    4:"MOV, MUL"
                },
                infoAnswer:"3",
                infoDesc: "push( ) 메서드는 배열 마지막 요소를 추가하는 스택 연산을 하고, pop( ) 메서드는 배열 마지막 요소를 삭제하는 스택 연산을 합니다."
            },{
                infoType: "정보처리 기능사",
                infoTime: "2010년 1회 (1월 31일)",
                infoNumber : "20100102",
                infoQuestion: "MIMD(Multiple Instruction Multiple Data)구조를 갖는 것은?",
                infoChoice: {
                    1: "다중 처리기",
                    2: "배열 처리기",
                    3: "벡터 처리기",
                    4: "파이프라인 처리기"
                },
                infoAnswer:"1",
                infoDesc: "MIMD(Multiple Instruction Multiple Data) : <br>멀티 명령어 멀티 데이터란 뜻으로 다중 명령, 다중 데이터라고 해석할수 있습니다. 즉, 다중 처리기에 관한 내용이 됩니다."
            },{
                infoType: "정보처리 기능사",
                infoTime: "2010년 1회 (1월 31일)",
                infoNumber : "20100103",
                infoQuestion: "논리적 주소에서 물리적 주소 또는 다른 논리적 주소로 번역하는 것은?",
                infoChoice:{
                    1:"매핑",
                    2:"적재",
                    3:"재배치",
                    4:"주소 바인딩"
                },
                infoAnswer:"4",
                infoDesc: "물리적 주소 또는 논리적 주소로 번역하는 것을 주소 바인딩이라고 합니다."
            },{
                infoType: "정보처리 기능사",
                infoTime: "2010년 1회 (1월 31일)",
                infoNumber : "20100104",
                infoQuestion: "컴퓨터의 중앙처리장치(CPU)의 구성 부분에 해당되지 않는 것은?",
                infoChoice:{
                    1:"주기억 장치",
                    2:"연산 장치",
                    3:"보조기억 장치",
                    4:"제어장치"
                },
                infoAnswer:"3",
                infoDesc: "CPU의 주 기능은 연산과 제어입니다. 그러나 여기에 추가적으로 기억 기능이 포함될 수는 있습니다. <br> 보조기억장치의 대표적인 예로는 HDD와 SSD가 있습니다."
            },{
                infoType: "정보처리 기능사",
                infoTime: "2010년 1회 (1월 31일)",
                infoNumber : "20100105",
                infoQuestion: "명령어의 주소(Address)부를 연산주소(Address)로 이용하는 주소지정방식은?",
                infoChoice:{
                    1:"상대 Address 방식",
                    2:"절대 Address 방식",
                    3:"간접 Address 방식",
                    4:"직접 Address 방식"
                },
                infoAnswer:"4",
                infoDesc: "절대주소는 실제주소(유효주소)이며 0,1,2,3... 순서로 차례대로 지정되는 주소방식입니다. <br>절대주소는 1byte 마다 연속된 16진수 번호를 부여하며 기계어의 정보가 기억되어 있습니다.<br> 상대주소는 기준주소를 기준으로 상대적으로 얼만큼 떨어져있는지 표시하는 주소입니다."
            },{
                infoType: "정보처리 기능사",
                infoTime: "2010년 1회 (1월 31일)",
                infoNumber : "20100106",
                infoQuestion: "인터럽트 발생 시 인터럽트를 처리하고 원래 수행하고 있었던 프로그램으로 되돌아가는데 사용되는 레지스터는?",
                infoChoice:{
                    1:"Stack",
                    2:"PC",
                    3:"MBR",
                    4:"PSW"
                },
                infoAnswer:"1",
                infoDesc: "인터럽트 발생시 현재 수행하고 있던 프로그램 주소를 스택(Stack)에 기억시킨뒤 인터럽트 처리후 스택의 주소를 참조하여 원래 수행하던 프로그램으로 되돌아 갑니다."
            }
        ]
        
         //선택자
        const quizWrap = document.querySelector(".quiz__wrap");
        const dogWrapTrue = quizWrap.querySelectorAll(".true");
        const dogWrapFalse = quizWrap.querySelectorAll(".false");
        let quizScore = 0;

        //출력
        const updateQuiz = () => {
            const exam = [];

            quizInfo.forEach((question, number) => {
                exam.push(`
                <div class="quiz">
                    <div class="quiz__header">
                        <h2 class="quiz__title">${question.infoType} ${question.infoTime}</h2>
                    </div>
                    <div class="quiz__main">
                        <div class="quiz__question"><em>${number+1}</em>. ${question.infoQuestion}</div>
                        <div class="quiz__view">
                            <div class="dog__wrap">
                                <div class="true">정답</div>
                                <div class="false">오답</div>
                                <div class="card-container">
                                    <div class="dog">
                                        <div class="head">
                                            <div class="ears"></div>
                                            <div class="face"></div>
                                            <div class="eyes">
                                                <div class="teardrop"></div>
                                            </div>
                                            <div class="nose"></div>
                                            <div class="mouth">
                                                <div class="tongue"></div>
                                            </div>
                                            <div class="chin"></div>
                                        </div>
                                        <div class="body">
                                            <div class="tail"></div>
                                            <div class="legs"></div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                        <div class="quiz__choice">
                            <label for="choice1${number}">
                                <input type="radio" id="choice1${number}" name="choice${number}" value="1">
                                <span>${question.infoChoice[1]}</span>
                            </label>
                            <label for="choice2${number}">
                                <input type="radio" id="choice2${number}" name="choice${number}" value="2">
                                <span>${question.infoChoice[2]}</span>
                            </label> 
                            <label for="choice3${number}">
                                <input type="radio" id="choice3${number}" name="choice${number}" value="3">
                                <span>${question.infoChoice[3]}</span>
                            </label> 
                            <label for="choice4${number}">
                                <input type="radio" id="choice4${number}" name="choice${number}" value="4">
                                <span>${question.infoChoice[4]}</span>
                            </label> 
                        </div>
                        <div class="quiz__desc">정답 : <em>${question.infoAnswer}</em>번<br>${question.infoDesc}</div>
                    </div>
                </div>
                `);
            });

            exam.push(`
            <div class="quiz__info">??점</div>
            <div class="quiz__check">정답 확인하기</div>
            `);
            quizWrap.innerHTML = exam.join(' ');

            //설명 숨기기
            document.querySelectorAll(".quiz__desc").forEach(el => el.style.display = "none");
        }
        updateQuiz();

        //정답 확인
        const answerQuiz = () => {
        const quizChoices = document.querySelectorAll(".quiz__choice");

            //사용자가 체크한 답과 문제의 정답이 일치하는지 확인
            quizInfo.forEach((question, number) => {
                const userSelector = `input[name=choice${number}]:checked`;
                const quizSelectorWrap = quizChoices[number];
                const userAnswer = (quizSelectorWrap.querySelector(userSelector) || {}).value;
                const dogWrap = quizWrap.querySelectorAll(".dog__wrap");
                
                if(userAnswer == question.infoAnswer) {
                    dogWrap[number].classList.add("like");
                    quizScore++;
                } else {
                    dogWrap[number].classList.add("dislike");
                }
            });
            //해설보이기
            document.querySelectorAll(".quiz__desc").forEach(el => el.style.display = "block");
            
            //점수 보이기
            document.querySelector(".quiz__info").innerHTML = Math.round((quizScore / quizInfo.length) * 100) + "점";
        }

        //정답 클릭
        document.querySelector(".quiz__check").addEventListener("click", answerQuiz);

 

 

const exam = [ ];

: 빈 배열을 선언한다.

 

quizinfo.forEach((question, number)) => { .... });

: quizinfo 배열의 각 요소를 반복하여 HTML 문자열을 만든다.

forEach( )메서드를 사용하여 quizinfo 배열의 각 요소를 반복한다.

 

여기서 콜백 함수가 사용된다.

콜백함수의 첫 번째 매개변수인 question은 quizifo 배열의 현재 요소를 나타내며, number은 quizinfo 배열에서 현재 요소의 인덱스를 나타낸다.

 

 

 

exam.push( .... )

: HTML 문자열을 생성하고 exam 배열에 추가한다.

이 HTML 문자열은 quizWrap 요소에 추가된다.

 

` ` (백틱)을 사용하여 템플릿 리터럴을 만들고 HTML 문자열의 내용은 question 객체의 속성들을 이용하여 생성된다

예를 들어` ${question.infoType}` , ` ${question.intoTime}`은  question 객체의 infoType과infoTime  속성을 사용하여 문제의 유형과 시간 정보를 생성한다.

 

 

exam.push(`

<div class="quiz__info">??점</div>

<div class="quiz__check">정답 확인하기</div>

`);

 

:exam 배열에 최종 점수를 나타내는 요소와 정답 확인 버튼을 추가한다.

quiz__info와 quiz__check 클래스를 가진 요소를 추가한다.

 

 

 

quizWrap.innerHTML = exam.join(' ');

: exam 배열을 문자열로 변환하고, quizWrap 요소의 innerHTML에 할당하여 화면에 출력한다.

 

 

 

document.querySelectorAll(".quiz__desc").forEach(el => el.style.display = "none");

:quiz__desc 클래스를 가진 모든 요소에 대해 반복문을 실행하면서 style.display 속성을 none으로 주어 해당 요소를 숨긴다.

 

 

 

updateQuiz( );

: 함수는 quizInfo 배열을 참조하영 문제와 선택지를 생성하고, 마지막으로 정답 확인 버튼을 생성한다.

이 때, 정답 확인 버튼을 클릭하기 전까지는 정답에 대한 설명을 숨기기 위해 quiz__desc 클래스를 가진 요소를 숨기는 역할을 수행합니다.

 

 

 

document.querySelectorAll(".quiz__choic")

: 함수 내부의 HTML 문서에서 quiz__choice 클래스를 가진 모든 요소를 찾는다.

이는 사용자가 선택 가능한 모든 답안을 선택하는 것이다.

 

 

 

quizInfo.forEach((question, number) => { .... }

:  quizInfo 배열을 문제에 대한 정보를 포함하고 있으며, 각 항목은 문제와 답안의 정보를 저장한다.

이 배열을 사용하여 사용자가 선택한 답과 무제의 정답을 비교하고 점수를 계산한다.

quizInfo 배열을 순회하면서 현재 문제의 정보와 문제 번호를 가져온다.

 

 

const userSelector = `input[name=choice${number}]:checked`;

: 사용자가 선택한 답안 요소의 선택된 값을 가져온다. 

선택된 라디오 버튼의 값이 아닌 라디오 버튼 요소 자체의 값을 가져온다.

 

 

 

const quizSelectorWrap = quizChoices[number];

: quizSelectorWrap 변수는 현재 문제 번호에 해당하는 답안 요소를 가져온다. 이 변수는 quizChoic[number]

를 사용하여 quiz__choice 클래스를 가진 요소 중 numder 번째 요소를 선택한다.

 

 

 

const userAnswer = (quizSelectorWrap.querySelector(userSelector) || {}).value;

: userAnswer 변수를 사용하여 사용자가 선택한 답안의 값을 가져온다. 

이때, quizSelectorWrap.querySelector(userSelector)를 사용하여 현재 문제 번호에 해당하는 답안 요소에서 선택된 라디오 버튼의 값을 가져온다.

만약 선택된 라디오 버튼이 없는 경우 { }를 사용하여 undefinde를 방지하고 value를 사용하여 해당 값을 가져온다.

 

 

 

const dogWrap = quizWrap.querySelectorAll(".dog__wrap");

:  dogWrap 변수를 사용하여 quizWrap.querySelectorAll(".dog__wrap") 을 사용하여 dog__wrap 클래스를 가진 모든 요소를 가져온다. 이 배열은 문제의 각 항목에 대한 강아지 이미지 요소를 저장한다.

 

 

 

if(userAnswer==question.infoAnswer) {.....}

: 사용자가 선택한 답안과 문제의 정답을 비교하고, 일치하는 경우 dogWrap[number].classList.add(
"like")를 사용하여 해당 문제의 강아지 이미지 요소에 like 클래스를 추가한다.

이를 통해 사용자가 정답을 맞춘 문제에 대한 피드백(웃는 모습의 강아지)을 제공한다.

 

반면, 사용자가 선택한 답안이 정답과 일치하지 않는 경우

dogWrap[number].classList.add("dislike"):해당 문제의 강아지 이미지 요소에 dislike 클래스를 추가한다.

 

 

 

document.querySelectorAll(".quiz__desc").forEach(el => el.style.display = "block");

:HTML 문서에서 quiz__desc 클래스를 가진 모든 요소를 찾아서, el.style.display="block"을 사용하여 해당 요소를 보이게 설정한다.

이는 사용자가 문제를 푼 후에 해설을 보여주는 역할을 한다.

quiz__desc 클래스를 가진 요소는 문제와 관련된 해설을 담고 있다.이를 통해 사용자는 문제에 대한 해설을 읽을 수 있다.

 

 

 

document.querySelector(".quiz__info").innerHTML = Math.round((quizScore / quizInfo.length) * 100) + "점"; }

: quiz__info 클래스를 가진 요소를 찾아서 해당 요소의 내용을 계산된 점수로 변경한다.

Math.round ( ....)는 사용자의 정답 수를 전체 문제수로 나눈 비율에 100을 곱하여 백분율 점수로 변환한다.

이때, Math.round( ) 함수는 소수점 이하를 반올림한다.

 

 

 

 

document.querySelector(".quiz__check").addEventListener("click", answerQuiz);

: quiz__check 클래스를 가진 요소를 찾아서 클릭 이벤트가 발생할 때 answerQuiz 함수를 실행한다.

 

즉, 사용자가 정답 확인 버튼인 quiz__check를 클릭하면 answerQuiz 함수가 실행되어 사용자의 답을 채점하고, 점수를 계산한 후 결과를 HTML 문서에 표시한다.

사용자는 자신의 시험 결과를 확인할 수 있다.

 

 

 

 

 

 

 

 

728x90
반응형

 

 

완성 화면

 

 

 

 

 

코드 보기 / HTML

<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>검색 효과(seacrh Effect)</title>

    <link rel="stylesheet" href="css/reset.css">
    <link rel="stylesheet" href="css/search.css">
</head>
<body class="pink">
    <header id="header">
        <ul>
            <li class="active"><a href="searchEffect01.html">1</a></li>
            <li><a href="#">2</a></li>
            <li><a href="#">3</a></li>
            <li><a href="#">4</a></li>
            <li><a href="#">5</a></li>
            <li><a href="#">6</a></li>
        </ul>
    </header>
    <main id="main">
        <div class="search__wrap">
            <div class="search__header">
                <h2 class="title">JAVASCRIPT</h2>
            </div>
            <div class="search__contents">
                <hgroup>
                    <h3>CSS 속성 검색하기</h3>
                    <h4>indexOf( ) / search( )</h4>
                </hgroup>
                <div class="search__box">
                    <label for="search">검색</label>
                    <input type="text" name="search" id="search" placeholder="CSS 속성을 입력해주세요">
                </div>
                <div class="search__info">
                    <div>
                        CSS 속성 : <span></span>개
                    </div>
                </div>
                <div class="search__list">
                    <ul class="listNumber">
                    	<li data-name="align-content"><strong>align-content</strong> : 콘텐츠 아이템의 상하관계 정렬 상태를 설정.</li>
                        <li data-name="align-items"><strong>align-items</strong> : 콘텐츠 아이템의 내부 상하관계 정렬 상태를 설정.</li>
                        <li data-name="align-self"><strong>align-self</strong> : 개별적인 콘텐츠 아이템의 정렬 상태를 설정.</li>
                        <li data-name="all"><strong>all</strong> : 요소의 속성을 초기화 또는 상속을 설정.</li>
                        <li data-name="animation"><strong>animation</strong> : 애니메이션과 관련된 속성을 일괄적으로 설정.</li>
                        <li data-name="animation-delay"><strong>animation-delay</strong> : 애니메이션 지연 시간을 설정.</li>
                    </ul>
                </div>
            </div>
        </div>
    </main>
    <footer id="footer">
        <a href="mailto:goed0522@gmail.com">goed0522@gmail.com</a>
    </footer>

 

 

 

 

 

코드 보기 / CSS

/* header */
#header {
    padding: 1.5vw 1.5vw 2.5vw 1.5vw;
    text-align: center;
}
#header li {
    list-style: none;
    display: inline-block;
}
#header li a{
    text-decoration: none;
    width: 55px;
    height: 55px;
    line-height: 55px;
    display: inline-block;
    font-size: 24px;
    color: #000;
    border: 2px solid #000;
    border-radius: 10%;
}
#header li:nth-child(3n+1) {
    background-color: #AF6EE3;
}
#header li:nth-child(3n+2){          
    background-color: #FFEF4A;
}
#header li:nth-child(3n+3){    
    background-color: #F76ECE;
}
/* main */
#main {}

/* main > search__wrap */
.search__wrap {
    width: 90%;
    margin: 0 auto;
    background-color: #fff;
    border: 0.3vw solid #000;
    border-top-left-radius: 3vw;
    border-bottom-right-radius: 3vw;
    box-shadow: 1vw 1vw 0px #000;
    transition: box-shadow 0.3s;
    overflow: hidden;
}
.search__wrap:hover {
    box-shadow: 0.5vw 0.5vw 0px #000;
}
.search__header {
    border-bottom: 0.3vw solid #000;
    padding: 0 0 0 3vw;
    background-color: #AF6EE3;
    position: relative;
    display: flex;
    align-items: center;
    justify-content: space-between;
}
.search__header .title {
    position: relative;
    background: #AF6EE3;
    position: relative;
    z-index: 10;
    padding-right: 35px;
    padding: 0.6vw 8.5vw 0.6vw 5.5vw;
}
.search__header::before {
    content: "";
    width: 90%;
    height: 0.3vw;
    background: #000;
    position: absolute;
    right: 0;
    top: 50%;
    transform: translateY(-10px);
    border-radius: 5px;
}
.search__header::after {
    content: "";
    width: 90%;
    height: 0.3vw;
    background: #000;
    position: absolute;
    right: 0;
    top: 50%;
    transform: translateY(4px);
    border-radius: 5px;
}
.search__header .btn {
    display: flex;
}
.search__header .btn span{
    display: block;
    width: 3vw;
    height: 3vw;
    line-height: 3vw;
    position: relative;
    z-index: 10;
    border-left: 0.3vw solid #000;
    box-sizing: content-box;
    text-align: center;
    font-size: 2vw;
    cursor: pointer;
}
.search__contents hgroup{
    padding: 3vw 5vw;
    text-align: center;
    border-bottom: 3px solid rgba(0, 0, 0, 0);
}
.search__contents hgroup h3{
    color: #FFEF4A;
    font-size: 8vw;
    text-shadow: 0.6vw 0.6vw 0px #000;
    -webkit-text-stroke: 0.3vw #000;
}
.search__contents hgroup h4 {
    color: #AF6EE3;
    font-size: 7vw;
    text-shadow: 0.6vw 0.6vw 0px #000;
    -webkit-text-stroke: 0.3vw #000;
}
.search__box label {
    padding: 1vw;
    padding: 1vw;
    font-size:2.5vw;
    font-family: 'CookieRun';
}
.search__box input {
    border: 0.4vw solid #000;
    width: 80%;
    box-shadow: 0.4vw 0.4vw 0 s#000;
    padding: 1.5vw 2.5vw;
    border-radius: 50px;
    font-family: 'CookieRun';
    font-size: 2vw;
    outline: none;
}
.search__info {
    text-align: right;
    padding: 0.5vw 2vw;
    border-bottom: 0.3vw dashed #000;
}
.search__list ul {
    padding: 3.5vw;
}
.search__list li {
 list-style: none;
 line-height: 2.5;
}
.search__list li.hide {
    display: none;
}
/* footer */
#footer {
    text-align: center;
    padding: 3.5vw;
}
#footer a {
    color: #000;
}

 

#header

padding: 1.5vw 1.5vw 2.5vw 1.5vw; - 뷰포트 width가 1.5, 2.5만큼 상하좌우의 여백을 지정한다.

text-align: center; - 헤더 안에 있는 텍스트들을 가운데 정렬한다.

 

 

 

#header li

list-style: none; - 리스트의 기본 스타일을 none으로 두어 리스트 마커를 없앤다.

display: inline-block; - 요소들의 속성을 inline-block으로 설정하여 요소들이 가로로 나열되도록 한다.

 

 

 

#header li a

text-decoration: none; - link 속성을 none으로 주어 하이퍼링크에 설정되는 밑줄과 색을 없앤다.

width: 55px; - 가로(너비)의 크기가 55px

height: 55px; - 세로(높이)의 크기가 55px

line-height: 55px; - 줄간격을 가로 세로 높이와 똑같이 두어 텍스트가 요소의 정가운데에 올 수 있도록 한다.

display: inline-block; - 요소들의 속성을  inline-block으로 설정하여 요소들이 가로로 나열되도록 한다.

font-size: 24px;  - 글자 크기를 24px로 설정한다.

color: #000; - 글자 색을 까만색으로 한다

border: 2px solid #000; - 테두리를 2px만큼 일반 실선, 검정색으로 설정한다.

border-radius: 10%; - 테두리의 모서리가 10%만큼 원형으로 둥글게 처리된다.

 

 

 

#header li:nth-child(3n+1)

background-color: #AF6EE3; - 헤더 안의 리스트 항목들 중 1,4,7, 10 ~ 번째 항목들에게 배경색을 보라색으로 지정한다.

 

 

 

#header li:nth-child(3n+2)

background-color: #FFEF4A; - 헤더 안의 리스트 항목들 중 2, 5, 8, 11 ~ 번째 항목들에게 배경색을 노란색으로 지정한다.

 

 

 

#header li:nth-child(3n+3)

background-color: #F76ECE; - 헤더 안의 리스트 항목들 중 3, 6, 9, 12 ~ 번째 항목들에게 배경색을 분홍색으로 지정한다.

 

 

 

.search__wrap  - 컨테이너 요소

width: 90%;   - 가로(너비)가 90%

margin: 0 auto;   - 가운데에 위치하도록 지정한다.

background-color: #fff;     - 검색 바의 배경색을 하얀 색으로 지정한다.

border: 0.3vw solid #000;   - 두께가 0.3vw (뷰포트)인 검은색 테두리를 지정한다.

border-top-left-radius: 3vw;  - 왼쪽 상단의 모서리를 3vw만큼 둥글게 지정한다.

border-bottom-right-radius: 3vw;  - 오른쪽 하단의 모서리를 3vw만큼 둥글게 지정한다.

box-shadow: 1vw 1vw 0px #000;  -  검색 바의 그림자 효과를 가로와 세로 방향으로 1vw만큼 지정한다.

transition: box-shadow 0.3s;  - 그림자 효과에 0.3초 동안 변화하는 효과를 지정한다.

overflow: hidden;  - 튀어나오는 부분은 보이지 않도록 설정한다.

 

 

 

.search__wrap:hover

box-shadow: 0.5vw 0.5vw 0px #000;  - 요소 위로 마우스를 가져갈 때, 그림자 효과가 가로 및 세로 방향으로 0.5vw로 변경되도록 설정한다.

 

 

 

.search__header  - 헤더 요소

border-bottom: 0.3vw solid #000;  - 하단 테두리를 검은색으로 0.3vw만큼 설정한다.

padding: 0 0 0 3vw;   - 검색 창 헤더의 좌측(왼쪽) 여백을 3vw만큼 설정한다.

background-color: #AF6EE3; - 검색 창 헤더의 배경 색상을 보라색으로 설정한다.

position: relative;  - 검색 창 헤더를 상대적인 위치로 설정한다.

display: flex; - 검색 창 헤더를 플렉스 박스로 설정한다.

align-items: center; - 검색 창 헤더 내의 요소들을 수직으로 중앙 정렬한다.

justify-content: space-between; - 검색 창 헤더 내의 요소들을 수평으로, 동일한 간격으로 배치한다.

 

 

 

.search__header .title - 헤더 내의 클래스 이름이 title인 요소

position: relative; -  제목 요소를 상대적인 위치로 설정한다.

background: #AF6EE3; - 제목 요소의 배경 색상을 보라색으로 설정한다.

z-index: 10; - 이 요소가 다른 요소드로가 겹치는 경우, 위로 올라오도록 한다.

padding-right: 35px; - 오른쪽 내부 여백을 35px로 설정한다.

padding: 0.6vw 8.5vw 0.6vw 5.5vw;  - 제목 요소 내부의 여백을 위, 오른쪽, 아래, 왼쪽 순서로 각각 다른 여백을 설정한다. 제목 내의 텍스트가 적절한 간격으로 배치되도록 하는 역할도 한다.

 

 

 

.search__header::before - 헤더의 가상 요소

content: " ";  - 가상 요소의 내용을 비운다.

width: 90%; - 가상 요소의 길이를 부모 요소의 90%로 설정한다.

height: 0.3vw; - 세로(높이)를 0.3vw(뷰포트)로 설정한다.

background: #000; - 배경색상을 검은색으로 지정한다.

position: absolute;  - 가상 요소를 절대적인 위치로 설정한다.

right: 0;  top: 50%; - 가상 요소를 부모의 오른쪽 끝, 상단에서 50% 높이에 위치 시킨다.

transform: translateY(-10px);  - 가상 요소를 y축으로 -10px만큼 이동시킨다. 요소의 중앙이 부모 요소의 상단에서 50% 높이에 위치하도록 하는 역할을 한다.

border-radius: 5px;  - 가상 요소의 테두리를 5px만큼 둥글게 처리한다.

 

 

 

.search__header::after - 헤더의 가상 요소

content: " "; - 가상 요소의 내용을 비운다.

width: 90%; - 가상 요소의 길이를 부모 요소의 90%로 설정한다.

height: 0.3vw; - 세로(높이)를 0.3vw(뷰포트)로 설정한다.

background: #000; - 배경색상을 검은색으로 지정한다.

position: absolute; - 가상 요소를 절대적인 위치로 설정한다.

right: 0; top: 50%; - 가상 요소를 부모의 오른쪽 끝, 상단에서 50% 높이에 위치 시킨다.

transform: translateY(4px);  - 가상 요소를 y축으로 4px만큼 이동시킨다. 요소가 아래쪽에 위치하도록 하는 역할을 한다.

border-radius: 5px; - 가상 요소의 테두리를 5px만큼 둥글게 처리한다.

 

 

 

.search__header .btn - 검색창 헤더 내의 클래스 이름이 btn인 요소

display: flex;  - 요소를 플렉스 박스로 지정한다.

 

 

 

.search__header .btn span - 클래스 이름이 btn인 요소 안에 있는 span 태그

display: block; - span 태그를 블록 레벨 요소로 표시한다.

width: 3vw; - 가로(너비)를 뷰포트의 너비 3%로 설정한다.

height: 3vw; - 세로(높이)를 뷰포트의 높이 3%로 설정한다.

line-height: 3vw; - span 태그의 행 높이를 뷰포트의 높이 3%로 설정한다.

position: relative;  - span 태그를 상대적 위치로 배치한다.

z-index: 10;  - 다른 요소들보다 위에 표시되도록 한다.

border-left: 0.3vw solid #000;  - span 태그의 왼쪽 테두리 두께를 뷰포트의 0.3만큼, 검정색으로 설정한다.

box-sizing: content-box;  - span 태그 요소의 크기 계산 방법을 컨텐츠 박스 기준으로 설정한다

text-align: center;  요소 내부 텍스트를 중앙 정렬 시킨다.

font-size: 2vw;  - 글자의 크기를 2vw(뷰포트)만큼 설정한다.

cursor: pointer;  - 해당 요소 위에 마우스 커서가 올라가면 포인터 모양으로 변경되게 설정한다.

 

 

 

.search__contents hgroup  - search__contents 클래스를 가진 요소 안에 있는 hgroup 태그

padding: 3vw 5vw; - 요소의 내부 여백을 위, 아래는 화면 너비의 3%, 좌우는 화면 너비의 5%로 설정한다

text-align: center; -요소 내부의 텍스트를 중앙 정렬한다

border-bottom: 3px solid rgba(0, 0, 0, 0);  - 요소의 하단 경계선을 3px의 두께와 투명한 색상으로 설정한다.

 

 

.search__contents hgroup h3 - search__contents 클래스를 가진 요소 안에 있는 hgroup 태그 내부의 h3 태그

color: #FFEF4A;  - 요소의 텍스트 색상을 노란색으로 설정한다.

font-size: 8vw;  - 글꼴 크기를 화면 너비의 8%로 설정한다.

text-shadow: 0.6vw 0.6vw 0px #000;  - 요소의 텍스트에 그림자 효과를 추가한다. 가로와 세로의 그림자 거리는 화면 너비의 0.6%, 색상은 검은색으로 설정한다.

-webkit-text-stroke: 0.3vw #000;   -  요소의 텍스트에 0.3%만큼의 굵기로 검은색 외곽선을 추가한다.

 

 

.search__contents hgroup h4  - search__contents 클래스를 가진 요소 안에 있는 hgroup 태그 내부의 h4 태그

color: #AF6EE3; - 요소의 텍스트 색상을 보라색으로 설정한다.

font-size: 7vw;  - 글꼴 크기를 화면 너비의 7%로 설정한다.

text-shadow: 0.6vw 0.6vw 0px #000;  - 요소의 텍스트에 그림자 효과를 추가한다. 가로와 세로의 그림자 거리는 화면 너비의 0.6%, 색상은 검은색으로 설정한다.

-webkit-text-stroke: 0.3vw #000;   -  요소의 텍스트에 0.3%만큼의 굵기로 검은색 외곽선을 추가한다.

 

 

 

.search__box label   - search__box 클래스를 가진 lable 요소

padding: 1vw;   - 내부 여백을 화면의 1%로 설정한다.

font-size:2.5vw;   - 요소의 텍스트 사이즈를 2.5%로 설정한다.

font-family: 'CookieRun';   - 텍스트의 글꼴은 CookieRun이라는 폰트를 사용한다.

 

 

 

.search__box input  - 검색 창의 입력 필드

border: 0.4vw solid #000;   - 테두리를 0.4%로 설정한다.

width: 80%;  - 요소의 너비를 부모 요소의 80%로 설정한다.

box-shadow: 0.4vw 0.4vw 0 s#000;  - 텍스트에 그림자 효과가 화면의 0.4%만큼 추가된다.

padding: 1.5vw 2.5vw;  - 입력 필드의 내부 여백을 상하로 1.5%만큼 좌우로 2.5%만큼 설정한다.

border-radius: 50px;  - 요소의 모서리를 50px만큼 둥글게 설정한다.

font-family: 'CookieRun';  - 텍스트의 글꼴은 CookieRun이라는 폰트를 사용한다.

font-size: 2vw;  - 텍스트의 크기는 화면의 2%만큼 설정한다.

outline: none;    - 입력 필드이 포커스가 되었을 때 테두리를 제거하는 속성이다.

 

 

.search__info - 검색 결과 페이지에서 사용돌 search__info  클래스를 정의

text-align: right; - 해당 요소 내에서 텍스트를 오른쪽으로 정렬하도록 지정한다.

padding: 0.5vw 2vw;  - 해당 요소의 내부 여백을 상하는 0.5%로 좌우는 2%를 각각 적용한다.

border-bottom: 0.3vw dashed #000;   - 해당 요소의 하단 경계선을 두께는 화면의 0.3%의 점선, 검정색으로 지정한다.

 

 

 

.search__list ul  - 검색 결과 페이지에서 사용되는 목록

padding: 3.5vw;   - 목록의 내부 여백을 뷰포트 너비의 3.5%로 정의한다

 

 

 

.search__list li   - 목록 내부의 각 항목을 정의

list-style: none;    - 리스트 스타일(목록 마커)를 표시하지 않도록 설정한다.

line-height: 2.5;   -  목록의 줄간격을 뷰포트의 2.5배로 설정한다.

 

 

 

.search__list li.hide   - 숨김 클래스가 적용된 목록 항목을 정의

display: none;   - 해당 항목을 화면에 표시하지 않도록 설정한다.

 

 

 

 

 

 

 

코드 보기 / JAVASCRIPT

 //선택자
        const searchBox = document.querySelector(".search__box input");
        const searchList = document.querySelectorAll(".search__list li");
        const listNumber = document.querySelector(".search__info span")

        //검색
        searchBox.addEventListener("keyup",() => {
            const userWord = searchBox.value;             

            searchList.forEach((el,i) =>{
                // const cssName = el.getAnimations("data-name");
                const cssName = el.dataset.name;
                listNumber.innerHTML = i;
                
                if(cssName.indexOf(userWord)) {             
                    el.classList.add("hide");                 
                } else {                                    
                    el.classList.remove("hide");  
                }
            })
        });

 

 

const searchBox = document.querySelector(".search__box input");

const searchList = document.querySelectorAll(".search__list li");

const listNumber = document.querySelector(".search__info span");

 

: 검색창, 검색겨로가 목록, 검색결과 정보를 나타내는 요소들을 각각 찾아와서 변수에 할당한다.

querySelector( ) 메서드를 사용하여 CSS 선택자를 통해 요소를 찾아온다.

 

 

searhBox에 키보드 이벤트(keyup) 리스너를 등록하여 사용자가 키보드로 입력하는 값을 userWord에 할당한다.

 

forEach( ) 메서드를 사용하여 검색결과 목록의 각 요소를 순회한다.

각 요소의 dataset 속성 중 name 속성 값을 가져온 후 userWord 값이 포함되어 있지 않다면 hide 클래스를 추가하여 해당 요소를 화면에서 감추고, 포함되어 있다면 hide 클래스를 제거하여 화면에 표시한다.

 

검색결과 정보를 나타내는 요소에는 검색결과 목록에서 표시된 항목 수를 업데이트 한다.

 

 

 

 

 

 

 

 

728x90
반응형

 

 

완성 화면

 

 

코드 보기 / CSS

 #header {
            z-index: 20;
        }
        .mouse__wrap {
            cursor: none;
        }
        .mouse__text {
            width: 100%;
            height: 100vh;
            overflow: hidden;
            display: flex;
            align-items: center;
            justify-content: center;
            flex-direction: column;
            position: relative;
            z-index: 10;
        }
        .mouse__text p {
            font-size: 2vw;
            line-height: 1.6;
        }
        .mouse__cursor {
            position: absolute;
            left: 0;
            top: 0;
            width: 200px;
            height: 200px;
            border-radius: 50%;
            border: 3px solid #fff;
            background-color: #ffffff7a;
            background-image: url(img/mouseEffect03-min.jpg);
            background-size: cover;
            background-position: center center;
            background-attachment: fixed;
        }

 

 

#header

: ID를 가진 요소에 z-index: 20을 적용하여 #header 요소(큰 제목과 소제목, 버튼 링크 요소)가 다른 요소들보다 더 위에 나

 

 

.mouse__wrap

:class를 가진 요소에 cursor: none; 속성을 주어 마우스 커서를 숨김 처리한다.

 

 

.mouse__text

: mouse__text라는  class를 가진 요소는 HTML 요소는 뷰포트 크기와 같은 전체 화면을 차지(width: 100%, height: 100vh)하면서 수직 및 수평 가운데로 정렬(align-item: center, justfy-content: center)되고, 이 요소 내부의 자식 요소들이 수직으로 배치(display:flex, flex-direction: column) 된다.

이 요소는 다른 요소들보다 위에 배치된다.(z-index: 10)

 

 

.mouse__text p

:mouse__text라는 class에 속한 모든 p 태그에 대한 스타일을 지정한다.

글꼴 크기는 2vw로, 현재 뷰포트 너비(%)에 따라 글꼴 크기가 조정되도록 설정하고, line-height로 1.6만큼 글자 높이의 1.6배 만큼 간격으로 줄간격을 넓힌다.

 

 

.mouse__cursor

: 마우스 커서를 디자인하는 구문이다.

 

position: absolute  - 마우스 커서를 절대 위치로 설정한다.

left: 0, top: 0 - 커서를 페이지 좌측 상단에 위치 시킨다.

width: 200px, height: 200px - 커서의 가로(width)와 세로(height)의 크기를 200px로 설정하다.

border-radius: 50% - 모서리를 둥글게 하여 원모양의 형태로 커서를 만든다.

border: 3px solid #fff - 흰색 테두리를 3px 두께로 설정한다.

background-color: #ffffff7a - 배경색을 흰색, 투명도 0.47의 색상으로 지정한다.

background-image: url(img/mouseEffect03-min.jpg) - 커서의 배경 이미지로 img/mouseEffect03-min.jpg로 설정한다.

background-size: cover - 배경 이미지의 크기를 자동으로 조절하여 커서 영역을 채우도록 한다.

background-position: center center - 배경 이미지를 커서 가운데로 정렬한다.

background-attachment: fixed - 배경 이미지를 고정 시켜 페이지 스크롤시 커서가 움직이지 않도록 한다.

 

 

 

 

 

 

 

 

코드 보기 / JAVASCRIPT

const cursor = document.querySelector(".mouse__cursor");
        const circle  = cursor.getBoundingClientRect();
      
        console.log(cursor.getBoundingClientRect());
        window.addEventListener("mousemove", e => {
            gsap.to(cursor, {
                duration: 0.5, 
                left: e.pageX - circle.width/2, 
                top:e.pageY - circle.height/2});
        });

 

document.querySelector(".mouse__cursor")

:  class가 mousse__cursor인 요소(마우스 커서가 될 요소)를 가져온다.

 

 

 

const cicrcle = cursor.getBoundingClienRect( )

:cursor 요소의 위치와 크기 정보를 얻기 위해 getBoundingClientRect( ) 메서드를 사용한다.

이 메서드는 DOMRect 라는 이름의 객체를 반환하며, 이 객체는 해당 요소의 크기, 위치 등의 정보를 포함한다.

마우스 커서 요소의 위치와 크기 정보를 얻는다.

 

 

※ getBoundingClientRect( ) 메서드

:요소의 위치와 크기 정보를 DOMRect 객체로 반환하며, 객체의 프로퍼티로는 top, left, right, bottom, width, height 등을 사용할 수 있다.

 

 

 

window.addEventListenter("mousemove", e=> {.....});

:마우스의 움직임을 감지하는 이벤트 리스너이다.

마우스를 움직이는 이벤트가 발생하면 콜백 함수를 실행한다.

 

 

 

 

gsap.to(cursor, { duration: 0.5 ····

:gsap 라이브러리를 사용하여 cursor 요소를  자연스럽고 부드럽게 움직일 수 있도록 한다.

duration 속성은 애니메이션의 지속 시간을 나타낸다.

첫 번째 인자로 애니메이션의 대상이 될 요소, 두 번째 인자로 애니메이션 속성과 값들을 받는다.

 

 

left: e.pageX - circle.width/2 , top: e.pageY - circle.height/2

: 마우스의 현재 위치 정보를 반영하여, 마우스 커서의 요소를 해당 위치로 이동 시킨다.

이 때, cursor 요소를 마우스 커서 중심으로 정렬하기 위해 left, top 값을 계산할 때 circle.width/2와 cicle.height/2를 빼준다.

 

 

 

 

 

 

728x90
반응형

 

 

완성 화면

 

 

 

 

 

코드 보기 / CSS

.mouse__wrap {
            cursor: none;
        }
        .mouse__text {
            width: 100%;
            height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            flex-direction: column;
            overflow: hidden;
        }
        .mouse__text p {
            font-size: 2.5vw;
            line-height: 2;
        }
        .mouse__text p:last-child {
            font-size: 2vw;
        }
        .mouse__text p span {
            color: #9b07f0;
        }
        .mouse__cursor {
            position: absolute;
            left: 0;
            top: 0;
            width: 10px;
            height: 10px;
            z-index: 9999;
            border-radius: 50%;
            background-color: rgba(255, 255, 255, 0.37);
            user-select: none;
            pointer-events: none;
            transform: 0.3s;
            transition: all 0.6s;
        }
        .mouse__cursor2 {
            position: absolute;
            left: 0;
            top: 0;
            width: 30px;
            height: 30px;
            z-index: 9999;
            border-radius: 50%;
            background-color: rgba(255, 255, 255, 0.37);
            user-select: none;
            pointer-events: none;
            transform: 0.3s;
            transition: all 0.6s;
        }
        .mouse__cursor.active {
            transform: scale(0);
        }
        .mouse__cursor2.active {
            transform: scale(5) rotateY(720deg);
            background-color: #4303698c;
        }
        .mouse__cursor.active2 {
            transform: scale(2) rotateX(720deg);
            background-color: #0340698d;
        }
        .mouse__cursor2.active2 {
            transform: scale(5) rotateY(720deg);
            background-color: #0340698d;
        }
        .mouse__cursor.active3 {
            transform: scale(3) rotateY(720deg);
            background-color: #6903288c;
        }
        .mouse__cursor2.active3 {
            transform: scale(5) rotateX(720deg);
            background-color: #6903288c;
        }

 

 

.mouse__wrap

: 커서를 감싸는 전체 영역에 대한 스타일을 정의한다.

여기에서는 마우스 커서를 숨기는 역할(cursor: none;)을 한다.

 

.mouse__text

: 커서 이펙트를 적용할 텍스트 영역에 대한 스타일을 정의한다.

텍스트를 가운데 정렬하고, 이 영역은 화면 전체 높이를 차지하도록( height: 100vh) 설정한다.

 

 

.mouse__text p

: 텍스트 영역 내부 단락에 대한 스타일을 정의한다.

여기에서는 텍스트의 글꼴 크기(font-size)와 줄 간격(line-height)을 설정한다.

 

 

.mouse__text p:last-child

: 마지막 단락에 대한 스타일을 정의한다.

여기에서는 글꼴 크기를 더 작게 조절한다.

 

 

.mouse__text p span

: 단락 내부의 특정 텍스트에 대한 스타일을 정의한다.

텍스트 중 일부를 강조하기 위해 글자색을 보라색(#9b07f0)으로 설정한다.

 

.mouse__cursor, .mouse__cursor2

: 두 가지 유형의 마우스 커서의에 대한 디자인을 설정한다.

크기, 배경색, 변형 및 애니메이션 속성을 사용하며, cursor보다 cursor2의 크기가 더 크다.

 

.mouse__cursor.active

: 커서가 활성화 될 때 적용되는 스타일을 정의한다. scale( ) 속성을 사용하여 마우스 커서의 크기를 줄이거나 키우는 효과를 주며, 배경색을 변경 시키는 등의 속성을 가지고 있다.

 

 

 

 

 

 

코드 보기 / JAVASCRIPT

 		//선택자
        const cursor = document.querySelector(".mouse__cursor");
        const cursor2 = document.querySelector(".mouse__cursor2");

        //좌표값
        window.addEventListener("mousemove", e =>{
            gsap.to(cursor,{duration:0.1, left: e.pageX-5, top: e.pageY-5});
            gsap.to(cursor2,{duration:0.3, left: e.pageX-15, top: e.pageY-15});

            document.querySelectorAll(".mouse__text span").forEach(span => {
               span.addEventListener("mouseenter", () => {
                cursor.classList.add(`active`);
                cursor2.classList.add(`active`);
               });
               span.addEventListener("mouseleave", () => {
                cursor.classList.remove(`active}`);
                cursor2.classList.remove(`active`);
               });
        });

            document.querySelectorAll("#header li a").forEach((e, i) => {
                    e.addEventListener("mouseenter", () => {
                        cursor.classList.add(`active${i}`);
                        cursor2.classList.add(`active${i}`);
                    });
                    e.addEventListener("mouseleave", () => {
                        cursor.classList.remove(`active${i}`);
                        cursor2.classList.remove(`active${i}`);
                    });
        });
    });

 

해당 코드는 마우스 커서가 움직일 때, 마우스 커서의 위치를 변경하고, 마우스 커서 위에 있는 요소에 대한 처리를 하는 JavaScript 코드이다.

 

 

const cursor = document.querySelector(".mouse__cursor");

const cursor2 = document.querySelector(".mouse__cursor2");

: const cursor, cursor2는 각각 class가 ".mouse__cursor"와 ".mouse__cursor2"인 요소를 찾아서 변수에 할당한다.

 

 

 

window.addEventListenter("mousemove", e => {

: 윈도우 전체에서 마우스 커서의 움직임을 감지하고, 이동한 위치의 좌표 값을 e.pageX, epageY로 받아온다.

 

 

 

gsap.to(cursor,{duration:0.1, left: e.pageX-5, top: e.pageY-5});
gsap.to(cursor2,{duration:0.3, left: e.pageX-15, top: e.pageY-15});} 

:gsap 라이브러리를 이용하여 cursor와 cursor2의 위치를 변경한다.

 

 

 

document.querySelectorAll(".mouse__text span").forEach(span => {...}

:class가 "mouse__text"인 요소 안에 있는 모든 span 요소를 찾아서 각각에 대하여 mouseente와 mouseleave 이벤트를 등록한다.

마우스가 span 요소 위에 올라갔을 때에는 cursor와 cursor2의 class 이름이 "active"인 클래스를 추가하고, 마우스가 span 요소에서 벗어나면 해당 class를 제거한다.

 

 

 

document.querySelectorAll("#header li a").forEach((e,i => {.....})

: 아이디(ID)가 header 요소 안에 있는 모든 li 요소 안의 모든 a 요소를 찾아서 각가에 대하여 mouseenter와 mouseleave 이벤트를 등록한다.

마우스가 a 요소 위에 올라갔을 때는 cursor와 coursor2 class에 이름이 active + i (0부터 시작하는 인덱스)인 class를 추가하고, 마우스가 a 요소에서 벗어나면 해당 class를 제거한다.

 

 

 

 

 

※ 비슷한 두 이벤트 속성의 차이

mouseover ↔ mouseenter , mouseout ↔ mouseleave

:마우스 이벤트 중에서도 유사한 역할을 하는 이벤트이다.

 

mouseover, mouseout mouseenter, mouseleave
마우스 포인터가 요소 안으로 진입할 때 발생한다.
즉, 마우스 포인터가 요소 내의 어떤 자식 요소 위에 올라가면 이벤트가 발생하는 것이다.
이벤트가 발생하면 이벤트 버블링[각주:1]이 발생하기 때문에 부모 요소까지 mouseover 이벤트가 발생한다.
마우스 포인터가 요소 안으로 진입할 때 발생하지만 이벤ㄴ트 버블링이 발생하지 않는다.

부모 요소에 mouseenter 이벤트가 등록되어 있다고 해도, 자식 요소에 마우스 포인터가 진입할 때에는 부모 요소의 mouseenter 이벤트가 발생하지 않는다.

 

mouseover와 mouseout이벤트는 필요한 경우에만 사용하고,  mouseenter, mouseleave 이벤트는 일반적으로 사용된다.

 

mouseout 이벤트는 요소를 벗어날 때 이벤트 버블링을 사용해 다른 요소에 대한 처리를 수행할 때 주로 사용하고, mouseleave 이벤트는 특정 요소에서 벗어났을 때의 처리를 수행하기 위해 사용한다.

 

 

 

 

 

 

 

 

 

  1. 이벤트가 발생한 요소에서 상위 요소까지 이벤트가 전파되는 것. [본문으로]
728x90
반응형

 

 

 

완성 화면

 

 

 

코드 보기 / CSS

.mouse__wrap {
            cursor: none;
        }
        .mouse__cursor {
            position: absolute;
            left: 0;
            top: 0;
            width: 50px;
            height: 50px;
            border-radius: 50%;
            border: 2px solid #fff;
            background-color: (255, 255, 255, 0.2);
            user-select: none;
            pointer-events: none;
            transition: 
            background-color 0.3s,
            border-color 0.3s,
            transform 0.6s,
            border 0.3s 
            ;
        }
        .mouse__cursor.s1{
            background-color: #fac5647c;
            border-color: #f7a103;
        }
        .mouse__cursor.s2{
            background-color: #4142427d;
            border-color: #000;
            transform: scale(2) rotateY(720deg);
        }
        .mouse__cursor.s3{
            background-color: #f8ec035d;
            border-color: #f3df06;
            transform: scale(1.5) rotateX(720deg);
        }
        .mouse__cursor.s4{
            background-color: #03a6f85d;
            border-color: #03a6f8;
            transform: scale(10);
        }
        .mouse__cursor.s5{
            background-color: #4142427d;
            border-color: #000;
            transform: scale(3) skew(5deg);
            border-radius: 10px;
        }
        .mouse__cursor.s6{
            background-color: #f8ec035d;
            border-color: #f3df06;
            transform: scale(10);
        }
        .mouse__text {
            width: 100%;
            height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            flex-direction: column;
        }
        .mouse__text p {
            font-size: 1vw;
            line-height: 2;
        }
        .mouse__text p:last-child {
            font-size: 2vw;
        }
        .mouse__text p span {
            color: #FAAC01;
            border-bottom: 1.5px solid #FAAC01;
        }
        .mouse__info {
            position: absolute;
            left: 0;
            bottom: 0;
            padding: 20px;
            font-size: 16px;
            line-height: 1.6;
        }

 

 

코드 보기 / JAVASCRIPT

 window.addEventListener("mousemove", function(event){
            document.querySelector(".clientX").innerHTML = event.clientX;
            document.querySelector(".clientY").innerHTML = event.clientY;
            document.querySelector(".offsetX").innerHTML = event.offsetX;
            document.querySelector(".offsetY").innerHTML = event.offsetY;
            document.querySelector(".pageX").innerHTML = event.pageX;
            document.querySelector(".pageY").innerHTML = event.pageY;
            document.querySelector(".screenX").innerHTML = event.screenX;
            document.querySelector(".screenY").innerHTML = event.screenY;
        });

        //선택자
        const cursor = document.querySelector(".mouse__cursor");

        window.addEventListener("mousemove", function(e){
            cursor.style.left = e.clientX - 25 + "px"
            cursor.style.top = e.clientY - 25 + "px"
        });
    
   
        document.querySelectorAll(".mouse__text span").forEach(function(span){
            let attr= span.getAttribute("class");               // attr = s1 ~ s6
            span.addEventListener("mouseover", function(){
                cursor.classList.add(attr);
            });
            span.addEventListener("mouseout", function(){
                cursor.classList.remove(attr);
            });
        });

 

 

 

 

 

 

1. 마우스의 좌표를 나타내는 구문

      window.addEventListener("mousemove", function(event){
            document.querySelector(".clientX").innerHTML = event.clientX;
            document.querySelector(".clientY").innerHTML = event.clientY;
            document.querySelector(".offsetX").innerHTML = event.offsetX;
            document.querySelector(".offsetY").innerHTML = event.offsetY;
            document.querySelector(".pageX").innerHTML = event.pageX;
            document.querySelector(".pageY").innerHTML = event.pageY;
            document.querySelector(".screenX").innerHTML = event.screenX;
            document.querySelector(".screenY").innerHTML = event.screenY;
        });

: 마우스의 이동을 감지하고, 이벤트 객체의 다양한 속성을 사용하여 마우스 위치에 대한 정보를 출력하는 구문.

addEventListener 함수를 사용하여 mousemove 이벤트를 감지하고, 이벤트 핸들러 함수[각주:1]를 등록한다.

이 객체를 통해 마우스 위치와 관련된 다양한 정보를 가져올 수 있다.

 

clientX, clientY : 브라우저 화면에서 마우스 커서의 위치를 나타낸다.

offsetX, offsetY : 이벤트 대상 요소에서 마우스 커서의 위치를 상대적으로 나타낸다.

pageX, pageY : 문서 전체에서 마우스 커서의 위치를 상대적으로 나타낸다.

screenX, screenY :  모니터 화면을 기준으로 한 마우스 커서의 위치를 나타낸다.

 

각각의 위치 정보는 querySelector 함수를 사용하여 HTML 요소를 선택하고, innerHTML 속성을 사용하여 해당 요소의 내용을 변경하고 화면에 출력한다.

 

 

 

 

 

2. 마우스 커서에 스타일을 주는 구문

.mouse__cursor {
            position: absolute;
            left: 0;
            top: 0;
            width: 50px;
            height: 50px;
            border-radius: 50%;
            border: 2px solid #fff;
            background-color: (255, 255, 255, 0.2);
            user-select: none;
            pointer-events: none;
            transition: 
            background-color 0.3s,
            border-color 0.3s,
            transform 0.6s,
            border 0.3s 
            ;
        }
        .mouse__cursor.s1{
            background-color: #fac5647c;
            border-color: #f7a103;
        }
        .mouse__cursor.s2{
            background-color: #4142427d;
            border-color: #000;
            transform: scale(2) rotateY(720deg);
        }
        .mouse__cursor.s3{
            background-color: #f8ec035d;
            border-color: #f3df06;
            transform: scale(1.5) rotateX(720deg);
        }
        .mouse__cursor.s4{
            background-color: #03a6f85d;
            border-color: #03a6f8;
            transform: scale(10);
        }
        .mouse__cursor.s5{
            background-color: #4142427d;
            border-color: #000;
            transform: scale(3) skew(5deg);
            border-radius: 10px;
        }
        .mouse__cursor.s6{
            background-color: #f8ec035d;
            border-color: #f3df06;
            transform: scale(10);
        }

 

: 마우스 커서 디자인을 구현하는 CSS 구문이다.

마우스 커서를 표현하는 요소의 클래스 이름이 .mouse__cursor로 지정 되어 있으며, position : absloute 속성을 사용하여 문서의 절대 위치에 배치되도록 지정한다.

 

width와 height 속성을 사용하여 마우스 커서의 크기를 지정하고, border-radius 속성을 사용하여 둥근 모서리를 생성한다.

border 속서을 사용하여 테두리의 굵기와 색상을 지정한다.

 

background-color 속성을 사용하여 배경색을 지정하고, user-select와 pointer-events 속성을 사용하여 마우스 커서 요소에 대한 선택과 이벤트를 무시하도록 지정한다.

 

transition 속성을 사용하여 마우스 커서 요소의 변화가 부드럽게 이루어지도록 지정한다.

 

클래스 이름 mouse__cursor에 더해진 s1부터 s6까지는 마우스 커서 요소에 추가적인 스타일을 적용하는 클래스 이름이다. 마우스 상호작용에 따라 마우스 커서를 다른 디자인으로 변경한다.

 

 

s1 

: 배경색과 테두리 색만 변경

 

s2

: 배경색과 테두리 색을 변경하면서 scale(2) 속성을 사용하여 원래 크기의 2배로 확대시켰고, rotateY(720deg) 속성을 사용하여 Y축으로 720도 회전 시킨다.

 

s3

:배경색과 테두리 색을 변경하고 scale(1.5)속성을 사용하여 원래 크기의 1.5배로 확대 시키며, rotateX축으로 720도 회전시킨다.

 

s4

: 배경색과 테두리 색을 변경하고 scale(10) 속성을 사용하여 원래 크기의 10배로 확대 시킨다.

 

s5

:배경색과 테두리 색을 변경하고 scale(3) 속성을 사용하여 원래 크기의 3배로 확대 시키며 skew(5deg) 속성을 사용하여 5도 만큼 기울이고, border-radius: 1opx 속성을 사용하여 둥근 테두리를 생성한다.

 

s6

:배경색과 테두리 색을 변경하고 scale(10) 속성을 사용하여 원래 크기의 10배로 확대 시킨다.

 

 

 

 

 

 

document.write.querySelector( )에 이벤트 효과를 주는 구문을 작성하는 4가지 방법.

1. 일일히 전부 작성하기

document.querySelector(".s2").addEventListener("mouseover", function(){
            cursor.classList.add("s2");
        });

        document.querySelector(".s2").addEventListener("mouseout", function(){
            cursor.classList.remove("s2");
        });

        document.querySelector(".s3").addEventListener("mouseover", function(){
            cursor.classList.add("s3");
        });

        document.querySelector(".s3").addEventListener("mouseout", function(){
            cursor.classList.remove("s3");
        });

        document.querySelector(".s4").addEventListener("mouseover", function(){
            cursor.classList.add("s4");
        });

        document.querySelector(".s4").addEventListener("mouseout", function(){
            cursor.classList.remove("s4");
        });

        document.querySelector(".s5").addEventListener("mouseover", function(){
            cursor.classList.add("s5");
        });

        document.querySelector(".s5").addEventListener("mouseout", function(){
            cursor.classList.remove("s5");
        });

        document.querySelector(".s6").addEventListener("mouseover", function(){
            cursor.classList.add("s6");
        });

        document.querySelector(".s6").addEventListener("mouseout", function(){
            cursor.classList.remove("s6");
        });

: 마우스 포인터에 반응하여 특정 요소들의 클래스를 추가하거나 제거하고, 커서의 모양을 변경하는 것을 목적으로 하는 구문이다.

 

각각의 요소에 대하여 mouseover 이벤트가 발생하면 해당 요소의 클래스를 cursor 요소에 추가하고, mouseout 이벤트가 발생하면 해당 요소의 클래스를 cursor 요소에서 제거한다.

 

 

 

 

 

2. for문 사용

for(let i=1; i<=6; i++) {
            document.querySelector(`.s${i}`).addEventListener("mouseover", function(){
                cursor.classList.add(`s${i}`);
            });
            document.querySelector(`.s${i}`).addEventListener("mouseout", function(){
                cursor.classList.remove(`s${i}`);
            });
        }

: for문을 사용하여 1부터 6까지 반복하면서, 템플릿 리터럴을 사용하여 선택적 문자열을 생성하고, 해당 요소에 대해 mouseover 및 mouseout 이벤트를 추가한다.

이벤트 핸들러 함수에서는 i 값을 포함하는 클래스 이름을 cursor 요소에 추가하거나 제거한다.

 

 

 

 

3. forEach문 사용

document.querySelectorAll(".mouse__text span").forEach(function(span, i){
            span.addEventListener("mouseover", function(){
                cursor.classList.add("s"+(i+1));
            });
            span.addEventListener("mouseout", function(){
                cursor.classList.remove("s"+(i+1));
            });
        });

:  forEach 문을 사용하여 여러 개의 span 요소를 포함하는 요소에서 각 span 요소에 대해 커서의 모양을 다르게 지정할 수 있다.

mouse__text 클래스를 가진 요소들의 하위 요소인 span 태그에 대해 mouseover 및 mouseout 이벤트를 추가하여, 각각의 span 요소에 마우스를 올리면 커서의 모양이 변경되는 것을 구현한다.

 

querySelectorAll 메서드를 사용하여 mouse__text span 선택자로 하위 요소들을 모두 선택하고, forEach 메서드를 사용하여 각 요소에 대해 mouseover 및 mouseout 이벤트를 추가한다.

이벤트 핸들러 함수는 i 값(인덱스 값)을 이용하여 클래스 이름을 동적으로 생성하고, cursor 요소에 추가하거나 제거한다.

 

 

 

 

 

4. getAttribute( ); 사용

document.querySelectorAll(".mouse__text span").forEach(function(span){
            let attr= span.getAttribute("class");               // attr = s1 ~ s6
            span.addEventListener("mouseover", function(){
                cursor.classList.add(attr);
            });
            span.addEventListener("mouseout", function(){
                cursor.classList.remove(attr);
            });
        });

 

: forEach문과 유사하지만 각 span 요소에 대해 클래스 이름을 가져오는 부분이 다르다.

 

querySeectorAll 메서드를 사용하여 mouse__text span 선택자로 하위 요소들을 모두 선택하고, forEach 메서드를 사용하여 각 요소에 대해 mouseover 및 mouseout 이벤트를 추가한다.

이벤트 핸들러 함수에서는 span 요소의 클래스 이름을 가져와서 attr 변수에 할당하고, 해당 클래스 이름을 cursor 요소에 추가하거나 제거한다.

 

각 span 요소가 s1부터 s6까지의 클래스 이름을 동적으로 생성하는 대신 각 요소의 클래스 이름을 가져와 사용할 수 있다.

 

 

  1. 브라우저에서 사용자의 조작이나 환경의 변화로 일어난 이벤트를 처리하기 위해 이벤트 객체를 매개변수로 전달받는 함수 [본문으로]
728x90
반응형

 

완성 화면

 

 

 

 

 

코드 보기 / HTML

 <div class="quiz__wrap">
            <div class="quiz">
                <div class="quiz__header">
                    <h2 class="quiz__title"></h2>
                </div>
                <div class="quiz__main">
                    <div class="quiz__question"></div>
                    <div class="quiz__view">
                        <div class="dog__wrap">
                            <div class="true">정답</div>
                            <div class="false">오답</div>
                            <div class="card-container">
                                <div class="dog">
                                    <div class="head">
                                        <div class="ears"></div>
                                        <div class="face"></div>
                                        <div class="eyes">
                                            <div class="teardrop"></div>
                                        </div>
                                        <div class="nose"></div>
                                        <div class="mouth">
                                            <div class="tongue"></div>
                                        </div>
                                        <div class="chin"></div>
                                    </div>
                                    <div class="body">
                                        <div class="tail"></div>
                                        <div class="legs"></div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="quiz__choice">
                        <label for="choice1">
                            <input type="radio" id="choice1" name="choice" value="1">
                            <span></span>
                        </label>
                        <label for="choice2">
                            <input type="radio" id="choice2" name="choice" value="2">
                            <span></span>
                        </label> 
                        <label for="choice3">
                            <input type="radio" id="choice3" name="choice" value="3">
                            <span></span>
                        </label> 
                        <label for="choice4">
                            <input type="radio" id="choice4" name="choice" value="4">
                            <span></span>
                        </label> 
                    </div>
                    <div class="quiz__answer">
                        <button class="confirm">정답 확인하기</button>
                    </div>
                    <div class="quiz__desc"></div>
                </div>
            </div>
        </div>

 

 

 

 

 

코드 보기 / JAVASCRIPT

 const quizWrap = document.querySelector(".quiz__wrap");
        const quizTitle = quizWrap.querySelector(".quiz__title");
        const quizQuestion = quizWrap.querySelector(".quiz__question");
        const quizChoice = quizWrap.querySelectorAll(".quiz__choice span");
        const quizSelect = quizWrap.querySelectorAll(".quiz__choice input");
        const quizConfirm = quizWrap.querySelector(".quiz__answer .confirm");
        const quizDesc = quizWrap.querySelector(".quiz__desc");
        const quizAnswer = quizWrap.querySelector(".quiz__answer");
        const dogWrap = quizWrap.querySelector(".dog__wrap");

        //문제정보    
        const quizInfo = [
            {
                infoType: "웹디자인 기능사",
                infoTime: "2016년 4회",
                infoNumber : "1",
                infoQuestion: "다음 중 디자인의 기본 요소들로 옳은 것은?",
                infoChoice:["선, 색채, 공간, 수량","점, 선, 면, 질감","시간, 수량, 구조, 공간","면, 구조, 공간, 수량"],
                infoAnswer:"2",
                infoDesc: "디자인의 기본 요소에는 점, 선, 면, 질감이 있습니다."
            }
        ];

        //문제 출력
        function updateQuiz(){
            quizTitle.innerHTML = quizInfo[0].infoType + " " + quizInfo[0].infoTime;
            quizQuestion.innerHTML = "<em>"+quizInfo[0].infoNumber+"</em>. "+ quizInfo[0].infoQuestion;
            quizDesc.innerHTML = quizInfo[0].infoDesc;
            
            for(let i=0; i<4; i++){
                quizChoice[i].textContent = quizInfo[0].infoChoice[i];
            }
        }

        //정답확인
        function answerQuiz(){
            for(let i=0; i<quizChoice.length; i++) {
                if(quizSelect[i].checked == true) {          //사용자가 보기를 선택 유무
                    if(quizSelect[i].value == quizInfo[0].infoAnswer) {         
                        alert ("정답") //정답일 시
                        dogWrap.classList.add("like"); 
                        quizAnswer.style.display = "none";
                        quizDesc.style.display = "none;"                            
                    } else {                               
                        alert ("오답")    //오답일 시
                        dogWrap.classList.add("dislike"); 
                        quizAnswer.style.display = "none";
                        quizDesc.style.display = "none";                  
                    }
                }
            }
        }


        //사용자가 선택한 인풋박스가 문제 정답(quizInfo[0].infoAnswer과 일치하는지 확인

        //정답 체크박스 클릭
        quizConfirm.addEventListener("click", answerQuiz);
        updateQuiz();       //문제출력

 

 

 

 

 

코드 보기 / CSS

.quiz__choice{
    padding: 20px;
    border-bottom: 6px ridge #a12cd3;
    font-family: 'ElandChoice';
}
.quiz__choice label {
    display: flex;
}
.quiz__choice label input {
    position: absolute;
    clip: rect(0 0 0 0);
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
}
.quiz__choice label span {
    font-size: 20px;
    line-height: 1.5;
    padding: 6px;
    display: flex;
    cursor: pointer;
    margin: 2px 0;
}
.quiz__choice label span::before {
    content: '';
    width: 26px;
    height: 26px;
    border-radius: 50%;
    margin-right: 15px;
    background-color: #fff;
    box-shadow: inset 0 0 0 4px #63068a;
    transition: all 0.2s;
    flex-shrink: 0;
}
.quiz__choice label input:checked + span {
    background-color: #f6ecfa;
}
.quiz__choice label input:checked + span::before {
    box-shadow: inset 0 0 0 8px #63068a;
}
.quiz__check {
    position: fixed;
    right: 20px;
    bottom: 20px;
    width: 110px;
    height: 110px;
    line-height: 110px;
    border-radius: 50%;
    z-index: 1000;
    background-color: #9d07f4;
    text-align: center;
    font-family: 'ElandChoice';
    color: #fff;
    cursor: pointer;
}

 

 

 

 

문제 출력 구문

function updateQuiz(){
            quizTitle.innerHTML = quizInfo[0].infoType + " " + quizInfo[0].infoTime;
            quizQuestion.innerHTML = "<em>"+quizInfo[0].infoNumber+"</em>. "+ quizInfo[0].infoQuestion;
            quizDesc.innerHTML = quizInfo[0].infoDesc;
            
            for(let i=0; i<4; i++){
                quizChoice[i].textContent = quizInfo[0].infoChoice[i];
            }
        }

 

quizTitle, quizQuestion, quizDesc

: HTML 문서에서 ID를 사용하여 가져온 요소.

각각 문제의 종목, 문제, 해설을 담게 될 요소들이다.

 

quizInfo

:배열로 정의된 객체를 담고 있는 변수

 

quizTitle.innerHTML = quizInfo[0].infoType + " " + quizInfo[0].infoTime;

: quizTitle 요소의 내부 HTML을 업데이트한다.
이 때, quizInfo 배열의 첫 번째 객체인 quizInfo[0]에서 infoType 속성과 infoTime 속성을 가져와 공백으로 구분한 문자열을 quizTitle 요소 내부 HTML로 설정합니다.

quizTitle 요소의 내용을 quizInfo 배열의 첫 번째 객체의 infoType(웹 디자인 기능사) 속성과 infoTime(2016년 4회) 속성을 이용해 설정한다.

 

 

quizQuestion.innerHTML = "<em>"+quizInfo[0].infoNumber+"</em>. "+ quizInfo[0].infoQuestion;

: quizQuestion 요소의 내부 HTML을 업데이트합니다.
이 때, quizInfo 배열의 첫 번째 객체인 quizInfo[0]에서 infoNumber 속성과 infoQuestion 속성을 가져와 <em> 태그로 감싸고 번호와 함께 문자열을 생성한 뒤, quizQuestion 요소 내부 HTML로 설정한다.

quizQuestion 요소의 내용을 quizInfo 배열의 첫 번째 객체의 infoNumber(1) 속성과 infoQuestion (다음 중 디자인의 기본 요소들로 ~)속성을 이용해 설정한다.

 

quizDesc.innerHTML = quizInfo[0].infoDesc;

: quizDesc 요소의 내부 HTML을 업데이트한다.
이 때, quizInfo 배열의 첫 번째 객체인 quizInfo[0]에서 infoDesc 속성을 가져와 quizDesc 요소 내부 HTML로 설정한다.

quizDesc 요소의 내용을 quizInfo  배열의 첫번째 객체의 infoDesc(디자인의 기본 요소에는 ~~) 속성을 이용해 설정한다.

 

 

 

for(let i=0; i<4; i++){ quizChoice[i].textContent = quizInfo[0].infoChoice[i]; }

: quizChoice 배열에 있는 네 개의 요소(quizChoice[0], quizChoice[1], quizChoice[2], quizChoice[3])의 내부 텍스트를 각각 quizInfo 배열의 첫 번째 객체인 quizInfo[0]에서 가져온 infoChoice 속성의 값으로 설정하고, for 루프를 사용하여 각 요소의 인덱스를 참조하고, textContent 속성을 사용하여 내부 텍스트를 업데이트한다.

for문을 사용하여 quizChoice 배열의 요소에 quizInfo 배열의 첫 번째 객체의 infoChoice 속성을 순서대로 설정한다.

 

 

 

 

 

정답 확인 구문

function answerQuiz(){
            for(let i=0; i<quizChoice.length; i++) {
                if(quizSelect[i].checked == true) {    
                    if(quizSelect[i].value == quizInfo[0].infoAnswer) {         
                        alert ("정답")
                        dogWrap.classList.add("like"); 
                        quizAnswer.style.display = "none";
                        quizDesc.style.display = "none;"                            
                    } else {                               
                        alert ("오답")    
                        dogWrap.classList.add("dislike"); 
                        quizAnswer.style.display = "none";
                        quizDesc.style.display = "none";                  
                    }
                }
            }
        }

 

 

for(let i=0; i<quizChoice.length; i++)

: for 루프를 이용하여 quizChoice 배열의 요소를 순서대로 확인한다.

 

 

 if(quizSelect[i].checked == true)

: if문은 해당 보기가 선택되었는지 확인한다.

quizSelect[i]는 해당 보기의 라디오 버튼 요소를 나타낸다.

checked 속성은 해당 라디오 버튼이 선택되었는지 여부를 확인하며, 선택된 보기가 정답인지 확인한다.

quizSelect[i].value는 해당 보기의 값(내용)을 나타낸다.

 

 

if(quizSelect[i].value == quizInfo[0].infoAnswer)  {         
                        alert ("정답")
                        dogWrap.classList.add("like"); 
                        quizAnswer.style.display = "none";
                        quizDesc.style.display = "none;"         

: quizInfo[0].infoAnswer는 퀴즈의 첫 번째 객체의 infoAnswer 속성으로, 정답의 값(내용)을 나타낸다.

정답일 경우, alert()를 이용하여 "정답" 메시지를 출력한다.

dogWrap은 HTML 문서에서 id를 이용해 가져온 요소로, classList.add() 메소드를 이용하여 like 클래스를 추가한다.

quizAnswer와 quizDesc 요소의 display 속성을 "none"으로 설정하여 숨긴다.

 

 

else {                               
                        alert ("오답")    
                        dogWrap.classList.add("dislike"); 
                        quizAnswer.style.display = "none";
                        quizDesc.style.display = "none";                  
                    }


:  오답일 경우, alert()를 이용하여 "오답" 메시지를 출력한다.

dogWrap은 HTML 문서에서 id를 이용해 가져온 요소로, classList.add() 메소드를 이용하여 dislike 클래스를 추가한다.

quizAnswer와 quizDesc 요소의 display 속성을 "none"으로 설정하여 숨긴다.

 

 

 

 

 

 

728x90
반응형

완성 화면

 

 

 

 

 

 

코드 보기 / 자바스크립트

//선택자
    const quizWrap = document.querySelector(".quiz__wrap");
    const quizTitle = quizWrap.querySelectorAll(".quiz__title");                    //시험 종목, 시간
    const quizQuestion = quizWrap.querySelectorAll(".quiz__question span");         //문제
    const quizQuestionNum = quizWrap.querySelectorAll(".quiz__question em");        //문제 번호
    const quizAnswerResult = quizWrap.querySelectorAll(".quiz__answer .result");    //정답
    const quizDesc = quizWrap.querySelectorAll(".quiz__desc");                      //해설 
    const quizAnswerConfirm = quizWrap.querySelectorAll(".quiz__answer .confirm");     //정답 버튼
    const quizAnswerInput = quizWrap.querySelectorAll(".quiz__answer .input");      // 사용자 정답
    const dogWrap = quizWrap.querySelectorAll(".dog__wrap");
  
 //문제 정보
const quizInfo = [
    {
        infoType: "정보처리 기능사",
        infoTime: "2011년 4회",
        infoNumber : "1",
        infoQuestion: "주파수 분할 다중화 방식에서 각 채널 간 간섭을 막기 위해서 일종의 완충지역 역할을 하는 것을 무엇이라고 하는가?",
        infoAnswer:"가드밴드",
        infoDesc: "가드밴드는 주파수 분할 다중화 방식에서 각 채널간 간섭을 막기 위해서 일종의 완충지역 역할을 한다."
    }, {
        infoType: "정보처리 기능사",
        infoTime: "2011년 4회",
        infoNumber : "2",
        infoQuestion: "사용자의 명령으로 시스템이 수행되고 그에 따른 결과를 나타내주는 대화식 운영체제는 무엇인가?",
        infoAnswer:"UNIX",
        infoDesc: "UNIX는 사용자의 명령으로 시스템이 수행되고 그에 따른 결과를 나타내 주는 대화식 운영체제이다."
    }, {
        infoType: "정보처리 기능사",
        infoTime: "2011년 4회",
        infoNumber : "3",
        infoQuestion: "프로세스들이 서로 다른 프로세스가 차지하고 있는 자원을 무한정 기다려 시스템이 멈춘 것처럼 보이는 상태는?",
        infoAnswer:"교착상태",
        infoDesc: "교착상태(Dead lock)는 2개 이상의 프로세스들이 서로 다른 프로세스가 차지하고 잇는 자원을 무한정 기다려 시스템이 멈춘 것처럼 보이는 상태이다."
    }
   ];

    //문제출력
        // //과목
        // quizTitle[0].textContent = quizInfo[0].infoType;
        // quizTitle[1].textContent = quizInfo[1].infoType;
        // quizTitle[2].textContent = quizInfo[2].infoType;
        // //회차
        // quizTime[0].textContent = quizInfo[0].infoTime;
        // quizTime[1].textContent = quizInfo[1].infoTime;
        // quizTime[2].textContent = quizInfo[2].infoTime;
        // //문제 번호
        // quizQuestionNum[0].textContent = quizInfo[0].infoNumber;
        // quizQuestionNum[1].textContent = quizInfo[1].infoNumber;
        // quizQuestionNum[2].textContent = quizInfo[2].infoNumber;
        // //질문
        // quizQuestion[0].textContent = quizInfo[0].infoQuestion;
        // quizQuestion[1].textContent = quizInfo[1].infoQuestion;
        // quizQuestion[2].textContent = quizInfo[2].infoQuestion;
        // //정답
        // quizAnswerResult[0].textContent = quizInfo[0].infoAnswer;
        // quizAnswerResult[1].textContent = quizInfo[1].infoAnswer;
        // quizAnswerResult[2].textContent = quizInfo[2].infoAnswer;
        // //해설
        // quizDesc[0].textContent = quizInfo[0].infoDesc;
        // quizDesc[1].textContent = quizInfo[1].infoDesc;
        // quizDesc[2].textContent = quizInfo[2].infoDesc;

        //#2
        //문제 종목 + 회차
        // quizTitle[0].innerHTML = quizInfo[0].infoType+" "+ quizInfo[0].infoTime;
        // quizTitle[1].innerHTML = quizInfo[1].infoType+" "+ quizInfo[1].infoTime;
        // quizTitle[2].innerHTML = quizInfo[2].infoType+" "+ quizInfo[2].infoTime;

        
        //번호
        // quizQuestionNum[0].textContent = quizInfo[0].infoNumber;
        // quizQuestionNum[1].textContent = quizInfo[1].infoNumber;
        // quizQuestionNum[2].textContent = quizInfo[2].infoNumber;
        
        //질문
        // quizQuestion[0].textContent = quizInfo[0].infoQuestion;
        // quizQuestion[1].textContent = quizInfo[1].infoQuestion;
        // quizQuestion[2].textContent = quizInfo[2].infoQuestion;
        
        // for(let i=0; i<quizInfo.length; i++) {
        //     quizTitle[i].innerHTML = quizInfo[i].infoType+" "+ quizInfo[i].infoTime;
        //     quizQuestionNum[i].textContent = quizInfo[i].infoNumber;
        //     quizQuestion[i].textContent = quizInfo[i].infoQuestion;
        //     quizAnswerResult[i].textContent = quizInfo[i].infoAnswer;
        //     quizDesc[i].textContent = quizInfo[i].infoDesc;
        // }

        quizInfo.forEach (function(e, i){
            quizTitle[i].innerHTML = quizInfo[i].infoType+" "+ quizInfo[i].infoTime;
            quizQuestionNum[i].textContent = quizInfo[i].infoNumber;
            quizQuestion[i].textContent = quizInfo[i].infoQuestion;
            quizAnswerResult[i].textContent = quizInfo[i].infoAnswer;
            quizDesc[i].textContent = quizInfo[i].infoDesc;
        });

 

 

 

 

그동안 데이터를 변수에 저장만 하고 하나씩 불러오는 방법을 사용했으나, 3번 이펙트에서 달라진 점은 같은 선택자가 반복되는 데이터를 불러오는 데에 for문과 forEach문을 사용했다는 것이다.

 

과목, 문제 번호, 질문, 정답, 해설 모두 배열의 키를 제외하면 전부 같은 문장이 반복되기 때문에 for문과 forEach문을 사용하여 데이터를 불러올 수 있다.

 

 

 

 

for문

for(let i=0; i<quizInfo.length; i++) {
            quizTitle[i].innerHTML = quizInfo[i].infoType+" "+ quizInfo[i].infoTime;
            quizQuestionNum[i].textContent = quizInfo[i].infoNumber;
            quizQuestion[i].textContent = quizInfo[i].infoQuestion;
            quizAnswerResult[i].textContent = quizInfo[i].infoAnswer;
            quizDesc[i].textContent = quizInfo[i].infoDesc;
        }

 

quizInfo

: 객체들(문제 종목과 회차, 문제 번호, 문제, 정답, 해설)이 담긴 배열이다.

 

 

quizTitle,

quizQuestionNum,

quizQuestion,

quizAnswerResult,

quizDesc

: HTML 문서에서 해당 객체 정보를 선택하여 출력하는 요소들이다.

 

 

for문을 이용해 문제의 정보가 담긴 quizInfo 배열의 길이만큼 반복하며, 각 객체의 정보를 출력하는 요소에 객체의 속성 값을 할당한다.

 

quizTitle

: 문제의 종목과 회차를 출력하는 요소이며, quizInfo 객체의 infoType 속성과 infoTime 속성 값을 가져와 할당한다.

(즉 정보처리 기능사, 2011년 4회라는 정보를 가져온다)

 

quizQuestionNum

:문제의 번호를 출력하는 요소이며, quizInfo 객체의 infoNumber 속성 값을 가져와 할당한다.

(즉 문제 1번, 문제 2번, 문제 3번 ····· 의 정보를 가져온다)

 

quizQuestion

: 문제를 출력하는 요소이며, quizInfo 객체의 infoQuestion 속성 값을 가져와 할당한다.

(즉 각 객체에서 infoQuestion이라는 키 안의 속성 값을 가져와 할당하는 것)

 

quizAnswerResult

:정답을 출력하는 요소이며, quizInfo 각 객체의 infoAnswer 속성 값을 가져와 할당한다.

 

 

quizDesc

:해설을 출력하는 요소이며, quizInfo 각 객체의 infoDesc 속성 값을 가져와 할당합니다.

 

 

 

 

 

forEach

quizInfo.forEach (function(e, i){
            quizTitle[i].innerHTML = quizInfo[i].infoType+" "+ quizInfo[i].infoTime;
            quizQuestionNum[i].textContent = quizInfo[i].infoNumber;
            quizQuestion[i].textContent = quizInfo[i].infoQuestion;
            quizAnswerResult[i].textContent = quizInfo[i].infoAnswer;
            quizDesc[i].textContent = quizInfo[i].infoDesc;
        });

 

 

forEach 함수를 이용해 quizInfo 배열의 요소들을 반복하며, 각 객체의 정보를 출력하는 요소에 객체의 속성 값을 할당한다.

 

quizTitle,

quizQuestionNum,

quizQuestion,

quizAnswerResult,

quizDesc

: HTML 문서에서 해당 객체 정보를 출력하는 요소들.

 

quizTitle

:문제 종목과 회차를 출력하는 요소이며, quizInfo 객체의 infoType 속성과 infoTime 속성 값을 가져와 할당한다.

 

quizQuestionNum

:문제의 번호를 출력하는 요소이며, quizInfo 객체의 infoNumber 속성 값을 가져와 할당한다.

 

quizQuestion

:문제를 출력하는 요소이며, quizInfo 객체의 infoQuestion 속성 값을 가져와 할당한다.

 

quizAnswerResult

:정답을 출력하는 요소이며, quizInfo 객체의 infoAnswer 속성 값을 가져와 할당한다.

 

quizDesc

:해설을 출력하는 요소이며, quizInfo 객체의 infoDesc 속성 값을 가져와 할당한다.

 

 

forEach문의 function(e,i)은 배열 요소와 해당 인덱스(키, 자리값)을 매개 변수로 받는 콜백 함수이다.

첫번째 매개변수는 element (배열요소, 속성)으로 받아들이기 때문에 인덱스(키, 자리값)을 사용하고 싶다면 반드시 앞에 el 이나 e 등 다른 변수를 적어준 후, 그 뒤에 키로 사용할 다른 변수 이름을 작성해 주어야 자리값을 사용할 수 있다.

 

 

 

 

 

728x90
반응형

 

 

완성 화면

클릭 시 완성화면으로 넘어갑니다.

 

 

 

 

코드 보기 / 자바스크립트

<!DOCTYPE html>
<html lang="ko">

<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>퀴즈 이펙트 02</title>

    <link rel="stylesheet" href="css/reset.css">
    <link rel="stylesheet" href="css/quiz.css">
</head>

<body>
    <header id="header">
            <h1><a href="../javascript14.html">Quiz <em> 주관식 확인하기</em></a></h1>
            <ul>
                <li><a href="quizEffect01.html">1</a></li>
                <li class="active"><a href="quizEffect02.html">2</a></li>
            </ul>
    </header>
    <!-- //header -->

    <main id="main">
        <div class="quiz__wrap">
            <div class="quiz">
                <div class="quiz__header">
                    <h2 class="quiz__title"><span></span><em></em></h2>
                </div>
                <div class="quiz__main">
                    <div class="quiz__question">
                        <em></em>. <span></span>
                    </div>
                    <div class="quiz__view">
                        <div class="dog__wrap">
                            <div class="card-container">
                                <div class="dog">
                                    <div class="head">
                                        <div class="ears"></div>
                                        <div class="face"></div>
                                        <div class="eyes">
                                            <div class="teardrop"></div>
                                        </div>
                                        <div class="nose"></div>
                                        <div class="mouth">
                                            <div class="tongue"></div>
                                        </div>
                                        <div class="chin"></div>
                                    </div>
                                    <div class="body">
                                        <div class="tail"></div>
                                        <div class="legs"></div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="quiz__answer">
                        <input class="input" type="text" placeholder="정답을 작성해주세요.">
                        <button class="confirm">정답 확인하기 </button>
                        <div class="result"></div>
                    </div>
                </div>
                <div class="quiz__footer">
                    <div class="quiz__desc">해설</div>
                </div>
            </div>
        </div>
</main>
<!-- //main -->

<footer id="footer">
    <a href="mailto:goed0522@gmail.com">goed0522@gmail.com</a>   
</footer>
<!-- //footer -->

<script>
    //선택자
    const quizWrap = document.querySelector(".quiz__wrap");
    const quizTitle = quizWrap.querySelector(".quiz__title span");
    const quizTime = quizWrap.querySelector(".quiz__title em");
    const quizQuestionNum = quizWrap.querySelector(".quiz__question em");
    const quizQuestion = quizWrap.querySelector(".quiz__question span");
    const quizAnswerConfirm = quizWrap.querySelector(".quiz__answer .confirm");
    const quizAnswerResult = quizWrap.querySelector(".quiz__answer .result");
    const dogWrap = quizWrap.querySelector(".dog__wrap");
    const quizDesc = quizWrap.querySelector(".quiz__desc");
    const quizFooter = quizWrap.querySelector(".quiz__footer");
    const quizAnswerInput = quizWrap.querySelector(".quiz__answer .input");
    

    //문제 정보
    const infoType = "정보처리 기능사";
    const infoTime = "2011년 5회";
    const infoNumber = "2";
    const infoQuestion = "프레젠테이션 화면 전체를 전환하는 단위를 의미하는 것은?";
    const infoAnswer = "슬라이드";
    const infoDesc = "슬라이드는 프레젠테이션 화면 전체를 말하고 개체는 화면을 구성하는 개개의 요소를 말한다.";

    //문제 출력
    quizTitle.textContent = infoType;
    quizTime.textContent = infoTime;
    quizQuestionNum.textContent = infoNumber;
    quizQuestion.textContent = infoQuestion;
    quizDesc.textContent = infoDesc;
    quizAnswerResult.textContent = infoAnswer;

    //정답 & 해설 숨기기
    quizAnswerResult.style.display = "none";
    quizFooter.style.display = "none";

    //사용자 정답
    quizAnswerConfirm.addEventListener("click", function () {
        quizAnswerResult.style.display = "block";           //정답 나타냄
        quizAnswerConfirm.style.display = "none";           //정답 확인버튼 숨김

        const userAnswer = quizAnswerInput.value.trim();
        if(infoAnswer == userAnswer) {
            alert("정답입니다") 
            dogWrap.classList.add("like");
            quizFooter.style.display = "block";             //팁 박스 숨김
            quizAnswerInput.style.display = "none";         //입력 박스 숨김
        } else {
            alert("오답입니다")
            dogWrap.classList.add("dislike");
            
        }
    });
    </script>
</body>
</html>

 

 

 

 

코드 보기 / CSS

#header {
    position: fixed;
    left: 0;
    top: 0;
    width: 100%;
    background-color: #390b47;
    color: #fff;
    padding: 10px;
    z-index: 1000;
    display: flex;
    justify-content: space-between;
}
#header::before {
    content: '';
    border: 4px ridge #580073;
    position: absolute;
    left: 5px;
    top: 5px;
    width: calc(100% - 10px);
    height: calc(100% - 10px);
}
#header h1 {
    font-size: 28px;
    padding: 5px 5px 5px 10px;
    font-family:'DungGeunMo';
    z-index: 10;
    position: relative;
}
#header h1 a {
    color: #fff;
}
#header h1 em {
    font-size: 0.5em;
}
#header ul  {
    padding: 5px;
}
#header li {
    display: inline;
    z-index: 10;
    position: relative;
}
#header li a {
    color: #fff;
    font-family: 'DungGeunMo';
    border: 1px dashed #fff;
    display: inline-block;
    padding: 5px;
}
#header li.active a,
#header li a:hover {
    background-color: #fff;
    color: #000;
}
/* footer */
#footer {
    position: fixed;
    left: 0;
    bottom: 0;
    width: 100%;
    background-color: #390b47;
    padding: 10px;
    text-align: center;
}
#footer a {
    color: #fff;
    padding: 20px;
    display: block;
    font-family: 'DungGeunMo';
    z-index: 10;
    position: relative;
}
#footer::before {
    content: '';
    border: 4px ridge #580073;
    position: absolute;
    left: 5px;
    top: 5px;
    width: calc(100% - 16px);
    height: calc(100% - 18px);
}
/* main */
#main {
    padding: 100px;
}
/* quiz__wrap */
.quiz__wrap {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
}
.quiz__wrap .quiz {
    width: 500px;
    background-color: #fff;
    border: 8px ridge #a12cd3;
    width: 500px;
}
.quiz__header {}
.quiz__title {
    background-color: #a12cd3;
    border: 3px ridge #a12cd3;
    border-bottom: 6px ridge #a12cd3;
    padding: 5px;
    font-family: 'DungGeunMo';
    font-size: 16px;
    color: #fffffff8;
    text-align: center;
}
.quiz__main {}

.quiz__question {
    padding: 20px;
    font-size: 24px;
    font-family: 'ElandChoice';
    font-weight: bold;
    border-bottom: 6px ridge #a12cd3;
    line-height: 1.4;
}
.quiz__question em {
    color: #2c024e
}
.quiz__answer {
    padding: 20px;
    font-size: 24px;
    font-family: 'ElandChoice';
    font-weight: 500;
    border-bottom: 6px ridge #a12cd3;
    text-align: center;
}
.quiz__answer .confirm {
    color: #fff;
    background-color: #a12cd3;
    width: 100%;
    font-family: 'ElandChoice';
    padding: 10px 20px;
    font-size: 22px;
    cursor: pointer;
    transition: all 0.3s;
    text-shadow: 1px 1px 1px #440460;
    font-weight: bold;
    margin-bottom: 10px;
}
.quiz__answer .confirm:hover {
    background-color: #620689;
}
.quiz__answer .result {
    background-color: #fff;
    border: 6px ridge #a12cd3;
    width: 100%;
    font-family: 'ElandChoice';
    padding: 10px 20px;
    font-size: 22px;
}
.quiz__answer .input {
    background-color: #fff;
    border: 6px ridge #a12cd3;
    width: 100%;
    font-family: 'ElandChoice';
    padding: 10px 20px;
    font-size: 22px;
    margin-bottom: 10px;
}
.quiz__view {
    border-bottom: 6px ridge #a12cd3;
    overflow: hidden;
}
.quiz__footer {
    font-family: 'ElandChoice';
    border-top: 6px ridge #a12cd3;
    padding: 20px;
    background-color: #f2e7fe;
}
.quiz__footer::before {
    content: "✏Tip";
    color : firebrick;
    font-weight: bold;
}
.quiz__desc {
    padding-top: 5px;
}

 

 

textContent

:문서에서 텍스트 컨텐츠를 나타내는 속성이다.

이 속성은 자식 노드[각주:1]를 포함하지 않고 요소의 텍스트 컨텐츠만 반환한다.

HTML  요소의 경우 HTML 태그를 문자열로 반환하며, 스크립트에서 해당 요소의 텍스트를 변경할 수 있다.

 

quizDesc.textContent = infoDesc;

:infoDesc라는 변수에 저장된 문자열 값을 가져와 quizDesc라는 요소의 textContent 속성에 할당한다.

quizDesc는 HTML에서 특정 요소를 나타내는 변수이며, textContent를 사용하면 해당 요소의 텍스트 컨텐츠가 infoDesc로 설정된다.

innerText와 같은 역할을 한다고 볼 수 있다.

 

 

 

value

:HTML 요소의 값을 나타내는 속성. 

이 속성은 해당 요소의 현재 값을 나타낸다. value를 사용하여 요소에 입력 된 값을 가져오거나 설정할 수 있다. 

 

 

const userAnswer = quizAnswerInput.value

:const를 사용하여 userAnswer 변수를 선언하고 quizAnswerInput 객체의 value 속성 값을 할당한다.

즉, 입력 창에 사용자가 입력한 현재 값을 가져오는 것이다.

질문에 대한 사용자의 답변을 받아 상수에 저장하는 작업을 수행한다.

 

 

 

trim( )

:원본 문자열을 수정하지 않으면서 문자열 양 옆(시작과 끝)의 공백만을 제거하여 새로운 문자열을 반환하는 메서드(Method)이다.

 

 

const userAnswer = quizAnswerInput.value.trim( );

:value가 입력 창에 사용자가 입력한 현재 값을 가져오면 trim( )은 이 값을 가져와서 앞 뒤의 공백을 제거한 후 userAnswer 변수에 할당한다.

즉, 사용자가 입력한 값의 앞 뒤 공백이 제거되기 때문에 결과적으로 문자열이 더 정확하게 비교되고 처리될 수 있다.

 

 

classList

:문서 요소의 클래스 속성을 나타내는 속성이다.
이는 DOMTokenList[각주:2]를 반환하며, 해당 객체는 클래스를 조작하는 여러 메서드(Method)를 가지고 있다.
 
 
classList 메서드를 사용하면 문서 요소의 클래스를 추가, 제거 또는 토글[각주:3]할 수 있다.
clasList의 메서드를 사용하여 문서 요소의 클래스를 동적으로 조작하거나 애니메이션 효과를 만드는데 유용하게 사용된다.
 
 
 

dogWrap.classList.add("dislike");

:dogWrap이라는 클래스 이름을 가진 HTML 요소에 dislike라는 css 클래스를 추가한다.
이를 통해 해당 요소의 스타일을 변경할 수 있다.
 

 

justify-content

 

space-between

:아이템들  즉, 요소 사이(between)에 균일한 간격을 만들어 주는 것.

한글office에서 사용하는 문단모양의 배분정렬이라고 생각하면 쉽다. 

 

position

position : fixed

:요소를 화면에서 고정된 위치에 놓도록 지정하는 속성.

스크롤 되더라고 화면에서 계속 같은 위치에 있도록 요소를 고정한다.

top, left, right, bottom 등을 사용하여 위치를 지정할 수 있다.

 

 

width, height

calc(100%-n px)

:100%에서 n px을 뺀 값을 계산하여 그 결과를 적용한다.

 

주로 박스 모델에서 사용되는 표현식이며, 요소의 너비(widht) 높이(height)를 계산할 때 유용하다.

요소가 화면 전체를 차지하면서 좌우 여백이 n px이 되도록 한다.

숫자와 연산자를 조합하여 사용할  수도 있다.

 

 

 

 

 

 

 

  1. html요소에 포함 된 하위 요소 또는 텍스트 노드 다른 말로는 자식 요소가 있다. [본문으로]
  2. 문서 요소의 클래스 목록을 나타내는 객체. [본문으로]
  3. DOMTokenList 객체의 메서드 중 하나로, 클래스의 존재 여부를 확인하고 클래스가 존재하면 제거, 클래스가 없으면 추가하는 역할을 한다 [본문으로]
728x90
반응형

 

퀴즈 이펙트 만들기

 

 

코드 / HTML

<!DOCTYPE html>
<html lang="ko">

<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>퀴즈 이펙트 01</title>

    <link rel="stylesheet" href="css/reset.css">
    <link rel="stylesheet" href="css/quiz.css">
</head>

<body>
    <header id="header">
        <a href="../javascript14.html">
            <h1>Quiz<em> 정답 확인하기 유형
        </a></em></h1>
    </header>
    <!-- //header -->

    <main id="main">
        <div class="quiz__wrap">
            <div class="quiz">
                <div class="quiz__header">
                    <h2 class="quiz__title"><span></span><em></em></h2>
                </div>
                <div class="quiz__main">
                    <div class="quiz__question">
                        <em></em>. <span></span>
                    </div>
                    <div class="quiz__view">
                        <div class="dog__wrap">
                            <div class="card-container">
                                <div class="dog">
                                    <div class="head">
                                        <div class="ears"></div>
                                        <div class="face"></div>
                                        <div class="eyes">
                                            <div class="teardrop"></div>
                                        </div>
                                        <div class="nose"></div>
                                        <div class="mouth">
                                            <div class="tongue"></div>
                                        </div>
                                        <div class="chin"></div>
                                    </div>
                                    <div class="body">
                                        <div class="tail"></div>
                                        <div class="legs"></div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="quiz__answer">
                        <button class="confirm">정답 확인하기 </button>
                        <div class="result"></div>
                    </div>
                </div>
                <div class="quiz__footer"></div>
            </div>
        </div>
    </main>
    <!-- //main -->

    <footer id="footer">
        <a href="mailto:goed0522@gmail.com">goed0522@gmail.com</a>
    </footer>
    <!-- //footer -->

    <script>
        //선택자
        const quizWrap = document.querySelector(".quiz__wrap");
        const quizTitle = quizWrap.querySelector(".quiz__title span");
        const quizTime = quizWrap.querySelector(".quiz__title em");
        const quizQuestion = quizWrap.querySelector(".quiz__question span")
        const quizQuestionNum = quizWrap.querySelector(".quiz__question em");
        const quizAnswerConfirm = quizWrap.querySelector(".quiz__answer .confirm");
        const quizAnswerResult = quizWrap.querySelector(".quiz__answer .result");

        //문제 정보
        const infoType = "웹디자인 기능사";
        const infoTime = "2012년 1회";
        const infoNumber = "1";
        const infoQuestion = "인접하는 두 색의 경계 부분에 색상, 명도, 채도의 대비가 더욱 강하게 일어나는 현상을 무엇이라고 하는가?";
        const infoAnswer = "연변대비";
        
        quizTitle.innerText = infoType;
        quizTime.innerText = infoTime;
        quizQuestionNum.innerText = infoNumber;
        quizQuestion.innerText = infoQuestion;
        quizAnswerResult.innerText = infoAnswer;

        //정답 숨기기
        quizAnswerResult.style.display = "none";

        //정답 확인
        quizAnswerConfirm.addEventListener("click", function (){
            quizAnswerResult.style.display = "block";
            quizAnswerConfirm.style.display = "none";
        });
    </script>
</body>

</html>

 

 

코드 / css

#header {
    position: fixed;
    left: 0;
    top: 0;
    width: 100%;
    background-color: #390b47;
    color: #fff;
    padding: 10px;
    z-index: 1000;
}
#header::before {
    content: '';
    border: 4px ridge #580073;
    position: absolute;
    left: 5px;
    top: 5px;
    width: calc(100% - 10px);
    height: calc(100% - 10px);
}
#header h1 {
    font-size: 28px;
    padding: 0px 5px 5px 10px;
    font-family: 'DungGeunMo';
    z-index: 10;
    position: relative;
}
#header h1 a {
    color: #fff;
}
#header h1 em {
    font-size: 0.5em;
}
/* main */
#main {
    padding: 100px 0;
}
/* footer */
#footer {
    position: fixed;
    left: 0;
    bottom: 0;
    width: 100%;
    background-color: #390b47;
    padding: 10px;
    text-align: center;
}
#footer a {
    color: #fff;
    padding: 20px;
    display: block;
    font-family: 'DungGeunMo';
    z-index: 10;
    position: relative;
}
#footer::before {
    content: '';
    border: 4px ridge #580073;
    position: absolute;
    left: 5px;
    top: 5px;
    width: calc(100% - 16px);
    height: calc(100% - 18px);
}

/* quiz__wrap */
.quiz__wrap {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
}
.quiz__wrap .quiz {
    max-width: 500px;
    background-color: #fff;
    border: 8px ridge #a12cd3;
    width: 500px;
}
.quiz__header {}
.quiz__title {
    background-color: #a12cd3;
    border: 3px ridge #a12cd3;
    border-bottom: 6px;
    padding: 5px;
    font-family: 'DungGeunMo';
    font-size: 16px;
    color: #fffffff8;
    text-align: center;
}
.quiz__main {}

.quiz__question {
    padding: 20px;
    font-size: 24px;
    font-family: 'ElandChoice';
    font-weight: bold;
    border-bottom: 6px ridge #a12cd3;
}
.quiz__question em {
    color: #2c024e
}
.quiz__answer {
    padding: 20px;
    font-size: 24px;
    font-family: 'ElandChoice';
    font-weight: 500;
    border-bottom: 6px ridge #a12cd3;
    text-align: center;
}
.quiz__answer .confirm {
    color: #fff;
    background-color: #a12cd3;
    width: 100%;
    font-family: 'ElandChoice';
    padding: 10px 20px;
    font-size: 22px;
    cursor: pointer;
    transition: all 0.3s;
    text-shadow: 1px 1px 1px #440460;
}
.quiz__answer .confirm:hover {
    background-color: #620689;
}
.quiz__answer .result {
    background-color: #fff;
    border: 6px ridge #a12cd3;
    width: 100%;
    font-family: 'ElandChoice';
    padding: 10px 20px;
    font-size: 22px;
}
.quiz__view {
    border-bottom: 6px ridge #a12cd3;
    overflow: hidden;
}
.quiz__footer {}

 

 

 

querySelector

document.querySelector( (.quiz__wrap) )

:HTML 문서 내에서 특정 선택자를 가진 요소를 찾아 해당 요소의 참조를 반환하는 역할을 한다.

Selector 뒤에 quiz__wrap이 붙으면 class 속성 값이 quiz__wrap인 첫번째 요소를 찾아 반환합니다.

반환된 요소에 대해 자바스크립트를 이용하여 다양한 조작이 가능하다.

 

quizWrap.querySelector

: quiz__Wrap 변수가 참조하는 요소의 하위 요소 중 Selector 뒤에 오는 선택자의 class 속성을 가진 첫번째 요소를 찾아 해당 요소의 참조를 반환 한다.

document.querySelector와 달리 quiz__Wrap 변수가 참조하는 요소 내에서만 검색을 수행하므로 검색 범위가한정적이다.이는 불필요한 검색을 줄이고 실행 속도를 향상시킨다.

 

.style.display

: HTML 문서 요소의 CSS 스타일을 조작하기 위한 프로퍼티로, 해당 요소의 display 속성 값을 설정하거나 가져올 수 있다.

 

요소명(ex. quizAnswerResult).style.display : none;

: 해당 요소를 화면에서 숨기는 역할을 수행한다. 요소를 화면에서 숨길 뿐 아니라 해당 요소의 영역도 사라진다.

 

요소명.style.display: block;

:해당 요소를 화면에 보이게 하는 역할을 수행한다.

 

 

 

요소명(ex.quizTitle).innerText

:텍스트 컨텐츠를 가져오거나 설정하기 위한 속성.

특정 HTML 요소의 텍스트 컨텐츠를 읽거나 수정할 수 있다.

특정 class나 id를 가진 요소의 텍스트 컨텐츠를 가져와서 변수에 저장하거나 새로운 텍스트를 할당하여 해당 요소의 내용을 변경할 수 있다.
 
 
 

.addEventListener

:이벤트를 처리하기 위해 사용되는 메서드(Method)이다.

이를 사용하면 HTML 요소에서 발생하는 이벤트 (클릭, 마우스 오버, 키보드 입력 등)을 감지하고 이벤트가 발생하였을 때 실행할 함수를 등록할 수 있다.

 

 

 

quizAnswerConfirm.addEventListener("click", function (){

            quizAnswerResult.style.display = "block";
            quizAnswerConfirm.style.display = "none";
        });

 : quizAnswerConfirm 이라는 요소 (정답확인하기 버튼)을 클릭했을 때 실행될 함수가 등록되어 있다.

등록된 함수는 quizAnswerResult 요소를 다시 화면에 보여주어(display: block; 하여) 정답 결과를 나타내고,  quizAnswerConfirm(정답확인버튼)을 화면에서 사라지게 만든다. (display: none;을 실행시킴)

 

 

 

z-index

:HTML 요소의 쌓이는 순서를 지정하는 CSS 속성이다.

z-index 값이 높을 수록 요소가 위에 쌓이게 된다.

 

또한 z-index는 요소가 겹쳐져 있을 때, 이 값을 설정하여 어떤 요소를 먼저 보여줄지 결정할 수 있다.

만약 요소 두 개가 겹쳐져 있다면 보여주어야 하는 요소의 z-index 값을 높게 설정해 위에 표시되게 한다.

z-index의 값은 정수로 지정된다. 음수 값도 사용할 수는 있다.

값이 같은 경우, HTML 코드 순서에 따라 나중에 작성된 요소가 위에 쌓이게 된다.

 

 

 

visibility

:CSS 속성 중 하나로 HTML 요소를 보이게 하는 속성이다.
이 속성을 지정하면 해당 요소가 회면에 표시된다.
 

visible or hidden

:요소를 화면에 보여주기 위해 wisible을 사용한다.
 반대로 요소가 화면에서 사라지게 할 때 hidden을 사용하며, 사용 시 요소는 화면에서 사라지지만 영역은 유지된다.
 
 
 

animation

:CSS 요소에 애니메이션 효과를 적용하는 속성으로, 이 속성을 사용하면 요소에 지정된 애니메이션 효과가 지정된 속성값에 따라 작동한다.

 

animation: cry 0.1s ease-in 0.25s forwards;

▶ cry

: 애니메이션 이름

 

▶ 0.1s

: 애니메이션의 재생 시간으로, 애니메이션이 한 번 실행될 때 걸리는 시간이다.

 

▶ ease-in

: 애니메이션의 타이밍 함수.  이 함수는 애니메이션의 속도를 조절할 수 있으며, 느리게 시작했다가 점점 빨라지는 타이밍 함수이다.

 

▶ 0.25s

: 애니메이션의 지연 시간 (delay) .

 

▶forwards

:애니메이션이 종료된 후에도 애니메이션의 마지막 프레임을 유지한다.

즉, 애니메이션이 끝난 후에도 요소의 최종 상태를 유지하는 것.

 

 

animation: movetail 0.1s linear infinite alternate forwards;

 

▶linear

:애니메이션의 타이밍 함수로, 이 함수는 애니메이션을 일정한 속도로 실행한다.

 

▶infinite

:애니메이션을 무한 반복하는 함수.

 

▶alternate

:애니메이션을 왕복하는 방향으로 반복한다.

 

 

 

 

 

 

+ Recent posts