CSS&JS/👀Study and Copy

[WDS]Intro.js 바닐라 자바스크립트로 구현하기

arancia_ 2022. 8. 18. 15:42

데모페이지 : https://ohikmyeong.github.io/wds-library/mine/

 

mine

Houses For Sale Live Demo Lorem ipsum dolor sit, amet consectetur adipisicing elit. Magnam tempore nostrum autem vel esse libero! Details Contact Seller X modal title modal content

ohikmyeong.github.io

WDS 강의 영상 : https://www.youtube.com/watch?v=vTsVl66q9jo

IntroJS : https://introjs.com

 

User Onboarding and Product Walkthrough Library | Intro.js

Create step-by-step product tours, user onboarding guides easily. Intro.js is a lightweight, open-source user onboarding library. Get started for free!

introjs.com

 

HTML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!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>mine</title>
    <link rel="stylesheet" type="text/css" href="./css/main.css"/>
    <script src="./js/main.js" type="module"></script>
</head>
<body>
    <h1 id="pageTitle">Houses For Sale</h1>
    <button id="btn-live-demo">Live Demo</button>
 
    <div id="wrap-card">
        <template>
            <section class="card">
                <img src="https://source.unsplash.com/178j8tJrNlc" alt=""/>
                <article class="card-txt">Lorem ipsum dolor sit, amet consectetur adipisicing elit. Magnam tempore nostrum autem vel esse libero!</article>
                <article class="card-btns">
                    <button class="card-btn blue">Details</button>
                    <button class="card-btn">Contact Seller</button>
                </article>
            </section><!-- card -->
        </template>
    </div><!-- wrap-card -->
 
    <!-- modal -->
    <div id="highlighter-box" class="off"></div><!-- highlighter -->
 
    <div id="modal" class="off">
        <button id="btn-modal-close">X</button>
        <h3 id="modal-head">modal title</h3>
        <section id="modal-body">modal content</section>
        <ul id="modal-pager"></ul>
        <section id="modal-foot">
            <button id="btn-modal-back" class="off">Back</button>
            <button id="btn-modal-next">Next</button>
        </section><!-- modal-foot -->
    </div><!-- modal -->
</body>
</html>
cs

modal.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
@charset "utf-8";
/* 툴팁 */
#modal{
    position:absolute;
    top:0;left:0;
    min-width:300px; max-width:90%;
    border:1px solid #ccc; border-radius:8px;
    background:#fff;
    box-shadow:0 0 5px rgba(0,0,0,.1);
    transition:opacity .5s;
}
 
    /* off */
    #modal.off{
        opacity:0;
        pointer-events:none;
    }
#btn-modal-close{
    display:block;
    position:absolute;
    top:0;right:0;
    width:35px;aspect-ratio:1/1;
    background:transparent;border:none;
    font-weight:bold; color:#999;
}
 
#modal-head{
    padding:1em; padding-bottom:.5em;
    text-align:center; font-size:1.3rem;
    text-transform:uppercase; letter-spacing:-.01em;
}
 
#modal-body{
    position:relative;
    padding:0 1.5rem;
}
 
/* pager */
#modal-pager{
    display:flex; flex-flow:row nowrap;
    justify-content:center; align-items:center;
    gap:3px;
    margin-top:1rem;}
 
#modal-pager li{
    width:20px; height:5px;
    background:#ddd;
    border-radius:2px;
    
}
 
    #modal-pager li.on{
        background:var(--blue); box-shadow:
            0 0 2px 1px #fff,
            0 0 10px 1px var(--blue-op-2);
        transition:box-shadow .2s .5s;}
 
/* footer */
#modal-foot{
    display:flex;flex-wrap:row nowrap;
    justify-content:space-between; align-items:center;
    position:relative;
    margin-top:1rem;
    border-top:1px solid #ddd;}
 
#btn-modal-back,
#btn-modal-next{
    padding:1em 2em;
    background:transparent;
    border:none;
    color:#777; font-weight:500; font-size:12px;
    user-select:none;}
 
    /* hover */
    #btn-modal-back:hover,
    #btn-modal-next:hover{background:#f9f9f9;}
    /* focus */
    #btn-modal-back:focus,
    #btn-modal-next:focus{background:#eee;color:var(--blue);}
    /* off */
    #modal-foot button.off{
        pointer-events:none; color:#ccc;}
 
