CSS分層動畫可以讓元素沿弧形路徑運動

jopen 8年前發布 | 35K 次閱讀 CSS WebKit 前端技術

譯者注:部分代碼示例在原文中可以看效果(作者寫在博文里面了…),我偷懶把它做成Gif圖了。

CSS 的 animations (動畫) 和 transitions(變換)擅于實現從點 A 到點 B 的直線運動,運動軌跡是直線路徑。給一個元素添加了 animation 或者 transition 以后,無論你如何調整 貝塞爾曲線 ,都無法讓它沿著弧形路徑運動。你可以通過自定義 timing function 屬性,做出彈動的效果,但是它沿著 X 和 Y 軸相對移動的值永遠是相同的。

與其使用 JavaScript 實現外觀自然的運動,不如嘗試用這種簡單的方式:分層動畫,繞過已有的限制。通過使用兩個或多個元素實現動畫效果,我們可以更加細粒度地控制某個元素的路徑,沿著 X 軸運動使用一種 timing function ,沿著 Y 軸運動使用另一種 timing function 。

問題所在

當我們深入探討解決方案之前,看看到底問題在哪。CSS animations 和 transitions 限制我們只能沿直線路徑運動。元素總是沿著點 A 到點 B 的最短路徑運動,如果我們另辟蹊徑,告訴 CSS 沿著“更好的路徑”,而不是“最短路徑”運動呢?

用 CSS (開啟硬件加速)實現兩點之間的運動,最直截了當的方式是使用 transform 的 translate 在一定時間內移動某個元素。這就產生了直線運動。在 @keyframes 中,我們打算在 (0,0) 和 (100,-100) 間來回運動,見上圖例子:

@keyframes straightLine {
  50% {
 transform: translate3D(100px, -100px, 0);
 }
}

.dot {
 animation: straightLine 2.5s infinite linear;
}

這些看起來并不難懂,但我們稍等片刻,思考一下我們需要的解決方案,拆分開來的動畫,視覺上長什么樣子呢。

0% 時,元素從 (0,0) 出發, 50% 時,我們用了 translate3D(100px, -100px, 0) 把它移動到 (100,-100),然后原路返回。換句話說,我們把元素向右移動了 100px ,向上移動了 100px ,兩個方向聯合作用使元素沿著一個角度運動。

解決方案:每個軸執行自己的動畫函數

那么,原先展示的例子中我們如何實現的弧形路徑呢?為了讓創建的路徑不是直線, 我們想讓元素沿 X 軸和 Y 軸的運動速度不同步

先前例子中都用到了 linear 線性運動函數,如果我們給運動的元素包裹一個容器,我們可以為 X 軸應用一種動畫函數,Y 軸應用另一種動畫函數。以下例子,我們在 X 軸使用 ease-in ,Y 軸使用 ease-out 。

每個軸元素的具體實現

不幸的是,我們不能只把 transform 動畫簡單疊加:因為只有最后聲明的動畫會執行。那么我們如何把兩個動畫效果聯合起來呢?可以把一個元素放入另一個元素內部,給容器元素加一種動畫,給里面的子元素添加另一種動畫。

在以上例子中,你已經看到一個點沿著弧形路徑運動,看到兩個獨立的元素一起做動畫,只不過容器元素是完全透明的。為了清晰地看到兩個元素沿著弧形路徑是如何相互作用的,我們給容器元素加個邊框看看唄:

那個點藏在帶邊框的盒子內部,跟隨盒子一起沿 X 軸遠動,同時它自己又在 Y 軸方向上下運動。去掉容器盒子的邊框,我們就得到了弧形路徑。與其在 HTML 中用兩個元素,還不如用偽元素實現嘞。如果 HTML 是這樣:

<div class="dot"></div>

我們可以添加偽元素:

.dot {
 /* 容器:沿 X 軸運動 */
}

.dot::after {
 /* 黑點兒,沿 Y 軸運動 */
}

然后,我們需要兩塊獨立的動畫代碼:X 軸,Y 軸各一塊。注意一處用了 ease-in ,另一處用了 ease-out :

.dot {
 /*省略 一些布局代碼...*/
 animation: xAxis 2.5s infinite ease-in;
}

.dot::after {
 /* 渲染小黑點兒*/
 animation: yAxis 2.5s infinite ease-out;
}

@keyframes xAxis {
  50% {
 animation-timing-function: ease-in;
 transform: translateX(100px);
 }
}

@keyframes yAxis {
  50% {
 animation-timing-function: ease-out;
 transform: translateY(-100px);
 }
}

加上 WebKit 前綴,用一些自定義的貝塞爾曲線代替 ease-in 和 ease-out ,我們就可以實現文章最開頭展示的效果:

.demo-dot {
 -webkit-animation: xAxis 2.5s infinite cubic-bezier(0.02, 0.01, 0.21, 1);
 animation: xAxis 2.5s infinite cubic-bezier(0.02, 0.01, 0.21, 1);
}

.demo-dot::after {
 content: '';
 display: block;
 width: 20px;
 height: 20px;
 border-radius: 20px;
 background-color: #fff;
 -webkit-animation: yAxis 2.5s infinite cubic-bezier(0.3, 0.27, 0.07, 1.64);
 animation: yAxis 2.5s infinite cubic-bezier(0.3, 0.27, 0.07, 1.64);
}

@-webkit-keyframes yAxis {
  50% {
 -webkit-animation-timing-function: cubic-bezier(0.02, 0.01, 0.21, 1);
 animation-timing-function: cubic-bezier(0.02, 0.01, 0.21, 1);
 -webkit-transform: translateY(-100px);
 transform: translateY(-100px);
 }
}

@keyframes yAxis {
  50% {
 -webkit-animation-timing-function: cubic-bezier(0.02, 0.01, 0.21, 1);
 animation-timing-function: cubic-bezier(0.02, 0.01, 0.21, 1);
 -webkit-transform: translateY(-100px);
 transform: translateY(-100px);
 }
}

@-webkit-keyframes xAxis {
  50% {
 -webkit-animation-timing-function: cubic-bezier(0.3, 0.27, 0.07, 1.64);
 animation-timing-function: cubic-bezier(0.3, 0.27, 0.07, 1.64);
 -webkit-transform: translateX(100px);
 transform: translateX(100px);
 }
}

@keyframes xAxis {
  50% {
 -webkit-animation-timing-function: cubic-bezier(0.3, 0.27, 0.07, 1.64);
 animation-timing-function: cubic-bezier(0.3, 0.27, 0.07, 1.64);
 -webkit-transform: translateX(100px);
 transform: translateX(100px);
 }
}

以下是文章起始處的例子:

JS Bin on jsbin.com

你可能注意到我們在所有例子中都用了 @keyframes ,這純粹是因為我們想展示黑點兒往返的兩種狀態。如果只想實現點 A 至點 B 的運動,使用 transition 屬性做分層動畫同樣好用。

如果有個絕對定位的元素,通過給 left 和 bottom 屬性加特效,就可以實現弧形路徑運動,單個元素就可以,不需要容器元素。為什么不這么做呢:它性能稍差一些,動畫的每一幀都會引起重繪。使用帶偽元素的分層動畫, translate 屬性又開了硬件加速,動畫效果更好,性能也更高。

譯者自己搞了個絕對定位的例子:

JS Bin on jsbin.com

來自: http://jinlong.github.io/2016/01/14/moving-along-a-curved-path-in-css-with-layered-animation/

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!