JS30 day26 Stripe Follow Along Dropdown

Posted by Anthony Chao on 2019-11-26

JS30 day26 - Stripe Follow Along Dropdown

作業內容

這次的網頁要做出下拉式選單動態移動的效果
今天的作業其實 code 本身不難,難就難在你能不能想到用這個概念做出效果,如果是我應該想不到XD,創意性看來還要再加油
可以參考這份 CodePen的效果

學到什麼

  • CSS

    1. 這邊很特別的地方是把隱藏的選項顯現出來的方式,一開始是 display: none 加上 opacity: 0
      第一步驟先把 display: none 變成 diaplay: block
      第二步再把 opacity: 0 變成 opacity: 1
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    .dropdown {
    opacity: 0;
    position: absolute;
    overflow: hidden;
    padding: 20px;
    top: -20px;
    border-radius: 2px;
    transition: all 0.5s;
    transform: translateY(100px);
    will-change: opacity;
    display: none;
    }

    .trigger-enter .dropdown {
    display: block;
    }

    .trigger-enter-active .dropdown {
    opacity: 1;
    }
  • JS

    1. 有個很細節的地方在於,如果導覽列上方有東西,我們必須把座標值扣掉,否則白框位置會跑掉
    1
    2
    3
    4
    5
    6
    const coords = {
    height: dropdownCoords.height,
    width: dropdownCoords.width,
    top: dropdownCoords.top - navCoords.top,
    left: dropdownCoords.left - navCoords.left
    }

    dropdownCoords.top - navCoords.top,像這行就是下拉選單的座標扣掉導覽列的上方座標

    1. 除此之外還有更細節的地方,一開始程式碼長的是下面這樣
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function handleEnter(){
    this.classList.add('trigger-enter')
    setTimeout(() => {
    this.classList.add('trigger-enter-active')
    },150)
    //下略
    }
    function handleLeave(){
    this.classList.remove('trigger-enter', 'trigger-enter-active')
    }
    //下略

    我再加上 class 的時候為了有動畫的效果,有個 setTimeout 設定 150 微秒之後才加上 trigger-enter-active 這 class,但移除的時候是同時移除
    這樣會有個問題,當我在導覽列中快速移動,可能已經觸發到移除了,但 150 微秒還沒到,所以後面下拉選單的內容才跑出來
    解決方法如下:再設上一個條件,150微秒的時候檢查是不是有一開始加上的那個 class trigger-enter,沒有的話就不用再加上這個 class

    1
    2
    3
    4
    5
    6
    7
    8
    function handleEnter(){
    this.classList.add('trigger-enter')
    setTimeout(() => {
    if(this.classList.contains('trigger-enter')){
    this.classList.add('trigger-enter-active')
    }
    },150)
    //下略

https://github.com/wesbos/JavaScript30

code 內容:

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
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
<h2>Cool</h2>
<nav class="top">
<div class="dropdownBackground">
<span class="arrow"></span>
</div>

<ul class="cool">
<li>
<a href="#">About Me</a>
<div class="dropdown dropdown1">
<div class="bio">
<img src="https://logo.clearbit.com/wesbos.com">
<p>Wes Bos sure does love web development. He teaches things like JavaScript, CSS and BBQ. Wait. BBQ isn't part of web development. It should be though!</p>
</div>
</div>
</li>
<li>
<a href="#">Courses</a>
<ul class="dropdown courses">
<li>
<span class="code">RFB</span>
<a href="https://ReactForBeginners.com">React For Beginners</a>
</li>
<li>
<span class="code">ES6</span>
<a href="https://ES6.io">ES6 For Everyone</a>
</li>
<li>
<span class="code">NODE</span>
<a href="https://LearnNode.com">Learn Node</a>
</li>
<li>
<span class="code">STPU</span>
<a href="https://SublimeTextBook.com">Sublime Text Power User</a>
</li>
<li>
<span class="code">WTF</span>
<a href="http://Flexbox.io">What The Flexbox?!</a>
</li>
<li>
<span class="code">GRID</span>
<a href="https://CSSGrid.io">CSS Grid</a>
</li>
<li>
<span class="code">LRX</span>
<a href="http://LearnRedux.com">Learn Redux</a>
</li>
<li>
<span class="code">CLPU</span>
<a href="http://CommandLinePowerUser.com">Command Line Power User</a>
</li>
<li>
<span class="code">MMD</span>
<a href="http://MasteringMarkdown.com">Mastering Markdown</a>
</li>
</ul>
</li>
<li>
<a href="#">Other Links</a>
<ul class="dropdown dropdown3">
<li><a class="button" href="http://twitter.com/wesbos">Twitter</a></li>
<li><a class="button" href="http://facebook.com/wesbos.developer">Facebook</a></li>
<li><a class="button" href="http://wesbos.com">Blog</a></li>
<li><a class="button" href="http://wesbos.com/courses">Course Catalog</a></li>
</ul>
</li>
</ul>
</nav>

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
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
html {
box-sizing: border-box;
font-family: "Arial Rounded MT Bold", "Helvetica Rounded", Arial, sans-serif;
}

*, *:before, *:after {
box-sizing: inherit;
}

body {
margin: 0;
min-height: 100vh;
background:
linear-gradient(45deg, hsla(340, 100%, 55%, 1) 0%, hsla(340, 100%, 55%, 0) 70%),
linear-gradient(135deg, hsla(225, 95%, 50%, 1) 10%, hsla(225, 95%, 50%, 0) 80%),
linear-gradient(225deg, hsla(140, 90%, 50%, 1) 10%, hsla(140, 90%, 50%, 0) 80%),
linear-gradient(315deg, hsla(35, 95%, 55%, 1) 100%, hsla(35, 95%, 55%, 0) 70%);
}

h2 {
margin-top: 0;
padding-top: .8em;
}

nav {
position: relative;
perspective: 600px;
}

.cool > li > a {
color: yellow;
text-decoration: none;
font-size: 20px;
background: rgba(0,0,0,0.2);
padding: 10px 20px;
display: inline-block;
margin: 20px;
border-radius: 5px;
}

nav ul {
list-style: none;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
}

.cool > li {
position: relative;
display: flex;
justify-content: center;
}

.dropdown {
opacity: 0;
position: absolute;
overflow: hidden;
padding: 20px;
top: -20px;
border-radius: 2px;
transition: all 0.5s;
transform: translateY(100px);
will-change: opacity;
display: none;
}

.trigger-enter .dropdown {
display: block;
}

.trigger-enter-active .dropdown {
opacity: 1;
}

.dropdownBackground {
width: 100px;
height: 100px;
position: absolute;
background: #fff;
border-radius: 4px;
box-shadow: 0 50px 100px rgba(50,50,93,.1), 0 15px 35px rgba(50,50,93,.15), 0 5px 15px rgba(0,0,0,.1);
transition: all 0.3s, opacity 0.1s, transform 0.2s;
transform-origin: 50% 0;
display: flex;
justify-content: center;
opacity:0;
}

.dropdownBackground.open {
opacity: 1;
}

.arrow {
position: absolute;
width: 20px;
height: 20px;
display: block;
background: white;
transform: translateY(-50%) rotate(45deg);
}

.bio {
min-width: 500px;
display: flex;
justify-content: center;
align-items: center;
line-height: 1.7;
}

.bio img {
float: left;
margin-right: 20px;
}

.courses {
min-width: 300px;
}

.courses li {
padding: 10px 0;
display: block;
border-bottom: 1px solid rgba(0,0,0,0.2);
}

.dropdown a {
text-decoration: none;
color: #ffc600;
}

a.button {
background: black;
display: block;
padding: 10px;
color: white;
margin-bottom: 10px;
}

/* Matches Twitter, TWITTER, twitter, tWitter, TWiTTeR... */
.button[href*=twitter] { background: #019FE9; }
.button[href*=facebook] { background: #3B5998; }
.button[href*=courses] { background: #ffc600; }

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
const triggers = document.querySelectorAll('.cool > li')
const background = document.querySelector('.dropdownBackground')
const nav = document.querySelector('.top')

function handleEnter(){
this.classList.add('trigger-enter')
setTimeout(() => {
if(this.classList.contains('trigger-enter')){
this.classList.add('trigger-enter-active')
}
},150)
background.classList.add('open')

const dropdown = this.querySelector('.dropdown') // 要這時候才抓,因為要抓到不同時候的下拉選單
const dropdownCoords = dropdown.getBoundingClientRect()
const navCoords = nav.getBoundingClientRect()
const coords = {
height: dropdownCoords.height,
width: dropdownCoords.width,
top: dropdownCoords.top - navCoords.top,
left: dropdownCoords.left - navCoords.left
}

background.style.setProperty('width', `${coords.width}px`)
background.style.setProperty('height', `${coords.height}px`)
background.style.setProperty('transform', `translate(${coords.left}px, ${coords.top}px`)
}
function handleLeave(){
this.classList.remove('trigger-enter', 'trigger-enter-active')
}

triggers.forEach( trigger => trigger.addEventListener('mouseenter', handleEnter))
triggers.forEach( trigger => trigger.addEventListener('mouseleave', handleLeave))




prevent_hack