Day26 Follow Along Nav

Demo

這次的範例有一小部份效果看似跟Day22的範例一樣
但其中藏了滿多值得留意的重點和技巧!

看似無聊但很多重點的單元!

觀察 HTML 和 CSS 結構

在範例中,每個 li 裡所包含的資料都已經存在
資料要包在 li 裡也關係到了我們觸發事件使用的mouseentermouseleave

mouseenterc 和 mouseover 差別:介紹 1 / 介紹 2

在 css 部分,我們要先隱藏原本的內容,除了透明外,display:none 是必須的
不然我們滑鼠一碰到這些隱藏內容,就會觸發事件
(因為內容包在 li 內,碰到內容即碰到 li,這不是我們要的效果)。

css 部屬

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;
}

說明:原始元素只有套用.dropdown這個 class,而下方兩個樣式是在必須有父層樣式(.trigger-enter,.trigger-enter-active)的條件下,才會有效果的 class,因此這邊我們是去控制父層的 class 來改變.dropdown的屬性。

再透過事件觸發去加上或刪除指定的 class
(分開寫純粹是效果需求)

對象和觸發條件

一樣先把觸發對象和條件先建構出來

1
2
3
4
5
6
7
8
9
10
11
12
13
const triggers = document.querySelectorAll(".cool > li");
const background = document.querySelector(".dropdownBackground");
const nav = document.querySelector(".top");

function handleEnter() {
//..
}
function handleLeave() {
//..
}

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

滑鼠移入後

滑鼠移入後,按照效果需求先後加上class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function handleEnter() {
this.classList.add('trigger-enter');
//關鍵:判斷有沒有class的方法
setTimeout(() => {
//此時的this已變成window,所以用ES6立即函示
//console.log(this)
if (this.classList.contains('trigger-enter')) {
this.classList.add('trigger-enter-active')
}
//小技巧:
//this.classList.contains('trigger-enter') && t his.classList.add('trigger-enter-active')
}, 150)
background.classList.add('open');
}

當然在這邊也可以不用特別去判斷是否有trigger-enter效果一樣存在,但因為有了這個判斷
我們可以認識DOMTokenList.contains

關鍵:DOMTokenList.contains()

為什麼不是includes?

includes() 方法會判斷陣列是否包含特定的元素,並以此來回傳 true 或 false。

因為我們的this.classList不是陣列

我們透過contains
DOMTokenList.contains()

The contains() method of the DOMTokenList interface returns a Boolean — true if the underlying list contains the given token, otherwise false.

還有很多方法可以判斷:
如何用JavaScript判断dom是否有class的值?

當然在這邊也可以不做setTimeout延遲效果,但因為有了setTimeout
我們注意到了this發生了變化!

關鍵:this

錯誤寫法:

1
2
3
4
5
6
setTimeout(function(){
console.log(this)
if (this.classList.contains('trigger-enter')) {
this.classList.add('trigger-enter-active')
}
}, 150)


在setTimeout裡面我們的this變成了window

ES5中的this指向的事觸發該涵式的對象

setTimeout是由windows觸發的。
所以這時候他不知道window.contains是什麼,因此出現錯誤。

解決方法可以簡單透過變數在setTimeout之前定義對象,不要用this即可
const showItem = event.target;

this看的是究竟是誰調用該函式 #Javascript:this用法整理


我們透過es6箭頭涵式中this的特性來解決

ES6中箭頭函數它没有自己的this值,它繼承外圍作用域的this

1
2
3
4
5
setTimeout(() => {
if (this.classList.contains('trigger-enter')) {
this.classList.add('trigger-enter-active')
}
}, 150)

所以這時的this指的就是我們的li

條件式縮寫

在原始範例中出現一個少見的條件式簡寫
原本:

1
2
3
if (this.classList.contains('trigger-enter')) {
this.classList.add('trigger-enter-active')
}

可以縮寫成:

1
this.classList.contains('trigger-enter') && this.classList.add('trigger-enter-active')

一個不起眼範例藏了滿多細節的!

不負責任聲明ಥ◡ಥ:
純屬個人筆記,每個範例都有不同的寫法,還有很多細節可以加入,甚至可能有看不到的bug,有任何問題都歡迎提出一起研究哦,我會很感謝您的!沒有好不好,只有適不適合 - 2018.09.01

0%