元素顯隱切換過渡效果的實現

近來看到 餓了麼 App和 h5站上,在商家詳情頁點餐以後,底部放置了一個點擊以後可以彈出模態框查看點餐詳情的元素,其中有個背景遮罩層的漸進顯隱的效果。javascript

 

憑着我少量的經驗,第一時間的想法是以爲這個遮罩層應該是使用 display:none;來控制隱藏和顯示的,可是這個屬性會破壞 transition動畫,也就是說若是遮罩層是使用了這個屬性來控制顯示與隱藏,那麼漸進顯隱的效果彷佛很難達到,效果應該是瞬間顯示與隱藏纔對。css

使用 Chrome 模擬移動端,查看了一下 餓了麼的實現方式,這纔想到 餓了麼用到了 vue,此動畫效果實際上是利用了 vue自帶的過渡動畫和鉤子函數實現的。vue


框架實現

基於 vue的動畫漸隱實現

利用框架實現這種效果然的是 so easy,不逼逼上代碼。java

// HTML
<div id="app">
  <button class="btn" @click="show = !show">click</button>
  <transition name='fade'>
    <div class="box1" v-if="show"></div>
  </transition>
</div>

// CSS
.box1 {
 width: 200px;
 height: 200px;
 background-color: green;
}

.fade-enter-active, .fade-leave-active {
 transition: opacity .5s
}
.fade-enter, .fade-leave-to{
 opacity: 0;
}

 

 

無圖無真相,看看效果助助興:react

 

 

 

簡直不能更簡單jquery

基於 react的動畫漸隱實現

react自己的單獨庫沒有自帶過渡動畫,不過有個 Animation Add-Ons: react-addons-css-transition-groupweb

import React, {Component} from 'react'
import ReactDOM from 'react-dom'
import ReactCSSTransitionGroup from 'react-addons-css-transition-group'

class TodoList extends React.Component {
 constructor(props) {
super(props)
this.state = {
     show: true
}
}

 render() {
return (
<div>
  <button onClick={this.changeShow.bind(this)}>click</button>
  <ReactCSSTransitionGroup
         component="div"
         transitionName="fade"
         transitionEnterTimeout={500}
         transitionLeaveTimeout={300}>
    {
      this.state.show &&
      <div className="box1">
      </div>
    }
  </ReactCSSTransitionGroup>
</div>
)
}

 changeShow() {
this.setState({
     show: !this.state.show
})
}
}

 

 

樣式以下:瀏覽器

.box1 {
 width: 100px;
 height: 100px;
 background-color: green;
 transition: opacity .5s;
}
.fade-leave.fade-leave-active, .fade-enter {
 opacity: 0;
}

.fade-enter.fade-enter-active, .fade-leave {
 opacity: 1;
}

 

 

依舊是很 easyapp

 

 


原生實現

以上都是框架實現,但若是項目歷史悠久,根本就沒用到這些亮瞎人眼的框架,充其量用了個 1.2版本的 jquery,那麼上面方法可都用不到了,我但願找到一種通用的原生方式,不利用任何框架。框架

visibility 代替 display

其中一種方案如題所示,由於 visibility這個屬性一樣可以控制元素的顯隱,並且, visibility屬性在值 visible 與 hidden的來回切換中,不會破壞元素的 transition 動畫。

不過 visibility與 display 之間控制元素顯隱的最終效果仍是有些差異的。

設置了 visibility:hidden; 的元素,視覺上確實是不可見了,可是元素仍然佔據該佔據的位置,仍然會存在於文檔流中,影響頁面的佈局,只不過設置了此屬性的元素在視覺上看不到,在頁面的原位置上留下一片空白而已(若是此元素具備寬高而且使用默認定位)。

而設置了 display:none;的元素,其既視覺上不可見,同時也不會佔據空間,也就是說已經從文檔流中消失了。

visibility控制元素顯隱一樣是瞬時發生的,不過這種瞬時發生的狀況又和 display的那種瞬時發生不太同樣, display是根本不會理會設置的 transition過渡屬性,設置了也和沒設置同樣。

但 visibility是有可能會理會這個值的,不過只理會 過渡時間 transition-duration這個屬性。

例如,從 visibility:hidden到 visibility:visible;變化時,若是設置了過渡時間爲 3s,那麼在事件發生後,元素並不會當即呈現出從 hidden到 visible的效果,而是會像下圖那樣,先等待 3s,而後再瞬間隱藏,從顯示到最終消失視線中的時間確實 3s,只不過並非逐漸過渡出現的。

 

 

上圖彷佛有個問題,從顯示到隱藏確實是等待了 3s,但從隱藏到顯示,好像仍是瞬間完成的,並無等待 3s的說法。

視覺上確實是這樣,不過這也只是視覺上的感受而已,實際上這個等待時間真實存在的,只是看不到而已,另外,這裏的 等待也不是真的什麼都沒作純粹的 等待。

