背景
在日常的項目開發中,我們會很經常的遇見如下的需求:
- 在瀏覽器頁面中,當鼠標移動到某個部分後,另一個部分在延遲若干時間後出現
- 在鼠標移除該區域後,另一部分也在延遲若干時間後消失
我相信這是一個很常見的一個需求,有很多種方式能夠實現,但是,其實現方式的原理各不相同,也有利有弊。
實現方案
CSS
在CSS中,有一個僞類hover
也能夠監聽鼠標移動到某個元素上面,因此我們也可以利用CSS來實現我們剛剛的功能。
我們需要使用的是CSS3中的新特性:transition
。
transition
是一個簡寫屬性,可設置transition-property
, transition-duration
, transition-timing-function
, transition-delay
。 transition
用來定義元素兩種狀態之間的過渡。不同狀態可以用 pseudo-classes 定義,比如 :hover
、:active
或使用JavaScript設置。
該屬性第一個值爲需要變化的屬性值,第二個值爲其持續時間,第三個值爲變化方式,第四個值爲其延時。該屬性指定的值只要指定的屬性有任何變化,都會觸發該屬性。即在從該樣式到其他樣式,以及其他樣式回到該樣式時都會產生效果。例如:
transition:opacity 1s linear 1s;
具體介紹請看MDN官方介紹。
現在,讓我們用transition
屬性來實現上面的功能。簡單點如下所示:
//HTML文件 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="css/index.css"> </head> <body> <div id="container"> <div id="div1"></div> <div id="div2"></div> </div> </body> </html> //Sass文件 #container { display: inline-block; #div1 { width: 100px; height: 100px; background-color: #000; } #div2 { visibility: hidden; opacity: 0; width: 100px; height: 100px; background-color: #F00; transition-delay: 0.5s; } &:hover { #div2 { visibility: visible; opacity: 1; } } }
通過在CSS中添加transition-duration
,我們可以實現延時顯示的目的。並且,我們不需要自己去清除定時器,而由瀏覽器來判別。
在此,我們爲什麼不用display
屬性呢,因爲在display
改變時,transition
並不會生效。
那我們爲什麼需要在使用了opacity
屬性的時候同時使用visibility
屬性呢。因爲opacity
屬性只是讓元素變得透明,而不會讓元素消失。如果不加速visibility
屬性的話,那元素變透明後仍然可以點擊,那麼會出現一些奇怪的影響。
到目前爲止,我們利用CSS完全模擬了第一部分我們使用JavaScript實現的功能,而且看上去更簡潔。那麼,下面我們來講一些更加複雜的功能有助於大家理解transition
。
transition進階
現在我們需要在鼠標移動上去後,出現一個漸變的效果,讓另一框慢慢出現,同時在鼠標移出的時候也有漸變消失的效果,那麼我們就需要來使用一下transition
的另外幾個屬性。
在上面的代碼中稍加改動,就能夠得到我們需要的效果。
transition: opacity 0.5s linear;
這樣的話,在鼠標移入的時候,會有一個漸變的效果。但是,問題也出現了,在鼠標移出的時候,div2
立馬就消失了。讓我們來分析一下鼠標移入和移出時的效果。
當鼠標移入時:
- 鼠標移入
div1
元素 - 觸發了
hover
事件,div2的visibility
屬性變爲visible
transition
屬性讓opacity
屬性從0變爲1
而當鼠標移出時:
- 鼠標移出
div1
元素 hover
事件停止觸發,div2的visibility
屬性變爲hidden
transition
屬性讓opacity
屬性從1變爲0
根據上面的情況我們知道,當hover
事件結束後,visibility
屬性立馬就變成了hidden
了,因此後面的動畫效果就無法看到了。
那麼如果我們爲visibility
屬性也加上延時呢,能不能夠達到我們的目標,讓我們來看一下效果。
transition: visibility 0s linear 0.5s, opacity 0.5s linear;
代碼改動爲如上情況後,我們會發現,當鼠標移出的時候,能夠看到動畫效果。但是當鼠標移入時,動畫效果消失了,現在再讓我們來分析下爲什麼會出現這個情況。
當時鼠標移入時:
- 鼠標移入
div1
元素 - 觸發
hover
事件 transition
屬性讓opacity
屬性從0變爲1visibility
屬性變爲visible
當鼠標移出時:
- 鼠標移出
div1
元素 hover
事件停止觸發transition
屬性讓opacity
屬性從1變爲0visibility
屬性變爲hidden
從上面的分析我們可以知道,因爲visibility
屬性爲不連續變化屬性,因此在transition
中只有transition-delay
對該屬性產生了效果。所以visibility
屬性延時了0.5s執行,導致了在鼠標移入時看不到效果。
那麼,我們有沒有辦法同時在鼠標移入和移出的時候同時看到動畫效果呢。需要達到這個目的,其實換一個思路立馬就能夠解決。我們不只需要在hover
事件中重置這個延時,將其重新指定爲0,馬上就能夠達到我們想要的效果。具體示例代碼如下:
#container { display: inline-block; #div1 { width: 100px; height: 100px; background-color: #000; } #div2 { visibility: hidden; opacity: 0; width: 100px; height: 100px; background-color: #F00; transition: visibility 0s linear 0.5s, opacity 0.5s linear; } &:hover { #div2 { visibility: visible; opacity: 1; transition-delay: 0s; } } }
那麼現在讓我們來分析下爲什麼這麼寫能夠達到我們需要的效果。
當鼠標移入時:
- 鼠標移入
div1
hover
事件觸發,重新指定transition
屬性的transition-delay
爲0s,visibility
屬性由hidden
立馬變成visible
transition
屬性讓opacity
屬性由0變爲1
當鼠標移出時:
- 鼠標移出
div1
hover
事件停止觸發,transition
延時恢復到0.5s,因此visibility
屬性不會馬上觸發transition
屬性讓opacity
屬性由1變爲0visibility
屬性由visible
變爲hidden
我們通過一個很簡單巧妙的方法,通過改變transition-delay
,從而讓visibility
屬性立即執行,達到了我們需要的效果。
JavaScript
附上利用JS來實現該方案的代碼用於參考。這個其實應該是大部分人會想到的方法,利用mouseover
或者click
事件來進行事件的監聽,利用setTimeout
來進行延時處理,例如這樣:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> #div1 { width: 100px; height: 100px; background-color: #000; } #div2 { display: none; width: 100px; height: 100px; background-color: #F00; } </style> </head> <body> <div id="div1"></div> <div id="div2"></div> <script> document.addEventListener('DOMContentLoaded', function() { var div1 = document.getElementById('div1'); var div2 = document.getElementById('div2'); div1.addEventListener('mouseover', function() { setTimeout(function() { div2.style.display = 'block'; }, 500); }); div1.addEventListener('mouseleave', function() { setTimeout(function() { div2.style.display = 'none'; }, 500); }); }); </script> </body> </html>
但是上面這個代碼有一個比較嚴重的問題,就是當鼠標兩次移動上去的間隔小於500ms時,上一次的setTimeout
的代碼還是會觸發,因此會看到一次閃動的效果。
因此,我們需要在檢測到兩次間隔小於500ms時,清除掉上次的setTimeout
的代碼。所以,改動代碼如下(注:如有表現與描述不一致請看文章末尾說明):
div1.addEventListener('mouseover', function() { clearTimeout(handle); setTimeout(function() { div2.style.display = 'block'; }, 500); }); var handle = 0; div1.addEventListener('mouseleave', function() { handle = setTimeout(function() { div2.style.display = 'none'; }, 500); });
調整爲上述代碼之後,基本可以滿足我們的需求。後續如果需要添加動畫之類的操作,也只需要繼續像代碼添加相關邏輯即可,在此就不再演示。
總結
在需求開發的過程中,遇到了這個問題。最開始用JavaScript實現,開發起來比較複雜,容易與業務邏輯代碼混在一起不好維護。通過CSS來實現這個功能,既簡單高效,又能夠避免增加JavaScript複雜度,是一個比較優的解決方案。
參考
說明
jsbin是一個在線的編輯器,能夠在代碼編寫完成後馬上看到效果,但是該文中有部分代碼在jsbin中出現表現與本地不一致的情況(例如CSS進階中最後一個代碼),大家可以將代碼放到本地驗證。由於代碼效果時好時壞,猜測可能與jsbin的容器相關。