CSS魔法堂:Transition就這麼好玩

肥仔John發表於2018-06-14

前言

 以前說起前端動畫必須使用JS,而CSS3為我們帶來transition和@keyframes,讓我們可以以更簡單(宣告式代替命令式)和更高效的方式實現UI狀態間的補間動畫。本文為近期對Transition的學習總結,歡迎各位拍磚。

屬性介紹

 首先先我們簡單粗暴瞭解transition屬性吧!

transition: <transition-property> <transition-duration> <transition-timing-function> <transition-delay>;

/* 設定啟用Transition效果的CSS屬性
 * 注意:僅會引發repaint或reflow的屬性可啟用Transition效果
 *       [CSS_animated_properties](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties)
 */
<transition-property>: all | none | <property> [,<property>]*

/* 設定過渡動畫持續時間,單位為s或ms
 */
<transition-duration>: 0s | <time> [, <time>]*

/* 設定過渡動畫的緩動函式
 * cubic-bezier的值從0到1
 * [一個很好用的cubic-bezier配置工具](http://cubic-bezier.com)
 */
<transition-timing-function>: linear|ease|ease-in|ease-out|ease-in-out|cubic-bezier(n,n,n,n)

/* 設定過渡動畫的延時,單位為s或ms
 */
<transition-delay>: 0s | <time> [, <time>]

 另外我們可以一次性為多個CSS屬性啟動Transition效果

transition: width 1s ease .6s,
            color .5s linear,
            background 2s ease-in-out;

觸發方式

 既然Transition是UI狀態間的補間動畫,那麼有且僅有修改UI狀態時才能讓動畫動起來。那麼就有3種方式了:

  1. 偽類.:link,:visited,:hover,:active:focus
  2. 通過JS修改CSS屬性值
  3. 通過JS修改className值

TransitionEnd事件詳解

TransitionEnd Event

el.addEventListener("transitionend"
  , e => 
    {
      const pseudoElement = e.pseudoElement // 觸發動畫的偽類
          , propertyName = e.propertyName   // 發生動畫的CSS屬性
          , elapsedTime = e.elapsedTime     // 動畫的持續時間
      // ..................
    })

注意:每個啟用TransitionCSS屬性的分別對應獨立的transitionend事件

/* 觸發3個transitionend事件 */
transition: width 1s ease .6s,
            color .5s linear,
            background 2s ease-in-out;

Visibility也能transition?

 在可啟用Transition的CSS屬性中,我們發現到一個很特別的CSS屬性——visibilityvisibility常與display相提並論的屬性,它憑什麼能啟用Transition,而display不行呢?這個我真心不清楚,不過我們還是瞭解啟用transition的visibility先吧!
visibility是離散值,0(hidden)表示隱藏,1(visible)表示完全顯示,非0表示顯示。那麼visibility狀態變化就存在兩個方向的差異了:

  1. 隱藏顯示,由於非0就是顯示,那麼從值從0到1的過程中,實際上是從隱藏直接切換到顯示的狀態,因此並沒有所謂的變化過程;
  2. 顯示隱藏,從1到0的過程中,存在一段時間保持在顯示的狀態,然後最後一瞬間切換到隱藏,因此效果上是變化延遲,依然沒有變化過程。

 上述表明啟用transition的visibility並沒有補間動畫的視覺效果,那麼到底有什麼作用呢?答案就是不影響/輔助其他CSS屬性的補間動畫。其中最明顯的例子就是輔助opacity屬性實現隱藏顯示的補間動畫。

<style>
.form-input{
    display: inline-flex;
    line-height: 2;
    border: solid 1px rgba(0,0,0,0.3);
}
.form-input:hover{
        border: solid 1px rgba(0,0,0,0.4);
}
.form-addon{
    font-style: normal;
    color: #666;
    background: #ddd;
    padding-left: 10px;
    padding-right: 10px;
    border-right: solid 1px rgba(0,0,0,0.3);
}
.form-addon-after{
    border-left: solid 1px rgba(0,0,0,0.3);
    border-right: none 0;
}
.form-control{
    border:none 0;
    outline-color: transparent;
    padding: 5px;
    caret-color: #888;
    font-size: 16px;
}
.tips-host{
    position: relative;
}
.tips{
    cursor: default;
    z-index: 999;
    position: absolute;
    top: 120%;
    font-size: 12px;
    color: #444;
    background: #ccc;
    padding: .5em;
    box-shadow: 2px 2px 10px #999;
    transition: box-shadow .2s,
                       opacity 1s,
                       visibility .8s;
    visibility: hidden;
    opacity: 0;
}
.tips:hover{
    box-shadow: 2px 2px 5px #999;
}
.tips::before{
    content: "";
    border: solid 10px transparent;
    border-bottom: solid 10px #ccc;
    position:  absolute;
    transform: translate(0, -100%);
}
.form-control:focus + .tips{
    visibility: visible;
    opacity: 1;
}
</style>
<div class="form-input tips-host" >
    <i class="form-addon">Amount</i>
    <input class="form-control">
    <div class="tips">
        Starts with alphabet, then anything you would like.
    </div>
</div>

 當opacity:0時,需要元素隱藏了但實際上它仍然位於原來的位置,而且可以攔截和響應滑鼠事件,當出現元素重疊時則會導致底層元素失效。而由於visibility:hidden時,元素不顯示且不攔截滑鼠事件,所以在補間動畫的最後設定visibility:hidden為不俗的解決辦法。

display:none讓transition失效的補救措施

 雖然修改display有可能會引發reflow,但它依然不能啟用Transition,這點真心要問問委員會了。更讓人疑惑的是,它不單不支援啟用Transition,而且當設定display:none時其餘CSS屬性的Transition均失效。難到這是讓元素脫離渲染樹的後果??

<style>
.box{
  display: none;
  background: red;
  height: 20px;
}
</style>
<div class="box"></div>
<button id="btn1">Transition has no effect</button>
<button id="btn2">Transition takes effect</button>
<script>
const box = document.querySelector(".box")
    , btn1 = document.querySelector("#btn1")
    , btn2 = document.querySelector("#btn2")
btn1.addEventListener("click", e => {
  box.style.display = "block"
  box.style.background = "blue"
})
btn2.addEventListener("click", e => {
  box.style.display = "block"
  box.offsetWidth              // 強制執行reflow
  box.style.background = "blue"
})
</script>

上面的程式碼,當我們點選btn1時背景色的transition失效,而點選btn2則生效,關鍵區別就是通過box.offsetWidth強制執行reflow,讓元素先加入渲染樹進行渲染,然後再修改背景色執行repaint。
那麼我們可以得到的補救措施就是——強制執行reflow,下面的操作均可強制執行reflow(注意:會影響效能哦!)

offsetWidth, offsetHeight, offsetTop, offsetLeft
scrollWidth, scrollHeight, scrollTop, scrollLeft
clientWidth, clientHeight, clientTop, clientLeft
getComputeStyle(), currentStyle()

總結

尊重原創,轉載請註明轉自:https://www.cnblogs.com/fsjohnhuang/p/9143035.html ^_^肥仔John

參考

小tip: transition與visibility
https://www.cnblogs.com/surfaces/p/4324044.html


相關文章