web前端入門到實戰(zhàn):關(guān)于CSS過渡(transition),你需要知道的事

CSS3的過渡屬性,給web應(yīng)用帶來了簡單優(yōu)雅的動畫,但是比起初次相見,他(transition)有許多細(xì)則。

在這片文章中,我將會專研CSS3的過渡(transition)中更加復(fù)雜的部分,從鏈?zhǔn)胶褪录接布铀俸蛣赢嫼瘮?shù)。

讓瀏覽器控制動畫序列,通過改變幀率,減少繪畫和減少GPU的工作,能夠優(yōu)化性能和效率。

應(yīng)用 transition

一個最簡單使用transition的方法就是和CSS偽元素一起用,比如:hover。注意我們在指定屬性名字,transition的時長,以及默計時函數(shù),linear。

.element {
  height: 100px;
  transition: height 2s linear;
}

.element:hover {
  height: 200px;
} 

當(dāng):hover偽元素被激活的時候,這高度會動態(tài)地在兩秒內(nèi)從100px過度到200px。

duration是唯一在transition縮寫中需要的項目。瀏覽器默認(rèn)的定時方法是ease,以及一個all的屬性,除非他們已經(jīng)提供了。

當(dāng)談?wù)摰郊せ顃ransition,我們不希望被限制于使用偽元素 —— 很顯然這不靈活。解決這個的方法就是用程序添加和刪除class


專門建立的學(xué)習(xí)Q-q-u-n: 731771211,分享學(xué)習(xí)方法和需要注意的小細(xì)節(jié),不停更新最新的教程和學(xué)習(xí)技巧
(從零基礎(chǔ)開始到前端項目實戰(zhàn)教程,學(xué)習(xí)工具,全棧開發(fā)學(xué)習(xí)路線以及規(guī)劃)
/* CSS */
.element {
  opacity: 0.0;
  transform: scale(0.95) translate3d(0,100%,0);
  transition: transform 400ms ease, opacity 400ms ease;
}

.element.active {
  opacity: 1.0;
  transform: scale(1.0) translate3d(0,0,0);
}

.element.inactive {
  opacity: 0.0;
  transform: scale(1) translate3d(0,0,0);
}

// JS with jQuery
var active = function(){
  $('.element').removeClass('inactive').addClass('active');
};

var inactive = function(){
  $('.element').removeClass('active').addClass('inactive');
}; 

以上的列子,我們用了2個不同的過渡(transition),當(dāng)激活的時候,元素向上滑動,當(dāng)無效化之后,淡出。所有的javascript所做的事就是切換active 和 inactive這兩個class。

過渡漸變

不是所有的CSS屬性都能過渡,最基本的規(guī)則是你只能過渡絕對值。比如,你不能讓height從 0px過渡到auto,瀏覽器不能計算中間過度值,因此屬性變化是瞬間的。

同時,有一些很好的解決方法。第一個方法包括添加透明度到漸變,然后過渡到背景色。比如:

