關(guān)于具體解決方法或如何規(guī)避問題的意見可以參考另一篇非常有見地的文章,“all you need to know about css transitions”。alex maccaw講述的是關(guān)于實現(xiàn)特定的效果,而我要談的是技術(shù)背景,主要討論在使用css過渡的過程中所未預(yù)料到的問題。
結(jié)構(gòu) (html),表現(xiàn)(css),以及行為(javascript)相分離并不是什么新鮮的事情,然而 css 能跨越這個界限并且可以在短期內(nèi)得到實際的應(yīng)用,這還真的是一個完全不同的討論話題。
幾周前,我開發(fā)一個 javascript 模塊,在能夠使用 css 過渡的條件下,javascript 端又無法獲取到實現(xiàn)過渡的方式。實際遇到的問題是這兩者根本沒有辦法同步,經(jīng)過多次的測試后,我只能放棄。而我的測試結(jié)果正是本文所講述的。
首先,我們要說一下getcomputedstyle(),是一種用 javascript 返回瀏覽器渲染css的屬性值的方法。 這個方法可以查看“dom level 2: getcomputedstyle()”和“css level 2: computed values”。
這對于像 font-size 這樣的屬性, 通過一個參數(shù)便可以轉(zhuǎn)換為像素值。 但對于可以縮寫的屬性值,例如 margin ,一些瀏覽器則返回為空。再就是那些同一屬性的不同屬性值,例如 font-weight 的值 bold 和700。webkit也有一個小bug,它會從偽對象中提取出屬性值。
這里所講述的瀏覽器之間的差異是2013年1月在使用 firefox18(gecko),opera 12.12 (presto), internet explorer10(trident),safari 瀏覽器6.0.2(webkit),chrome 23(webkit) 以及 gecko 和 webkit的 nightly build channels。
事不宜遲,讓我們來一起看一下規(guī)范與實際情況的差異,為了方便,我省略了各瀏覽器的前綴。在文中我通過創(chuàng)建一個 css3 transitions test suite 來發(fā)現(xiàn)問題。
1、指定過渡
css3 transitions 規(guī)范定義了以下四個 css 屬性:
transition-property
transition-duration
transition-delay
transition-timing-function
過渡屬性
transition-property 是用來指定當(dāng)元素其中一個屬性改變時執(zhí)行 transition 效果。系統(tǒng)默認(rèn)值是 all,這意味著瀏覽器能夠以動畫形式呈現(xiàn)所有的可過渡屬性(transition-duration持續(xù)時間超過0s),該屬性支持單個值或以逗號隔開的多個值列表(跟其他所有transition-*屬性一樣)。
規(guī)范規(guī)定,一個瀏覽器應(yīng)該接受并保存任何它不能識別的屬性。因此,下面的例子中將會看到持續(xù)2秒的 padding 過渡:
1
2
transition-property:foobar,padding;
transition-duration:1s,2s;
不同于規(guī)范的是,上面的情況在 webkit 下會解析為 transition-property: all。 而 firefox 和 opera 會解析為 transition-property: all, padding.
過渡持續(xù)時間
transition-duration 屬性規(guī)定了一個過渡從初始狀態(tài)到目標(biāo)狀態(tài)的持續(xù)時間。它接受以秒或毫秒的值(例如,2.3s和2300ms都是指2.3秒)。
盡管規(guī)范明確規(guī)定了過渡值必須為正數(shù),但 opera 仍接受-5s的值,至少對于getcomputedstyle()來說是這樣的。雖然規(guī)范中并沒有限制屬性值的大小,但 opera 和 ie 不接受低于10ms的值。而 webkit 在 getcomputedstyle()執(zhí)行中有個小bug,例如:返回值0.009999999776482582s會取代0.01s。
過渡延遲時間
transition-delay 屬性規(guī)定了在執(zhí)行一個過渡之前的等待時間,同樣使用值。delay 可以是負(fù)值,但這會導(dǎo)致動畫無法平滑過渡。
ie 和 opera 不接受 transition-duration 在-10ms和10ms之間的值。webkit 的 floating point 也會在這兒出現(xiàn)。
transition-timing-function 屬性規(guī)定了過渡效果的時間曲線。包括cubic-bezier(x1, y1, x2, y2), step(, start|end),和預(yù)先定義的 cubic-bezier 曲線關(guān)鍵詞,linear, ease, ease-in, ease-out和ease-in-out。在使用 lea verou 特有的 cubic-bezier 曲線編輯器時,cubic-bezier 背后的公式就變得不再重要。盡管 cubic-bezier 曲線會平滑過渡,但是step()函數(shù)會在一個固定的間隔跳到下一個值。這樣便會產(chǎn)生逐幀動畫的效果;如“pure css3 typing animation with steps()”。
linear 的計算值通常表示為 cubic-bezier(0, 0, 1, 1)—— webkit除外。但 webkit 仍然會返回 cubic-bezier(0.25, 0.1, 0.25, 1),而不是 ease。規(guī)范規(guī)定 x 值的必須介于0和1之間,y 值可以超過該范圍,而webkit 允許 x 超過此范圍,而 android 瀏覽器(4.0版本)卻混淆了x和y的范圍。
2 過渡完成
我前面已經(jīng)提到了 css 過渡異步運行的問題。規(guī)范提及了 transitionend 事件允許 javascript 與已完成的過渡同步進(jìn)行。但可惡的是該規(guī)范對此并沒具體闡述。事實上,它只是簡單地說明單個事件會因為已完成過渡的屬性而被終止。
規(guī)范指出縮寫屬性(如padding)應(yīng)為包括其在內(nèi)的所有屬性(padding-top,padding-right,等等)實現(xiàn)過渡,它并沒有說哪個屬性應(yīng)該在 transitionend 事件中被具體命名。然而即使過渡被定義為縮寫屬性(如padding),gecko,trident 和 presto 對于普通書寫的子屬性(如padding-top)同樣可以實現(xiàn)過渡,而 webkit 則會阻止過渡。 如果你指定 transition-property: padding,webkit 會為 padding 執(zhí)行過渡, 但 transition-property: all 這樣就會針對 padding-left 執(zhí)行新的過渡。而當(dāng) padding 正執(zhí)行過渡時, iphone 6.0.1 的 safari 瀏覽器在也可以執(zhí)行 font-size 和 line-height的過渡。
1
2
.example{padding:1px;transition-property:padding;transition-duration:1s;}
.example:hover{padding:10px;}
以上 css 將在不同瀏覽器下觸發(fā)不同的 transitionend:
gecko,trident,presto:
padding-top,padding-right,padding-bottom,padding-left
webkit:
padding
1
2
.example {padding: 1px;transition-property: all, padding;transition-duration:1s;}
.example:hover{padding:10px;}
以上 css 將在不同瀏覽器下觸發(fā)不同的transitionend:
gecko,trident,presto,webkit:
padding-top,padding-right,padding-bottom,padding-left
safari 6.0.1 on iphone:
padding-top, padding-right, padding-bottom, padding-left, font-size, line-height
你可以指定負(fù)值 transition-delay 來“快速實現(xiàn)”轉(zhuǎn)換。但是transition-duration: 1s; transition-delay: -1s; 在 gecko 和 webkit 下執(zhí)行轉(zhuǎn)換并會立即跳轉(zhuǎn)至目標(biāo)值。而trident 和 presto 將不會觸發(fā)任何事件。
webkit在 getcomputedstyle() 上遇到的浮點問題也同樣存在于 transitionend.elapsedtime 中,所有的瀏覽器如此。 math.round(event.elapsedtime * 1000) / 1000 可輔助修復(fù)。
webkit 和 ie 瀏覽器下執(zhí)行 background-position,會觸發(fā)對 background-position-x 和 background-position-y 的 transitionend,而不是 background-position 的transitionend。
所以,即使你知道過渡正在執(zhí)行,你也不能依賴已有的 transitionend.propertyname。盡管你可以編寫大量的 javascript 來彌補,但在沒有對每一個屬性進(jìn)行恰當(dāng)性能檢測的情況下,即使你采用最新方法也將無法實現(xiàn)。
3 過渡屬性
規(guī)范列出了瀏覽器支持動畫過渡的一些css屬性。當(dāng)然也包括css2.1的屬性。還有一些可以動態(tài)變化的新屬性,如 flexible box layout。
該屬性數(shù)值類型非常重要。margin-top 接受和值,但根據(jù)可過渡css屬性列表,只有是可實現(xiàn)動畫效果。但這并不能讓瀏覽器開發(fā)商避開值實現(xiàn)過渡。然而,word-spacing 屬性除外。該屬性包括值,但沒有瀏覽器能以動畫形式顯示。
撇開 transitionend 事件,如果在過渡發(fā)生的指定時間內(nèi),getcomputedstyle()值從a變到b,該屬性就會從值a過渡為值b。如果沒有執(zhí)行,例如“css屬性值發(fā)生變化”,那么也許應(yīng)該仔細(xì)核查下dom。settimeout()的解析度還不夠好以達(dá)到快速過渡(小于幾百毫秒的持續(xù)時間),這時候requestanimationframe()就是你的幫手。在重繪前會提醒你,并提供了一些中間值供參考。除了opera,其他的都可以支持。
4 過渡屬性的優(yōu)先級
transition-property 規(guī)范允許多次過渡單個屬性,如果單個屬性在“過渡屬性”中的值被多次指定,過渡將通過持續(xù)時間,延遲和時間曲線給出的值來實現(xiàn)。因此,我們可以實現(xiàn) padding 過渡持續(xù)1秒,padding-left 過渡持續(xù)2秒; 或使用 transition-property: all 來定義默認(rèn)屬性類型并重置特定屬性。
在 firefox 和 ie 瀏覽器上,這些都沒有任何問題。 但 opera下會混淆優(yōu)先順序。它認(rèn)為 padding-left 比padding 和 all 更加具體,而不是簡單地使用最后一個屬性。
最大的問題是webkit。如果一個屬性被多次指定,它將進(jìn)行多次過渡。 如果想讓webkit崩潰,嘗試用transition-duration :0.1秒運行transition-property: padding, padding-left,webkit將至少執(zhí)行兩次過渡。但更有意思的是,transitionend可以進(jìn)行上百次的單一過渡。
5 auto的轉(zhuǎn)變
css 屬性中的 auto 值能夠自適應(yīng)寬度,如果塊級元素設(shè)置了width: auto,那么就會繼承父級的寬度。有時你需要從 width: auto 改變到一個具體寬度,并且需要過渡那個改變。當(dāng)然本規(guī)范并沒有強(qiáng)制或否定 auto 值可用于過渡。
firefox,ie 和 opera 無法從 or 值過渡到 auto 值。 ie 下有 z-index 有一點點例外,但僅此而已。 另一方面,webkit 能夠從and 過渡到幾乎可以接受 auto 值的任意css 屬性。webkit 不太喜歡 clip;因為這個屬性,它只會引發(fā) transitionend 過渡,而過渡期間不會產(chǎn)生或顯示任何中間值或狀態(tài)。
對于其他屬性,如 width 和 height,webkit 下會有一些差異。如果 width: auto 過渡為 300px 的寬度,然后再過渡成 100px,那么過渡不會從 300 縮至100 像素。它會從 0 增加到 100 像素。
關(guān)于完整的兼容性列表,可以查看“css animatable properties.”
6 隱式過渡
隱式過渡發(fā)生在當(dāng)一個屬性的改變引起另一個屬性被過渡的時候, 或者當(dāng)你想改變一個父級元素中的屬性, 會導(dǎo)致子元素不論是繼承過渡或附屬屬性的過渡。font-size: 18px, padding: 2em—–padding 會被計算為 2 × font-size, em 就是36像素。
有各種各樣的相對值類型:, , em, rem, vh, vw等等。使用一個相對值,如 padding: 2em,讓瀏覽器重新計算屬性的 getcomputedvalue(),每次應(yīng)變量(如font-size)都會發(fā)生改變。由于計算樣式改變,將反過來導(dǎo)致 padding 的過渡。這種過渡被定義為“隱式過渡”,因為padding屬性值沒有被修改。
大多數(shù)瀏覽器會實現(xiàn)這種隱式過渡。除了 ie 10,只對 line-height 屬性執(zhí)行隱式過渡。除了 vertical-align 外,webkit 可以針對其他所有屬性執(zhí)行隱式過渡。除了字體相對屬性值,還有寬度相對屬性值(通常為),相對屬性值(如vh和vw),默認(rèn)初始值(opera中的column-gap: 1em),還有“currentcolor”。所有這些都有可能會,也可能不會引起隱式過渡。
在 firefox 中, 當(dāng)繼承和附屬屬性執(zhí)行過渡,但他們的 transition-duration 或 transition-delay 并沒有隨著過渡, 這些隱式過渡就會變得特別有趣。 而 webkit 和 opera 執(zhí)行過渡時會很有視覺感,但 firefox 會稍顯錯亂。在ie中不會輕易執(zhí)行隱式過渡。
另外,別忘了繼承, dom 元素的 font-size 將會由其子元素繼承,只要不被覆蓋,就可能引起隱式過渡。
7 轉(zhuǎn)換和偽元素
偽元素(:before和:after),在 css2 中已經(jīng)有了介紹. 如果不熟悉可以查看 “l(fā)earning to use the :before and :after pseudo-elements in css”。 雖然 css3 中定義了額外的偽元素(::alternate,::outside),但是他們(到目前為止)還并沒有被支持。因此所有 css 動畫屬性也應(yīng)該是偽元素的動畫屬性。
firefox 和 ie 10 可以在偽元素上實現(xiàn)屬性過渡. 而 opera,chrome 和 safari 則不會。 webkit 從2013年一月起也開始支持。
偽元素的過渡會導(dǎo)致內(nèi)容自身產(chǎn)生一些新問題,因為在生成內(nèi)容時 transitionend 過渡根本還沒有結(jié)束。 在某一時間段內(nèi),他們理應(yīng)在主元素上被觸發(fā),并通過 transitionend.pseudoelement 提供偽元素,但即便是“css動畫過渡”的“過渡事件”部分,編寫者的方案也并沒有指定哪一個最合適。
我們想要改變 content 屬性值,因此ie 8將在特殊情況下(比如:hover狀態(tài))將會重新渲染該元素。結(jié)果表明,對老的ie版本進(jìn)行兼容會影響到所有其他瀏覽器的效率。所以, 當(dāng)試圖在偽元素上進(jìn)行屬性過渡時,要確保 content 的值不會被改變。
如果主元素沒有運行:hover狀態(tài),那么 ie 10 將不針對偽元素“:hover”執(zhí)行過渡。
1
2
.some-selector:before{content:hello;color:red;transition:all 1s linear 0s;}
.some-selector:hover:before{color:green;}
在 ie10 下,:before在 mouseover 的時候,:hover 是一定要定義的。
這個問題在于不是一定要求你定義主元素:hover 狀態(tài)。而是如果沒有定義,ie 10 會將:hover解釋為:active。更奇怪的是,:active狀態(tài)甚至?xí)?mouseup 后繼續(xù)持續(xù),而當(dāng)你再次點擊就會取消。
8 背景標(biāo)簽
在編輯標(biāo)簽時,ie 10 是唯一可對背景或前景響應(yīng)的瀏覽器,如果標(biāo)簽變?yōu)楸尘昂?,雖然它會完成正在執(zhí)行的過渡,但它不會執(zhí)行新的過渡。ie 10 將等到標(biāo)簽變?yōu)榍熬昂笤賵?zhí)行新過渡。幸運的是,ie 10 已經(jīng)支持頁面的可見性 api,允許開發(fā)人員應(yīng)對這種操作行為。
9 隱藏元素
對于隱藏的元素,過渡是不會被執(zhí)行的,因為大多數(shù)瀏覽器都明確認(rèn)為沒有必要在一個看不見的元素里運行過渡。然而,也有特例,在 opera 下無論元素隱藏與否它都將執(zhí)行過渡。
10 過渡之前,dom樹是否加載完畢
當(dāng)文檔脫離解析模式時,domcontentloaded 被觸發(fā),如果你在使用 jquery,那么應(yīng)該了解 jquery.ready(),過渡可以在這之前運行。
11 渲染差異
這個問題我之前已經(jīng)說過了, 本文就是基于我的測試結(jié)果進(jìn)行闡述的。測試是自動運行的,但事實證明,還是發(fā)現(xiàn)了很多問題。
當(dāng)時要實現(xiàn)從漸變到漸變的背景過渡是不可能的,但可以實現(xiàn)從漸變到純色的過渡。如果漸變正在進(jìn)行中,從白色到目標(biāo)顏色的過渡即將開始,在過渡啟動時,會看到白色在快速閃動。目前所有的瀏覽器中都可以察覺到這一點。
不過firefox 似乎是用不同的算法來渲染圖像的,以表明它們執(zhí)行了動畫過渡(見實例)。很顯然,在動畫過渡時, gecko 并沒有呈現(xiàn)好的效果。如果 transform: scale() 足夠低,這種情況將發(fā)生。
firefox 不會從 a:visited 到 a:hover 過程中過渡動畫,反之亦然。 但它會從 a:visited 直接跳到 a:link,然后過渡到 a:hover 狀態(tài), 你可以在這個例子中看到,這是在 mozilla developer network”privacy and the :visited selector”中提到的。然而 ie 10 與 chrome,safari 和 opera 瀏覽器一樣,會從a:link到a:visited實現(xiàn)過渡。
如果子元素的 position 改變時, firefox 不會觸發(fā)元素的屬性, 而 webkit,opera 和 ie 10 則會觸發(fā)。
12 對規(guī)范的建議
看完了整個規(guī)范并對所有功能進(jìn)行了測試之后,覺得如果能進(jìn)行以下優(yōu)化將會更好:
加入transitionsend(注意是復(fù)數(shù)),一個元素的所有過渡一旦完成就啟動觸發(fā)。它能告知一系列已被觸發(fā)的屬性,但是不需要知道哪些已被過渡, 只要知道所有的動畫過渡何時可以完成即可。
加入 transitionstart 任務(wù),以便可以獲取每個待過渡屬性。因為 javascript 的事件循環(huán)和渲染路徑不一定能互相牽制,單一的 transitionsstart(也會重復(fù)多次)可能是更好的解決方案。我不知道為什么要 cancel 任務(wù),所以這就叫“操作后就不再管”。
要明確哪些 transitionend 需要被觸發(fā),前面舉例的 webkit 中 padding 和 padding-left 的問題會讓人很頭疼。
要明確說明“隱形過渡”如何處理, 前面例子中 transition-property: font-size的line-height: 1em 應(yīng)該要有明確的處理方式。
需要添加那些允許定義 pointer-events: none 并防止意外懸停狀態(tài)的::transitioning偽類,這里防止濫用樣式,因為他們自身會引發(fā)新的過渡或者改變正在進(jìn)行的過渡。除了這些建議,我們還需要能在不大量使用 javascript 進(jìn)行輔助的情況下進(jìn)行一些常規(guī)操作。
有時候你需要禁用過渡。例如,為了在網(wǎng)站訪問者面前呈現(xiàn)完美過渡之前,你需要調(diào)整布局并對尺寸規(guī)格進(jìn)行精確測算對位置進(jìn)行完美布局。
有時你想立即從 dom 中移除一個對象。你可以添加一個類,等待 transitionend 完成后再進(jìn)行刪除。
跟刪除對象一樣,你想要添加一個新元素。你可插入這個元素,設(shè)置“隱藏”以實現(xiàn)新元素的樣式變化。
重新排序,隱藏和顯示元素都比較常見。針對這些進(jìn)行樣式操作就要像操作實用程序一樣,如 isotope。
13 使用delay
使用延時,可以很好的解決無意的鼠標(biāo)懸停造成的樣式變化,如同settimeout()。
14 總結(jié)(可參考前面談到過的實例)
使用 transition-property: all 時注意,否則將遇到本不需要進(jìn)行轉(zhuǎn)換的 transitionend 情況。
當(dāng)使用可縮寫屬性時,觸發(fā)事件的數(shù)量會根據(jù)不同瀏覽器而不同。
opera 和 ie 不支持延遲時間為負(fù)值。
webkit在屬性優(yōu)先級上存在問題,例如:要避免transition-property: margin, margin-left的情況。
ie不支持隱式轉(zhuǎn)換。
firefox和opera無法解析 transition-property: all, width。
opera 混淆了屬性的優(yōu)先級。
偽元素的過渡不會影響 transitionend。
偽元素的過渡在 ie 10 下會出現(xiàn):hover的bug。
更多信息請查看IT技術(shù)專欄