#btn-modal-back{border-right:1px solid #fafafc;}
#btn-modal-next{border-left:1px solid #fafafc;}
    
    
/* 하이라이터 */
#highlighter-box{
    position:absolute;
    top:0;left:0;
    border:3px solid #000;
    box-shadow:0 0 0 200vmax rgba(0,0,0,.5);
    cursor:pointer;
    transition:opacity .5s;
}
 
    #highlighter-box.off{
        opacity:0;
        pointer-events:none;user-select:none;
    }
cs

main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import { CardMaker, fake_card_data_maker } from "./cardMaker.js";
import { ModalMaker } from "./modalMaker.js";
 
const cardData = fake_card_data_maker(10);
const modalData = [
    {
        title : "Hello World",
        body : "<p>This the first Tutorial of Blah Blah...</p><p>잘 따라 와보셈</p>",
    },
    {
        title : "Title(2)",
        body : "집중 되니???",
        focus : true
    },
    {
        title : "Title(3)",
        body : "했다가 안 했다가 할 수도 있습니다.",
        focus : true
    },
    {
        title : "조금만 더 견뎌봐요",
        body : "피카츄 라이츄 파이리 꼬부기",
    },
    {
        title : "존버",
        body : "버터플 야도란 피존투 또가스",
        focus : true
    },
    {
        title : "Title(4)",
        body : "짠짠 마지막 포커스",
        focus : true
    },
    {
        title : "끝!",
        body : "수고했졍",
    },
];
 
new CardMaker(cardData);
const modalMaker  = new ModalMaker(modalData);
const $btnDemo = document.getElementById('btn-live-demo');
modalMaker.init();
 
$btnDemo.addEventListener('click',()=>{
    modalMaker.reset_idx();
    modalMaker.reset_ftr_btn();
    modalMaker.display_modal();
});
cs

fn.js

1
2
3
4
5
6
7
export function domMaker(domName,...clssList){
    const $dom = document.createElement(domName);
    for(const clssName of clssList){
        $dom.classList.add(clssName);
    }//for
    return $dom;
}//domMaker
cs

CardMaker.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import { domMaker } from "./fn.js";
 