.panel {
  background-color: #000;
  background-image: linear-gradient(rgba(255, 255, 0, 0.4), #FAFAFA);
  transition: background-color 400ms ease;
}

.panel:hover {
  background-color: #DDD;
} 

如果漸變是持續(xù)的,你可以過渡background-position,就像這里寫的,否則,你的最后手段是創(chuàng)建兩個元素,一個放在另一個之上,然后過渡你的透明度。

.element {  
  width: 100px;  
  height: 100px;  
  position: relative;
  background: linear-gradient(#C7D3DC,#5B798E);    
}  

.element .inner { 
  content: '';
  position: absolute;
  left: 0; top: 0; right: 0; bottom: 0;
  background: linear-gradient(#DDD, #FAFAFA);          
  opacity: 0;
  transition: opacity 1s linear;
}

.element:hover .inner {
  opacity: 1;
} 

后者方法的需要注意的是,這需要額外的標(biāo)記,并且在內(nèi)部的div能夠捕捉到指針事件。偽元素,類似:before和:after可以是過度理想的使用案例。

硬件加速

過渡某個屬性,比如left和margin會導(dǎo)致瀏覽器每幀都會重新計算樣式。這消耗相當(dāng)昂貴,并且可能會導(dǎo)致不必要的重繪,特別是如果你在屏幕上有很多元素。這在低性能設(shè)備上顯得特別明顯,比如手機。

這個解決方案是使用CSS過渡來減少渲染給GPU帶來的壓力。簡單來說,這在過渡的時候,將元素變成了一張圖片,避免任何樣式重新計算,這極大程度上增加了性能。一個簡單強迫瀏覽器用硬件渲染一個元素的方法是,設(shè)置轉(zhuǎn)型的Z軸,這個你可以用translate3d:

transform: translate3d(0,0,0);

不過這不是根治性能的方法,并且會帶來許多本身的問題。只有當(dāng)需要的時候,你才應(yīng)該用硬件加速,并且完全不需要在每個元素上都使用它。

比如,硬件加速會導(dǎo)致微妙的字體問題,比如一個字體出現(xiàn)的時候失去了加粗效果。這是因為一個bug,當(dāng)元素開啟硬件加速的時候,不支持子像素抗鋸齒。你可以看到在兩個渲染模式下的一個清晰的差別。

此外,不同的瀏覽器用不同的硬件加速的庫,這可能會造成跨瀏覽器問題。比如,當(dāng)Chrome和Safari都是WebKit內(nèi)核的,Chrome使用Skia來做圖形渲染,然而同時Safari用的CoreGraphics。這兩個庫之間的差別是細(xì)微的,但是確是存在。

你可以用Chrome的開發(fā)者工具概覽頁面,顯示所有的重繪。外加你可以在開發(fā)者工具選項中選擇顯示三角形,甚至可以通過 about:flags 打開復(fù)合渲染層邊界,來看哪個層是作用在GPU上的。關(guān)鍵是批量在DOM刷新下,減少繪畫,并且從GPU上減少最多的的壓力。

如果你在瀏覽器之間因為硬件加速而有了顯示問題,比如閃爍或者顫動,確保你沒有用transform3d()的CSS屬性在元素上。

剪裁

為了充分利用GPU渲染,你需要避免使用CSS樣式變換而不是像width這種重新計算樣式的屬性。若果你確實需要給元素的寬度做動畫?解決方案就是剪裁。

在以下的例子中個,你可以看到一個搜索框和2個過度狀態(tài)。第二個擴(kuò)展?fàn)顟B(tài)被一個剪裁的元素給隱藏了。

過渡這個擴(kuò)展的寬度,我們所需要做的就是轉(zhuǎn)變X軸到左邊。這邊的關(guān)鍵是我們用translate3d而不是改變元素的寬度。

.clipped {
  overflow: hidden;
  position: relative;
}

.clipped .clip {
  right: 0px;
  width: 45px;
  height: 45px;
  background: url(/images/clip.png) no-repeat
}

input:focus {
  -webkit-transform: translate3d(-50px, 0, 0);
} 

確保我們不會在每一幀重新計算元素的寬度,過渡會變得順滑和高性能。

時間函數(shù)

到目前為止,我們用了一些瀏覽器預(yù)定義時間函數(shù)linear, ease, ease-in, ease-out和ease-in-out。對于更多復(fù)雜的時間函數(shù)來說,我們要寫我們自己的時間函數(shù),通過定義貝塞爾曲線的4個關(guān)鍵點。

transition: -webkit-transform 1s cubic-bezier(.17,.67,.69,1.33);

規(guī)劃過渡

在CSS中寫過渡非常好,但是有時候你需要更多控制,特別是談到鏈?zhǔn)竭^渡的時候。幸運的是我們不僅能從javascript中調(diào)用過渡,也能定義他們。

CSS過渡有一個魔法般的all屬性,這確保了任何屬性改變都是過渡的。讓我們看看如何實踐他們

var defaults = {
  duration: 400,
  easing: ''
};

$.fn.transition = function (properties, options) {
  options = $.extend({}, defaults, options);
  properties['webkitTransition'] = 'all ' + options.duration + 'ms ' + options.easing;
  $(this).css(properties);
}; 

現(xiàn)在我們有個jQuery函數(shù)$.fn.transition,我們可以用它來編程調(diào)用過渡。

$('.element').transition({background: 'red'});

過渡回調(diào)

接下來的步奏是鏈?zhǔn)竭^渡,是過渡結(jié)束后回調(diào)。你可以在Webkit中獲得這個狀態(tài),通過監(jiān)聽webkitTransitionEnd這個事件。