之因此 display會破壞 transition動畫,有個說法是,由於 transition對元素在整個過渡過程當中的狀態控制,是根據元素過渡先後起始的狀態來計算得出的,例如從 opacity:0 到 opacity:1的變化,用時 3s,那麼 transition會計算在這 3s內的每一幀畫面中元素該有的 opacity值,從而完成過渡效果,其餘的一些屬性,例如 width、 scale、 color等均可以轉化爲數字進行計算 (說明文檔參見), 但 display是個尷尬的屬性,從 display:none到 display:block 該怎麼計算值呢?

計算不了,因此就只能 破壞了, visibility一樣如此,只不過 visibility比 display稍好一點,由於最起碼 visibility不會破罐子破摔,不會搞破壞。

從 visibility:hidden到 visibility:visible的過程當中。由於沒辦法計算過渡階段沒幀的值,因此元素就直接顯示出來了,但內在的過渡操做依舊在元素顯示出來後顯示了 3s,而從 visibility:visible 到 visibility:hidden,元素在視覺上看起來等待的 3s內,實際在內部已經在進行 transition過渡操做,只不過仍是由於沒辦法計算值,因此到了過渡階段的最後一刻時,就直接將元素設置爲結束狀態,也就是隱藏了。

想要驗證這種說法,還須要配合另一個屬性: opacity,此屬性也是配合 visibility完成過渡效果的搭配屬性。

實現代碼以下

// HTML
<button class="btn">click</button>
<div class="box1"></div>

 

 

// CSS
.box1 {
 width: 200px;
 height: 200px;
 background-color: green;

 opacity: 0;
 visibility: hidden;
 transition: all 2s linear;
}
.show {
 opacity: .6;
 visibility: visible;
}

 

 

js控制顯隱效果代碼以下:

let box1 = document.querySelector('.box1')
let btn = document.querySelector('button')
btn.addEventListener('click', ()=>{
let boxClassName = box1.className
 boxClassName.includes('show')
? box1.className = boxClassName.slice(0, boxClassName.length-5)
: box1.className += ' show'
})

 

 

效果依舊沒問題:

 

 

由於雖然 visibility沒辦法計算值,但 opacity能夠,過渡的效果其實是 opacity在起做用。

其實 opacity自己就能控制元素的顯隱,把上面代碼中的全部 visibility 所有刪除,效果依舊不變,而且和 visibility 同樣,設置了 opacity:0; 的元素依舊存在於文檔流中, but,相比於 visibility:hidden, opacity:0 的元素並不會出現點透。

而 visibility:hidden的元素就會出現點透,點擊事件會穿透 visibility:hidden的元素,被下面的元素接收到,元素在隱藏的時候,就不會干擾到其餘元素的點擊事件。

關於這個說法,彷佛網上有些爭論,可是我用迄今最新版的 Chrome Firefox 以及 360瀏覽器 進行測試, 都是上面的結果。

若是你只是想讓元素簡單的漸進顯隱,不用管顯隱元素會不會遮擋什麼點擊事件之類的,那麼徹底能夠不用加 visibility 屬性,加了反而是自找麻煩,可是若是須要考慮到這一點,那麼最好加上。


setTimeOut

若是不使用 visibility的話還好,可是若是使用了此屬性,那麼上述的解決方案其實還有點小瑕疵,由於 visibility從 IE10以及 Android4.4纔開始支持,若是你須要支持這種版本的瀏覽器,那麼 visibility 就派不上用場了。

 

 

哎呦呦,公司網站最低要求都是 IE9,用不了了誒。

怎麼辦?再回到 display 這個屬性上。

爲何 display 這個屬性會影響到 transition 動畫的緣由上面已經大體說了下,既然問題是出在了 display上,那麼我能夠一樣參考上面 visibility的作法,加個 opocity屬性進行輔助,又由於考慮到 display 比起 visibility 來講破壞性較大,因此再讓 opocity 與 display 分開執行不就好了嗎?

你若是寫成這種形式:

box1.style.display='block'
box1.style.opacity=1

 

 

其實仍是沒用的,儘管 display值的設定在代碼上看起來好像是在 opacity前面,可是執行的時候倒是幾乎同時發生的。