export function fake_card_data_maker(limit){
    const url = "https://source.unsplash.com/";
    const ImgID = ["1ddol8rgUH8","178j8tJrNlc""eWqOgJ-lfiI","1sCXwVoqKAw","B0aCvAVSX8E","L7EwHkq1B2s","TiVPTYCG_3E","CnXVHyO1GGA","IYfp2Ixe9nM","yFV39g6AZ5o"];
    const TXT = "Lorem ipsum dolor sit, amet consectetur adipisicing elit. Magnam tempore nostrum autem vel esse libero! 어쩌고 저쩌고 얄리얄리 얄랑셩 얄라".split(' ');
    
    const result = [];
 
    for(let i=0; i<limit; i++){
        const obj = {};
        
        const randomImg = ImgID[Math.floor(Math.random() * ImgID.length)];
        const IMG = `${url}${randomImg}`;
        obj.img = IMG;
        
        const start = Math.round(Math.random() * 3);
        const final = Math.round(Math.random() * TXT.length+ 3;
        obj.txt = TXT.slice(start,final).join(' ');
        result.push(obj);
    }//for
    return result;
}//fake_card_data_maker
 
 
export class CardMaker{
    constructor(data){
        this.data = data;
        this.$wrap = document.getElementById('wrap-card');
        this.init();
    }//constructor
 
    init(){
        const $frag = document.createDocumentFragment();
        this.data.forEach((data,idx) => {
            const $card = this.make_card(data);
            if([2,5,6,9].includes(idx)){
                $card.dataset.focus = "focus";
            }//if
            $frag.appendChild($card);
        });
 
        this.$wrap.appendChild($frag);
    }//init
 
    make_card(data){
        const {img,txt} = data;
        const $card = domMaker('SECTION','card'); 
        
        const $img = new Image();
        $img.src = img;
        
        const $txt = domMaker('ARTICLE','card-txt');
        $txt.textContent = txt;
 
        const $btns = domMaker('ARTICLE','card-btns');
        const btn1 = domMaker('BUTTON','card-btn','blue');
        btn1.textContent = "Details";
        const btn2 = domMaker('BUTTON','card-btn');
        btn2.textContent = "Contact Seller";
        $btns.appendChild(btn1);
        $btns.appendChild(btn2);
 
        $card.appendChild($img);
        $card.appendChild($txt);
        $card.appendChild($btns);
        return $card;
    }//make_card
}//CardMaker
cs

ModalMaker.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
import { Highlighter } from "./Highlighter.js";
 
export class ModalMaker{
    constructor(data){
        this.HIGH = new Highlighter();
        this.data = data;
        this.$$focus = this.make_focus_list();
        this.$modal = document.getElementById('modal');
        this.idx = 0;
    }//constructor
 
    reset_idx(){this.idx = 0;}
    
    init(){
        //pager 만들기
        this.add_pager();
        //하단 버튼 준비
        this.set_click_ftr_modal();
        //닫을 준비
        this.add_close_event();
 
        //모달 행동 총괄
        this.display_modal();
 
        //resize시 이벤트 추가
        this.add_on_resize();
    }//init
 
    display_modal(){
        //pager에 on 표시 
        this.on_pager();
        //모달 내용 바꾸기
        this.change_content();
        //모달 위치 조정
        this.set_pos_modal();
        //하이라이트 박스 조정
        this.HIGH.set_pos(this.$$focus[this.idx]);
        //해당 위치로 스크롤
        this.scroll_to_focus();
        //보이고, 
        if(this.$modal.classList.contains('off')) this.show_modal(true);
    }//display_modal
 
    /* focus list 만들기 */
    make_focus_list(){
        const $$focus = Array.from(document.querySelectorAll('[data-focus="focus"]'));
        const result = [];
        for(let i=0; i<this.data.length; i++){
            const {focus} = this.data[i];
            if(focus){
                result.push($$focus.shift());
            }else{
                result.push(null);
            }//if else
        }//for
        return result;
    }//make_focus_list
   
    /* pager 만들기 */
    add_pager(){
        const $pager = document.getElementById('modal-pager');
        const $frag = document.createDocumentFragment();
        for(let i=0; i<this.data.length; i++){
            const $li = document.createElement('LI');
            $frag.appendChild($li);
        }//for
        $pager.appendChild($frag);
    }//add_pager
 
    on_pager(){
        const $$li = document.getElementById('modal-pager').children;
        const target = $$li[this.idx];
        const $$sib = Array.prototype.filter.call($$li, $li => $li !== target);
        target.classList.add('on');
        $$sib.forEach($sib => $sib.classList.remove('on'));
    }//on_pager
 
    /* 하단 버튼 준비 */
    set_click_ftr_modal(){
        const $ftr = document.getElementById('modal-foot');
        $ftr.addEventListener('click',this.on_click_ftr_modal);
    }//set_click_ftr_modal
 
    /* Back, Next 버튼 누를 때 */
    on_click_ftr_modal = e =>{
        if(e.target.tagName !== "BUTTON"return;
        const btnType = e.target.id.match(/btn\-modal\-(\w*)/)[1];
        const $back = document.getElementById('btn-modal-back');
        const $next = document.getElementById('btn-modal-next');
            
        if(btnType == "next"){
            /* NEXT */
            this.idx++;
            $back.classList.remove('off');
            if(this.idx >= this.data.length - 1){
                this.idx = this.data.length - 1;
                $next.classList.add('off');
            }//if
            $next.blur();
        }else{
            /* PREV */
            this.idx--;
            $next.classList.remove('off');
 
            if(this.idx <= 0){
                this.reset_idx();
                this.reset_ftr_btn();
            }//if
            $back.blur();
        }//if else
 
        this.display_modal();
    }//on_click_ftr_modal
 
    reset_ftr_btn(){
        const $back = document.getElementById('btn-modal-back');
        const $next = document.getElementById('btn-modal-next');
        $back.classList.add('off');
        $next.classList.remove('off');
    }//reset_ftr_btn
 
    /* 모달 내용 바꾸기 */
    change_content(){
        const data = this.data[this.idx];
        const {title,body} = data;
        const $head = document.getElementById('modal-head');
        const $body = document.getElementById('modal-body');
        $head.textContent = title;
        $body.innerHTML = body;
    }//change_content
 
    /* 모달 위치 지정*/
    set_pos_modal(){
        const $focus = this.$$focus[this.idx];
        const winHeiHalf = window.innerHeight / 2;
        const winWidHalf = window.innerWidth / 2;
        const mdHei = this.$modal.offsetHeight;
        const mdWid = this.$modal.offsetWidth;
        if(!$focus){
            const scY = window.scrollY;
            const TOP = scY + (winHeiHalf - mdHei / 2);
            const LEFT = winWidHalf - mdWid / 2;
            this.$modal.style.top = `${TOP}px`;
            this.$modal.style.left = `${LEFT}px`;
        }else{
            const dom = $focus.getBoundingClientRect();
            const focusTop = dom.top + (dom.height / 2- (mdHei / 2);
            const focusLeft = dom.left;
            const TOP = focusTop + 10;
            const LEFT_LEFT = focusLeft - mdWid - 20
            const LEFT_RIGHT = focusLeft + dom.width + 20;
            let LEFT = focusLeft > winWidHalf ? LEFT_LEFT : LEFT_RIGHT ; 
            const is_full_wid = Math.ceil($focus.getBoundingClientRect().width + (16 * 4.5));
            if(window.innerWidth <= is_full_wid){LEFT = winWidHalf - mdWid / 2;}
            this.$modal.style.top = `${TOP  + window.scrollY}px`;
            this.$modal.style.left = `${LEFT}px`;
        }//else
    }//set_pos_modal
 
    /* 모달 보이기 */
    show_modal(bool){
        this.$modal.classList.toggle('off',!bool);
        this.HIGH.show_box(bool);
    }//show_modal
 
    /* 모달 X 버튼 */
    add_close_event(){
        const $close = document.getElementById('btn-modal-close');
        $close.addEventListener('click',()=>{
            this.show_modal(false);
        });
    }//add_close_event
 
    /* 그 위치로 스크롤 */
    scroll_to_focus(){
        const $focus = this.$$focus[this.idx];
        if(!$focus) return;
        window.scroll({
            top : $focus.getBoundingClientRect().top + window.scrollY - 30,
            behavior : 'smooth'
        });
    }//scroll_to_focus
 
    /* resize시 이벤트 추가 */
    add_on_resize(){
        window.addEventListener('resize',()=>{
            this.set_pos_modal();
            this.HIGH.set_pos(this.$$focus[this.idx]);
        });
    }//add_on_resize
}//ModalMaker
cs

Highlighter.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
export class Highlighter{
    constructor(){
        this.$box = document.getElementById('highlighter-box');
    }//constructor
 
    show_box(bool){
        this.$box.classList.toggle('off',!bool);
    }//show_box
 
    set_pos($focus){
        if(!$focus){
            this.reset_box();
            return;
        }else{
            this.change_box($focus);
        };
    }//set_pos
 
    reset_box(){
        const TOP = (window.innerHeight / 2+ window.scrollY;
        const LEFT = (window.innerWidth / 2);
        this.$box.style.left = `${LEFT}px`;
        this.$box.style.top = `${TOP}px`;
        this.$box.style.width = '1px';
        this.$box.style.height = '1px';
    }//reset_box
 
    change_box($focus){
        const {left,top,width,height} = $focus.getBoundingClientRect();
        this.$box.style.left = `${left - 10}px`;
        this.$box.style.top = `${window.scrollY + top - 10}px`;
        this.$box.style.width = `${width + 20}px`;
        this.$box.style.height = `${height + 20}px`;
    }//change_box
}//Highlighter
cs

'CSS&JS > 👀Study and Copy' 카테고리의 다른 글

[Nomad Coders] 바닐라 JS로 그림판 만들기 - 2021ver  (0) 2022.08.26
WDS-Snake Game  (0) 2022.08.22
[WDS] Flip Card Time Turner  (0) 2022.08.17
WDS,OT - Magic CSS Indicator  (0) 2022.08.09
WDS- Sortable Drag & Drop List  (0) 2022.08.09