專門建立的學(xué)習(xí)Q-q-u-n: 731771211,分享學(xué)習(xí)方法和需要注意的小細(xì)節(jié),不停更新最新的教程和學(xué)習(xí)技巧
(從零基礎(chǔ)開始到前端項目實戰(zhàn)教程,學(xué)習(xí)工具,全棧開發(fā)學(xué)習(xí)路線以及規(guī)劃)
var callback = function () {
    // ...
  }

  $(this).one('webkitTransitionEnd', callback)
  $(this).css(properties); 

記住有時候事件沒有綁定,經(jīng)常是在那些屬性不改變或者一個繪畫沒有被觸發(fā)的情況下。為了確保我們一直有一個回調(diào),讓我們設(shè)置一個超時,這會手動幫我們觸發(fā)時間。

$.fn.emulateTransitionEnd = function(duration) {
  var called = false, $el = this;
  $(this).one('webkitTransitionEnd', function() { called = true; });
  var callback = function() { if (!called) $($el).trigger('webkitTransitionEnd'); };
  setTimeout(callback, duration);
}; 

我們在設(shè)置元素css之前,請求 $.fn.emulateTransitionEnd(),以確保我們過渡之后會有CSS回調(diào)。

$(this).one('webkitTransitionEnd', callback);
$(this).emulateTransitionEnd(options.duration + 50);
$(this).css(properties); 

鏈?zhǔn)竭^渡

因此,現(xiàn)在我們能夠通過寫程引用過渡,當(dāng)他們結(jié)束之后獲得回調(diào),我們能夠開始排序過渡。我們能夠?qū)懽约旱男蛄衼碜鲞@件事,但是我們用了jQuery,我們最好滲透庫的現(xiàn)存方法。

jQuery提供了2個關(guān)鍵函數(shù)和他的隊列和API聯(lián)系,.fn.queue(callback)和.fn.dequeue()。前者加了一個回調(diào)到隊列中,然后后者執(zhí)行下一個隊列中的項目。

換句話說,我們需要設(shè)置我們的CSS過渡在.fn.queue回調(diào)之中,然后確保當(dāng)過渡完成的時候,我們調(diào)用了.fn.dequeue

var $el = $(this);
$el.queue(function(){
  $el.one('webkitTransitionEnd', function(){
    $el.dequeue();
  });
  $el.css(properties);
}); 

這個例子相對簡單,但是他讓我們創(chuàng)建了復(fù)雜的鏈?zhǔn)絼赢嫞⑶疑踔潦褂胘Query的delay()函數(shù):比如:

$('.element').transition({left: '20px'})
             .delay(200)
             .transition({background: 'red'}); 

重新繪制

在過渡的時候,你會需要兩組CSS屬性。最初的屬性是動畫應(yīng)該從哪里開始,和最后的屬性,過渡應(yīng)該在哪里結(jié)束。

$('.element').css({left: '10px'})
             .transition({left: '20px'}); 

然而,你會發(fā)現(xiàn)如果你應(yīng)用了兩組屬性,一個立馬在另一個之后運行了,然后瀏覽器嘗試優(yōu)化屬性改變,無視你的初始屬性和阻止過渡。在場景之后,瀏覽器繪制之前,批處理屬性變動,經(jīng)常會加速渲染,經(jīng)常會有不良反應(yīng)。

解決方法是在兩組屬性之間強迫重繪。一個簡單的方法是獲取Dom元素的offsetHeight屬性,就像這樣

$.fn.redraw = function(){
  $(this).each(function(){
    var redraw = this.offsetHeight;
  });
}; 

這在素有的瀏覽器中有有效,但是我有次很巧合地在Android中,這依然不行??晒┨娲姆椒ㄓ衪imeout或者切換class名。

$('.element').css({left: '10px'})
             .redraw()
             .transition({left: '20px'}); 

未來

過渡(Transition)正在活躍地應(yīng)用,以及下一個標(biāo)準(zhǔn)看上去很有前景。這個建議包括了一個新的javascript的API,這個api專注于過渡現(xiàn)存的限制,并給與開發(fā)者更多的靈活性。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容