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

来源:http://www.cnblogs.com/zhuanzhuanfe/archive/2017/09/26/7596389.html
-Advertisement-
Play Games

近來看到 餓了麽 App和 h5站上,在商家詳情頁點餐之後,底部放置了一個點擊之後能夠彈出模態框查看點餐詳情的元素,其中有個背景遮罩層的漸進顯隱的效果。 憑著我少許的經驗,第一時間的想法是覺得這個遮罩層應該是使用 display:none;來控制隱藏和顯示的,但是這個屬性會破壞 transition ...


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

 

憑著我少許的經驗,第一時間的想法是覺得這個遮罩層應該是使用 display:none;來控制隱藏和顯示的,但是這個屬性會破壞 transition動畫,也就是說如果遮罩層是使用了這個屬性來控制顯示與隱藏,那麼漸進顯隱的效果似乎很難達到,效果應該是瞬間顯示與隱藏才對。

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


框架實現

基於 vue的動畫漸隱實現

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

// 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的動畫漸隱實現

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

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

 

 

依舊是很 easy

 

 


原生實現

以上都是框架實現,但如果項目歷史悠久,根本就沒用到這些亮瞎人眼的框架,充其量用了個 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 。更多的瞭解我們
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本篇文章是關於覆選框的,有2種形式:1、全選、反選由2個按鈕實現;2、全選、反選由一個按鈕實現。 在實踐中碰到一個問題——check全選失效。解決辦法,使用prop方法代替attr。 這或許是和jQuery版本有關。 ...
  • 上文,寫完弧度與貝塞爾曲線[js高手之路] html5 canvas系列教程 - arcTo(弧度與二次,三次貝塞爾曲線以及線上工具),本文主要是關於線條的樣式設置 lineWidth: 設置線條的寬度,值是一個數值,如lineWidth = 5. 畫3條不同寬度的線條: lineWidth設置弧形 ...
  • 調試代碼之前,我設置了兩個緩存 分別是username和content 在控制台console設置兩個緩存代碼 localStorage.setItem('username','老王')localStorage.setItem('content','類容') 運行下麵代碼一定要先設置這兩個緩存,因為 ...
  • 自己寫了個上傳圖片的子組件,父組件需要獲取到子組件上傳的圖片地址,這裡最好的辦法是給相應的子組件加ref, 父組件在最後提交的時候獲取thi.$ref.avatar.相應數據即可,因為在這裡才能保證圖片已經上傳,否則如果圖片沒上傳,拿到的值一定為空。 ...
  • HTML5學堂-碼匠:本期繼續走入演算法 —— 冒泡排序法。冒泡排序演算法相對簡單,容易上手,穩定性也比較高, 算是一種較好理解的演算法,也是面試官高頻提問的演算法之一。 ...
  • 之前,我寫了一個arc函數的用法:[js高手之路] html5 canvas系列教程 - arc繪製曲線圖形(曲線,弧線,圓形). arcTo: cxt.arcTo( cx, cy, x2, y2, 半徑 ) cx,cy表示控制點的坐標,x2,y2表示結束點的坐標,如果我們想畫一條弧線,需要提供3個 ...
  • Web前端開發規範文檔你需要知道的事 規範目的 為提高團隊協作效率, 便於後臺人員添加功能及前端後期優化維護, 輸出高質量的文檔, 特製訂此文檔. 本規範文檔一經確認, 前端開發人員必須按本文檔規範進行前臺頁面開發. 本文檔如有不對或者不合適的地方請及時提出, 經討論決定後方可更改. 基本準則 符合 ...
  • 藥藥,切克鬧,一人我編碼累,累把那bug寫成堆。秋高氣爽空氣乾燥你一定dei多喝水,過完了這周我就要回去、趁還有幾天、你盡情的來跟我懟~~~ 新的一年,很久沒更博客了,眼看十一要來了,聽說過了十一就等過年了,但是感覺剛過完年一樣,心裡黯然神傷,更博一篇以表對小白駒過隙之神速聊以慰藉下…… 在開發中這 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...