個人理解是應該是瀏覽器對代碼進行了優化,瀏覽器看到你分兩步爲同一個元素設置 CSS屬性,感受有點浪費,爲了更快地完成這兩步,它幫你合併了一下,放在一個 tick(參見 [ ( ] )內執行,變成一步到位了,也就是同步執行了這兩句代碼。

那麼如何明確地讓瀏覽器不要合併到一個 tick內執行呢? setTimeOut就派上了用場。

setTimeOut 一個重要功能就是延遲執行,只要將 opacity屬性的設置延遲到 display後面執行就好了。

// CSS
.box1 {
 width: 200px;
 height: 200px;
 background-color: green;

 display: none;
 opacity: 0;
 transition: all 2s linear;
}

 

 

下面是控制元素漸進顯示的代碼:

// JS
let box1 = document.querySelector('.box1')
let btn = document.querySelector('.btn')
btn.addEventListener('click', ()=>{
let boxDisplay = box1.style.display
if(boxDisplay === 'none') {
   box1.style.display='block'
   setTimeout(()=> {
     box1.style.opacity = 0.4
})
}
})

 

 

上述代碼中,最關鍵的就是 setTimeOut 這一句,延遲元素 opacity屬性的設定。

setTiomeOut的第二個可選的時間 delay參數,我在最新版的 Chrome和 360 瀏覽器上測試,此參數能夠不寫,也能夠寫成 0或者其餘數值,可是在 firefox上,此參數必須寫,否則漸進效果時靈時不靈,並且不能爲 0,也不能過小,我測出來的最小數值是 14,這樣才能保證漸進效果,因此爲了兼容考慮,最好仍是都統一加上時間。

至於爲何是 14,我就不清楚了,不過記得之前看過一篇文章,其中說 CPU可以反應過來的最低時間就是 14ms,我猜可能與這個有關吧。

顯示的效果有了,那麼要隱藏怎麼辦? setTimeOut 固然也能夠,在 JS代碼的 if(boxDisplay==='none')後面再加個 else

else {
  box1.style.opacity = 0
  setTimeout(()=>{
    box1.style.display = 'none' 
}, 2000)
}

 

隱藏時先設置 opacity,等 opacity過渡完了,再設置 display:none;。

可是這裏有點不太合理,由於雖然 setTimeOut的 delay參數 2000ms和 transition 時間 2s同樣大,但由於 JS是單線程,遵循時間輪詢,因此並不能保證 display屬性的設置恰好是在 opacity過渡完了的同時執行,可能會有更多一點的延遲,這取決於過渡動畫完成之刻, JS主線程是否繁忙。

固然,就算是延遲,通常也不會延遲多長時間的,人眼不太可能感受獲得,若是不那麼計較的話其實徹底能夠無視,可是若是我就吹毛求疵,要想作到更完美,那怎麼辦?


transitionend

transition 動畫結束的時候,對應着一個事件: transitionend,MDN [  ] 上關於此事件的詳細以下:

transitionend 事件會在 CSS transition 結束後觸發. 當 transition完成前移除 transition時,好比移除 css的 transition-property 屬性,事件將不會被觸發,如在 transition完成前設置 display:none,事件一樣不會被觸發。

若是你可以使用 transition,那麼基本上也就可以使用這個事件了,只不過此事件須要加前綴的瀏覽器比較多(如今最新版的全部 主流瀏覽器,都已經不用寫前綴了),大體有以下寫法:

transitionend
webkitTransitionEnd
mozTransitionEnd
oTransitionEnd

 

 

使用此屬性,就能夠避免上面 setTimeOut可能出現的問題了 ,使用示例以下:

// ...
else {
 box1.style.opacity = 0
 box1.addEventListener('transitionend', function(e) {
   box1.style.display = 'none'
});
}

 

 

須要注意的是, transitionend 事件監聽的對象是全部 CSS中transition屬性指定的值,例如,若是你爲元素設置了 transition:all3s;的 樣式,那麼元素可能不管是 left top仍是 opacity 的改變,都會觸發該事件,也就是說此事件可能會被觸發屢次,而且並不必定每次都是你想要觸發的,針對這種狀況,最好加一個判斷。

既然是 涉及到了 JS實現的動畫,那麼其實能夠考慮一下 把 setTimeout換成 requestAnimationFrame。

btn.addEventListener('click', ()=>{
let boxDisplay = box1.style.display
if(boxDisplay === 'none') {
   box1.style.display='block'
// setTimeOut 換成 requestAnimationFrame
   requestAnimationFrame(()=> {
     box1.style.opacity = 0.6
})
} else {
  box1.style.opacity = 0
  box1.addEventListener('transitionend', function(e) {
    box1.style.display = 'none'
});
}
})

 

 

文章最開始說過的 vue 和 react這兩個框架實現示例動畫的方法,也利用到了這個 API,,監聽動畫過渡的狀態,爲元素添加和刪除一系列過渡類名的操做,固然,並非所有,此事件只能監聽動畫結束的這個時刻,其餘時間點是沒法監聽的。

  • 如下爲 transitionEnd 在 react-addons-css-transition-group源碼裏面出現的形式:

 

 

react-addons-css-transition-group對 transitionend作了兼容,若是瀏覽器支持此屬性,則使用,若是不支持,就使用 setTimeOut這種形式。

  • 如下爲 transitionEnd 在 vue源碼裏面出現的形式:

 

 

另外,順帶一提的是,除了 transitionend 事件,還有一個 animationend [  ] 事件,此事件是對應 animation動畫, react-addons-css-transition-group 和 vue中也都對應着 transitionend 出現了此屬性的身影,這裏就不展開了。

 

若是你喜歡咱們的文章,關注咱們的公衆號和咱們互動吧。

 

 

 

咱們是轉轉FE團隊,歡迎你們關注公衆號 大轉轉FE 。更多的瞭解咱們
相關文章
相關標籤/搜索