精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

揭秘!如何將動效描述自動轉化為動效代碼

發布于 2024-12-31 17:02
瀏覽
0收藏

導讀:在上一篇文章中,我們詳細介紹了Vision動效平臺的渲染引擎——Crab,并分享在復雜動效渲染場景下積累的實踐經驗和精彩案例。今天,我們將揭秘如何將「動效描述翻譯為動效代碼」——從Lottie導出CSS/Animated代碼。

一、項目背景

在進行前端頁面開發中,經常需要涉及到元素動效的開發,比如按鈕的呼吸狀態動效,彈窗的出現和消失動效等等,這些動效為用戶在頁面交互過程中獲得良好的體驗起到重要的作用。

要開發這些動效,一般的工作流程是由設計同學提供動效描述,然后研發同學按照參數實現對應平臺的動效代碼(如Web平臺的CSS或React Native的Animated),從而進行動效的還原。

1.1 元素動效開發的痛點

對于一些獨立性較強或比較復雜的動效,可以直接使用Lottie來進行播放,但是一方面對于一些比較簡單的動效需求,如果引入Lottie來進行播放,則Lottie帶來的額外運行時包體積的成本相比于動效本身過高,另一方面,對于元素動效中常見的和業務邏輯或用戶操作綁定的情況,直接使用Lottie有時反而會引入額外的開發成本。

在動效還原的過程中,研發需要面對設計師交付的各種不同格式的動效描述,可能是一句自然語言的描述,一個時間軸或者使用AE插件導出的文本描述等等,然后人肉將設計同學提供的這些動效描述翻譯為動效代碼,這個過程常常是一個重復性很強的工作,且耗時耗力,會帶來不小的心智負擔。

文本動效參數交付示例:

Total Dur: 1200ms
 
≡ 盒子.png ≡
- 縮放 -
Delay: 0ms
Dur: 267ms
Val: 0% ?? 189.6%
(0.33, 0, 0.67, 1)

- 縮放 -
Delay: 267ms
Dur: 500ms
Val: [189.6,189.6]%??[205.4,173.8]%
(0.33, 0, 0.83, 1)

- 縮放 -
Delay: 767ms
Dur: 67ms
Val: [205.4,173.8]%??[237,142.2]%
(0.17, 0, 0.83, 1)

- 縮放 -
Delay: 833ms
Dur: 100ms
Val: [237,142.2]%??[142.2,237]%
(0.17, 0, 0.83, 1)

- 縮放 -
Delay: 933ms
Dur: 167ms
Val: [142.2,237]%??[205.4,173.8]%
(0.17, 0, 0.83, 1)

- 縮放 -
Delay: 1100ms
Dur: 100ms
Val: [205.4,173.8]%??[189.6,189.6]%
(0.17, 0, 0.67, 1)

- 位置 -
Delay: 833ms
Dur: 100ms
Val: [380,957]??[380,848]
(0.33, 0, 0.67, 1)

- 位置 -
Delay: 933ms
Dur: 133ms
Val: [380,848]??[380,957]
(0.33, 0, 0.67, 1)

- 旋轉 -
Delay: 267ms
Dur: 73ms
Val: 0° ??? -3°
(0.33, 0, 0.67, 1)

- 旋轉 -
Delay: 340ms
Dur: 73ms
Val: -3° ??? 3°
(0.33, 0, 0.67, 1)

- 旋轉 -
Delay: 413ms
Dur: 73ms
Val: 3° ??? -3°
(0.33, 0, 0.67, 1)

- 旋轉 -
Delay: 487ms
Dur: 73ms
Val: -3° ??? 3°
(0.33, 0, 0.67, 1)

- 旋轉 -
Delay: 560ms
Dur: 73ms
Val: 3° ??? -3°
(0.33, 0, 0.67, 1)

- 旋轉 -
Delay: 633ms
Dur: 67ms
Val: -3° ??? 3°
(0.33, 0, 0.67, 1)

- 旋轉 -
Delay: 700ms
Dur: 67ms
Val: 3° ??? 0°
(0.33, 0, 0.67, 1)



Total Dur: 500ms
 
≡ 蓋子_關.png ≡
- 位置 -
Delay: 0ms
Dur: 500ms
Val: [74,13]??[74,13]
No Change

- 旋轉 -
Delay: 0ms
Dur: 28ms
Val: 0.75° ??? 0°
(0.33, 0.54, 0.83, 1)

- 旋轉 -
Delay: 28ms
Dur: 72ms
Val: 0° ??? -2°
(0.33, 0, 0.67, 1)

- 旋轉 -
Delay: 100ms
Dur: 72ms
Val: -2° ??? 2°
(0.33, 0, 0.67, 1)

- 旋轉 -
Delay: 172ms
Dur: 72ms
Val: 2° ??? -2°
(0.33, 0, 0.67, 1)

- 旋轉 -
Delay: 244ms
Dur: 72ms
Val: -2° ??? 2°
(0.33, 0, 0.67, 1)

- 旋轉 -
Delay: 317ms
Dur: 72ms
Val: 2° ??? -2°
(0.33, 0, 0.67, 1)

- 旋轉 -
Delay: 389ms
Dur: 72ms
Val: -2° ??? 2°
(0.33, 0, 0.67, 1)

- 旋轉 -
Delay: 461ms
Dur: 39ms
Val: 2° ??? 0.75°
(0.33, 0, 0.67, 0.55)
(盒子.png是蓋子_關.png父級)


Total Dur: 1633ms
 
≡ 蓋子_開.png ≡
- 位置 -
Delay: 0ms
Dur: 1633ms
Val: [113,5]??[113,5]
Linear
(在第1633ms,切換 蓋子_開.png和盒子.2.png)


Total Dur: 267ms
 
≡ 盒子.2.png ≡
- 縮放 -
Delay: 0ms
Dur: 267ms
Val: 189.6% ?? 0%(0.17, 0, 0.83, 1)

表格動效參數交付示例:

揭秘!如何將動效描述自動轉化為動效代碼-AI.x社區圖片

要解決這個痛點,我們可以考慮將「從動效描述翻譯為動效代碼」的工作通過自動化的方式完成。而要實現這個自動化的流程,首先要解決的就是設計師提供的動效描述沒有統一格式的問題。

最適合用作動效描述統一格式的方案就是Lottie,Lottie是一個基于JSON的動畫文件格式,它可以使用Bodymmovin解析導出Adobe After Effects動畫,并在移動設備上渲染它們。通過它,設計師可以創造和發布酷炫的動畫,且無需工程師費心的手工重建動畫效果。

它具有以下優點:

  • 標準化:Lottie的JSON格式中,每個屬性的含義和數據類型都很明確,相比于自然語言的描述方式,更加清晰明確。
  • 無感知:設計師在AE中完成動效的編輯后,可以直接使用AE的BodyMovin插件導出我們期望Lottie格式動效描述,導出過程不會為設計師引入額外的成本。
  • 透明化:Lottie的運行庫是開源的,這意味著我們可以通過它的代碼和文檔完全弄清楚json中每一個字段的具體含義和處理方式。

二、Lottie格式簡介

在進行代碼轉換之前,我們首先來介紹下Lottie的JSON格式。

首先在Lottie格式的Root層,會存儲動畫的全局信息,比如動效的展示寬高,播放幀率,引用的圖片等資源描述以及動畫細節描述等。

interface LottieSchema {
    /**
     * Adobe After Effects 插件 Bodymovin 的版本
     * Bodymovin Version
     */
    v: string; 

    /**
     * Name: 動畫名稱
     * Animation name
     */
    nm: string; // name

    /**
     * Width: 動畫容器寬度
     * Animation Width
     */
    w: number; // width

    /**
     * Height: 動畫容器高度
     * Animation Height
     */
    h: number; // height

    /**
     * Frame Rate: 動畫幀率
     * Frame Rate
     */
    fr: number; // fps

    /**
     * In Point: 動畫起始幀
     * In Point of the Time Ruler. Sets the initial Frame of the animation.
     */
    ip: number; // startFrame

    /**
     * Out Point: 動畫結束幀
     * Out Point of the Time Ruler. Sets the final Frame of the animation
     */
    op: number; // endFrame

    /**
     * 3D: 是否含有3D特效
     * Animation has 3-D layers
     */
    ddd: BooleanType;

    /**
     * Layers: 特效圖層
     * List of Composition Layers
     */
    layers: RuntimeLayer[]; // layers

    /**
     * Assets: 可被復用的資源
     * source items that can be used in multiple places. Comps and Images for now.
     */
    assets: RuntimeAsset[]; // assets

    // ......
}

在這些屬性中,

最為關鍵的是描述可復用資源的assets和描述詳細動畫信息的layers。

2.1 AE中動畫的實現方式

為了更好的理解Lottie中的layers和assets的具體含義,我們首先從前端角度簡單了解下設計師是如何在AE中實現動畫,并導出為Lottie的。

AE中進行動畫展示的基礎模塊是圖層(layer),設計師通過在AE中創建圖層的方式來創建動畫元素,而要讓動畫元素動起來,則可以通過在圖層上的不同屬性進行關鍵幀的設置來實現。這樣,通過多個圖層的疊加,以及在每個圖層的不同屬性上設置不同的關鍵幀就可以實現最終的效果。

示例

如下所示的引導小手動效,就可以通過創建四個圖層以及設置每個圖層的位移、旋轉、縮放或透明度的關鍵幀來實現。

揭秘!如何將動效描述自動轉化為動效代碼-AI.x社區

詳細動畫信息layers

layers是一個數組,其中的每一項會描述來自AE的一個圖層的具體動畫信息和展示信息。AE中有許多不同的圖層類型,每種有不同的特性和用途,Lottie中最常用的圖層類型有:文本圖層、圖像圖層、純色圖層、空圖層以及合成圖層等,所有圖層有一些通用的屬性,其中比較重要的屬性如下:

type LottieBaseLayer {
    /**
     * Type: 圖層類型
     * Type of layer
     */
    ty: LayerType;

    /**
     * Key Frames: Transform和透明度動畫關鍵幀
     * Transform properties
     */
    ks: RuntimeTransform;

    /**
     * Index: AE 圖層的 Index,用于查找圖層(如圖層父級查找和表達式中圖層查找)
     * Layer index in AE. Used for parenting and expressions.
     */
    ind: number;

    /**
     * In Point: 圖層開始展示幀
     * In Point of layer. Sets the initial frame of the layer.
     */
    ip: number;

    /**
     * Out Point: 圖層開始隱藏幀
     * Out Point of layer. Sets the final frame of the layer.
     */
    op: number;

    /**
     * Start Time: 圖層起始幀偏移(合成維度)
     * Start Time of layer. Sets the start time of the layer.
     */
    st: number;

    /**
     * Name: AE 圖層名稱
     * After Effects Layer Name
     */
    nm: string;

    /**
     * Stretch: 時間縮放系數
     * Layer Time Stretching
     */
    sr: number;

    /**
     * Parent: 父級圖層的 ind
     * Layer Parent. Uses ind of parent.
     */
    parent?: number;

    /**
     * Width: 圖層寬度
     * Width
     */
    w?: number;

    /**
     * Height: 圖層高度
     * Height
     */
    h?: number;
}

所有圖層中都含有描述Transform關鍵幀的ks屬性,這也是我們在做動效代碼轉換時著重關注的屬性。ks屬性中會描述圖層的位移、旋轉、縮放這樣的Transform屬性以及展示透明度的動畫,其中每一幀(每一段)的描述格式大致如下:

// keyframe desc
type KeyFrameSchema<T extends Array<number> | number> {
  // 起始數值 (p0)
   s: T;
  // 結束數值 (p3)
  e?: T;
  // 起始幀
  t: number;

  // 時間 cubic bezier 控制點(p1)
  o?: T;
    // 時間 cubic bezier 控制點(p2)
  i?: T;

  // 路徑 cubic bezier 控制點(p1)
  to?: T;
    // 路徑 cubic bezier 控制點(p2)
    ti?: T;
}

圖層的關鍵幀信息中會包含每個關鍵點的屬性數值,所在幀,該點上的控制緩動曲線的出射控制點和入射控制點,另外,對于位移的動畫,AE還支持路徑運動,在Lottie中的體現就是to和ti兩個參數,它們是和當前控制點相關的路徑貝塞爾曲線的控制點。

揭秘!如何將動效描述自動轉化為動效代碼-AI.x社區圖片

2.2 可復用資產 assets

layers里面描述的圖層信息有時會包含對外部資源的引用,比如圖像圖層會引用一張外部圖片,預合成圖層會引用一份預合成。這些被引用的資源描述都會存放在assets里。

關于預合成

預合成圖層是Lottie中一個比較特殊的圖層類型。一般情況下,Lottie是從設計師在AE中編輯的合成來導出的,但就像程序員寫的函數中可以調用其他的函數一樣,合成中也可以使用其他的合成,合成中引用的其他合成,就是預合成圖層,它是該合成的外部資源,因此存放在Lottie的assets屬性里;它的內容是另一個合成,因此Lottie里該圖層信息的描述方式和一個單獨的Lottie類似;預合成作為一個單獨的合成,當然也可以引用其他的合成,因此嵌套的預合成也是允許存在的。

在實現預合成圖層中的圖層動畫時,我們不單要關注這個圖層本身的Transform和透明度變化,還要關注它所在的合成被上層合成引用的預合成圖層的Transform和透明度變化。

三、從Lottie導出動效代碼

從上一章的Lottie格式的介紹中,我們了解了Lottie中的動畫描述方式,以及每個動畫元素(圖層)中的關鍵動畫信息,比如開始幀,結束幀,緩動函數控制點以及屬性關鍵幀的數值等等。

現在我們已經從Lottie中獲得了動效代碼所需的完備信息,可以開始進行動效代碼的生成了。

3.1 CSS代碼生成

逐幀方案

最符合直覺最簡單的從Lottie導出CSS動效代碼的方式可能就是逐幀記錄CSS關鍵幀的方式了。我們可以計算一個圖層從出現到消失每一幀transform和opacity的值,然后記錄在CSS keyframes里。

如下圖所示的就是使用逐幀記錄CSS關鍵幀方式還原的Lottie動畫效果:

  • Lottie效果:

揭秘!如何將動效描述自動轉化為動效代碼-AI.x社區圖片

  • 代碼片段:
// in layers
{
    "ddd": 0,
    "ind": 2,
    "ty": 2,
    "nm": "截圖103.png",
    "cl": "png",
    "refId": "image_0",
    "sr": 1,
    "ks": {
        "o": {
            "a": 1,
            "k": [
                {
                    "i": {
                        "x": [
                            0.667
                        ],
                        "y": [
                            1
                        ]
                    },
                    "o": {
                        "x": [
                            0.333
                        ],
                        "y": [
                            0
                        ]
                    },
                    "t": 16,
                    "s": [
                        100
                    ]
                },
                {
                    "t": 20,
                    "s": [
                        1
                    ]
                }
            ],
            "ix": 11
        },
        "r": {
            "a": 0,
            "k": 0,
            "ix": 10
        },
        "p": {
            "a": 1,
            "k": [
                {
                    "i": {
                        "x": 0.874,
                        "y": 1
                    },
                    "o": {
                        "x": 0.869,
                        "y": 0
                    },
                    "t": 8,
                    "s": [
                        414.8,
                        907.857,
                        0
                    ],
                    "to": [
                        -251.534,
                        -388.714,
                        0
                    ],
                    "ti": [
                        16,
                        -336.878,
                        0
                    ]
                },
                {
                    "t": 20,
                    "s": [
                        90,
                        1514.769,
                        0
                    ]
                }
            ],
            "ix": 2,
            "l": 2
        },
        "a": {
            "a": 0,
            "k": [
                414,
                896,
                0
            ],
            "ix": 1,
            "l": 2
        },
        "s": {
            "a": 1,
            "k": [
                {
                    "i": {
                        "x": [
                            0.667,
                            0.667,
                            0.667
                        ],
                        "y": [
                            1,
                            1,
                            1
                        ]
                    },
                    "o": {
                        "x": [
                            0.333,
                            0.333,
                            0.333
                        ],
                        "y": [
                            0,
                            0,
                            0
                        ]
                    },
                    "t": 8,
                    "s": [
                        100,
                        100,
                        100
                    ]
                },
                {
                    "t": 20,
                    "s": [
                        15,
                        15,
                        100
                    ]
                }
            ],
            "ix": 6,
            "l": 2
        }
    },
    "ao": 0,
    "ip": 0,
    "op": 49,
    "st": -95,
    "bm": 0
}
  • 逐幀CSS效果:

揭秘!如何將動效描述自動轉化為動效代碼-AI.x社區圖片

  • 代碼片段:
.hash5edafe06{
 transform-origin: 50% 50%;
 animation: hash5edafe06_kf 0.667s 0s linear /**forwards**/ /**infinite**/;
}

@keyframes hash5edafe06_kf {
 0% {
  opacity: 1;
  transform: matrix3d(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1);

 }
 15% {
  opacity: 1;
  transform: matrix3d(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1);

 }
 30% {
  opacity: 1;
  transform: matrix3d(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1);

 }
 45% {
  opacity: 1;
  transform: matrix3d(0.983,0,0,0,0,0.983,0,0,0,0,1,0,-0.847,-1.301,0,1);

 }
 60% {
  opacity: 1;
  transform: matrix3d(0.78,0,0,0,0,0.78,0,0,0,0,1,0,-16.751,-23.566,0,1);

 }
 75% {
  opacity: 1;
  transform: matrix3d(0.47,0,0,0,0,0.47,0,0,0,0,1,0,-82.509,-56.177,0,1);

 }
 90% {
  opacity: 0.3824875;
  transform: matrix3d(0.213,0,0,0,0,0.213,0,0,0,0,1,0,-146.717,120.698,0,1);

 }
 100% {
  opacity: 0.01;
  transform: matrix3d(0.15,0,0,0,0,0.15,0,0,0,0,1,0,-162.4,303.456,0,1);

 }

}

Tips

雖然是逐幀的方案,但是每秒對應30個甚至更多 CSS keyframes 中的關鍵幀的話,一方面在效果上沒有明顯提升,另一方面,也會導致生成的CSS 代碼片段更大,因此是沒有必要的,更好的方式是每秒采樣5-10個關鍵幀,然后通過設置easing function來將關鍵幀之間的插值方式設置為線性插值,這樣在擬合效果的同時,生成的CSS代碼量更少。

優點
  • 實現簡單:只需要按照固定間隔采樣并計算圖層的transform和透明度信息并組織為CSS keyframes的形式就可以擬合效果,原理簡單,易于實現。
缺點
  • 生成代碼量大:因為是每秒固定間隔采樣關鍵幀,當動畫的總時長較長的時候,采樣的關鍵幀會比較多,導致生成的代碼量也比較大。
  • 可讀性差,不易修改:逐幀方案采樣的是每幀的最終transform和透明度,相比原始的Lottie描述,會增加一些冗余信息,不利于人類理解,并且因為采樣的關鍵幀密度比較大且距離近的關鍵幀相關性高,因此導出的CSS代碼很難手動修改,比如一個只包含起點和終點關鍵幀的路徑移動的動畫,在Lottie的json中,只需要修改兩個數值就可以自然的改變動畫的終點,而要在導出的逐幀CSS中實現同樣的修改則需要修改者修改多個關鍵幀的數值,且數值的內容需要自行計算才能得到。

逐幀方案雖然可以擬合Lottie中的動畫效果,但有著生成代碼量大和可讀性差,不易修改的缺點,因此只適合時長較短且比較簡單的動效。

關鍵幀方案

那么有沒有什么方式,在保留對Lottie的擬合效果的同時,生成的代碼量更小,且可讀性更好呢?

一種可行的想法是,忠實的還原Lottie中的動畫描述,使生成的CSS keyframes中的關鍵幀以及幀間的緩動函數等和Lottie中描述的關鍵幀和緩動方式等完全對應。但遺憾的是,CSS的動畫描述方式和Lottie的動畫描述方式并不能直接對應,要將Lottie的關鍵幀動畫描述方式映射為CSS的關鍵幀動畫描述方式,我們需要做一些中間操作抹平它們的差別。

「Lottie和CSS關鍵幀動畫描述方式的差別」

從每一個幀動畫信息的描述方式來說,Lottie中的動畫描述基本都在關鍵幀信息中進行描述,包括關鍵幀對應的時間(幀數),屬性數值,時間樣條曲線(三次貝塞爾控制點)和路徑樣條曲線(應用在位移的三次貝塞爾控制點)。

而在CSS的動畫描述中,關鍵幀只描述對應的時間(百分比)和屬性數值,時間樣條曲線在在關鍵幀外的animation-easing-func里描述,路徑樣條曲線要直觀實現更是需要通過支持性不高的offset-path 和 offset-distance / motion-path 和 motion-offset 來實現,這樣的差別導致CSS的動畫描述方式不如Lottie中的描述方式靈活。

從不同屬性的動畫信息的描述方式來說,Lottie中的位移、旋轉、縮放和透明度變化分別使用不同的屬性來進行描述,如果某個屬性的不同維度需要不同的關鍵幀分布或時間插值方式來進行描述,還可以更進一步細分。比如,縮放的動畫可以s屬性來進行描述,如果2維情況下的x軸和y軸需要不同關鍵幀和插值方式,則s屬性可以被拆分為sx 和 sy 兩個獨立屬性,各自不相關的描述x軸和y軸的縮放動畫。

而在CSS中,位移、旋轉和縮放的描述都由transform屬性承接,位移、旋轉和縮放的順序和數量也不像常見的AE、Unity等軟件那樣進行約束,這讓單個Dom上的transform屬性在描述特定靜態狀態或由js進行修改達成動態效果時的描述能力上限很高,但對于使用CSS @keyframes制作動畫時則會帶來災難性的屬性耦合問題。

「示例」

考慮這樣的一個情況,一張圖片有一個總長為100幀的元素動畫,元素的2D旋轉角度在第0幀(0%)處為0deg、第5幀(5%)處為-18deg, 第10幀處為18deg, 第15幀(100%)之后為0deg,幀間使用線性插值;元素的縮放系數在第0幀(0%)處為0,第50幀(50%)處為2,第100幀(100%)處為1,幀間分別使用ease-in和ease-out插值,這樣的動畫用AE可以簡單的實現,也可以自然的導出為Lottie格式描述,但如果要用CSS動畫來描述的話,因為使用了三種時間插值函數超出了單個@keyframes的描述能力,無法使用一個動畫來進行描述;又因為動畫同時作用于縮放和旋轉這些在CSS中使用同一個屬性描述的變換,在一個Dom上使用多個動畫描述又會引入屬性值互相覆蓋的問題。

「實現方案」

總之,在將Lottie動畫轉換成CSS關鍵幀動畫時,主要有兩個需要解決的問題,第一個是不同的transform屬性在CSS動畫中內容互相耦合的問題,另一個是同一個屬性的動畫不能通過一組@keyframes應用多種時間插值曲線的問題。

對于第一個問題,我們可以通過多個嵌套的Dom來進行規避,將不應耦合的屬性動畫放在不同Dom的CSS動畫中進行實現。

對于第二個問題,我們可以將應用了不同時間插值曲線的部分放在不同的@keyframes里進行描述,然后應用在同一個Dom的CSS動畫中。

如下圖所示的就是使用關鍵幀CSS還原的Lottie動畫效果:

  • 變量CSS效果:

揭秘!如何將動效描述自動轉化為動效代碼-AI.x社區圖片

  • 代碼片段:
<style>
.hash5edafe06_0{
 transform-origin: 50% 50%;
 animation: hash5edafe06_0_keyframe_0 0.4s 0.267s cubic-bezier(0.333, 0, 0.667, 1) /* forwards */;
}

@keyframes hash5edafe06_0_keyframe_0 {
 0% {
  transform: scale(1.000,1.000);

 }
 66.667% {
  opacity: 1.000;

 }
 100% {
  opacity: 0.010;
 transform: scale(0.150,0.150);

 }

}

.hash5edafe06_1{
 transform-origin: 50% 50%;
 animation: hash5edafe06_1_keyframe_0 0.4s 0.267s cubic-bezier(0.869, 0.774, 0.874, 0.951) /* forwards */;
}

@keyframes hash5edafe06_1_keyframe_0 {
 0% {
  transform: translateX(0.000px);

 }
 100% {
  transform: translateX(-162.400px);

 }

}

.hash5edafe06_2{
 transform-origin: 50% 50%;
 animation: hash5edafe06_2_keyframe_0 0.4s 0.267s cubic-bezier(0.869, -0.64, 0.874, 0.445) /* forwards */;
}

@keyframes hash5edafe06_2_keyframe_0 {
 0% {
  transform: translateY(0.000px);

 }
 100% {
  transform: translateY(303.456px);

 }

}
  
</style>
<!-- ....... -->
<!-- order matters  -->
<div class='hash5edafe06_2'>
  <div class='hash5edafe06_1'>
    <div class='hash5edafe06_0'>
      <!-- real content -->
    </div>
  </div>
</div>

Tips

從2023年7月開始,主流瀏覽器和設備開始支持CSS的animation-composition屬性,該屬性的開放讓多個動畫上對同一個屬性的賦值除了選擇覆蓋邏輯,還可選擇相加或累加邏輯,大大降低了CSS動畫的耦合問題。

在可以使用animation-composition屬性的前提下,關鍵幀方案導出的動畫可共同作用在同一個元素上:keyframe css with composition snippet 。不過考慮到該屬性的覆蓋率,很遺憾還不推薦在現階段應用在實際業務中。

「路徑的實現方式」

在上面展示的demo效果還原中涉及到了路徑動畫的還原,從起點到終點的位移并不是沿著直線移動,而是沿著特定的曲線移動。在還原這個效果前,我們首先觀察下路徑動畫在Lottie中的原始描述方式:

{
  {
    // 時間插值曲線控制點
      "i": {
          "x": 0.874,
          "y": 1
      },
      "o": {
          "x": 0.869,
          "y": 0
      },
      "t": 8,
      "s": [
          414.8,
          907.857,
          0
      ],
      // 路徑曲線控制點
      "to": [
          -251.534,
          -388.714,
          0
      ],
      "ti": [
          16,
          -336.878,
          0
      ]
  },
  {
      "t": 20,
      "s": [
          90,
          1514.769,
          0
      ]
  }
}

Lottie中的路徑曲線也是由三次貝塞爾曲線來進行描述的,而三次貝塞爾曲線則通過它的兩個控制點進行描述。而根據貝塞爾曲線的定義,我們可以發現,N維貝塞爾曲線的維度之間是互相獨立的,這意味著2D平面上的曲線路徑可以通過拆分的x軸和y軸位移來進行重現,如上面demo中.hash5edafe06_1 和 .hash5edafe06_2 中的內容可以重現原Lottie的曲線路徑。

不過需要注意的是,路徑曲線上的時間曲線并不是簡單對應于路徑貝塞爾曲線的變量t , 而是對應于路徑曲線長度的百分比位置,因此路徑曲線上的時間插值曲線并不能完全重現,只能盡量擬合。

優點
  • 關鍵幀的數量相比逐幀CSS更少,語義更加清晰,方便修改
  • 生成的代碼體積更小,可以降低使用者的使用負擔
缺點:對較為復雜的動畫效果,可能會生成需要應用在多個Dom上的動畫代碼,會引入一定的使用成本

Tip:關于貝塞爾曲線

貝塞爾曲線是樣條曲線的一種,它的優點是使用靈活和實現直觀,貝塞爾曲線的使用非常廣泛,它被用作一些其他樣條曲線的一部分(如B樣條, 優化了高階貝塞爾曲線的耦合問題),也是動效領域的一種通用曲線實現方案,在動畫插值(如CSS關鍵幀插值, 模型動作關鍵幀插值),矢量繪制(如路徑移動, 字體字形描述)等方面均有重要應用。

貝塞爾曲線的一般描述方程如下:

揭秘!如何將動效描述自動轉化為動效代碼-AI.x社區圖片

該描述方程中不存在矩陣計算部分,因此貝塞爾曲線在N維空間中的描述方式都是統一的,且各個維度坐標(e.g. x/y/z)的數值計算互相獨立。

變量方案

關鍵幀方案生產的代碼可能存在需要多個Dom共同作用來實現一個元素動畫的情況,這是它的最大缺點,而這個問題的根本原因就在于前面提到過的「不同的transform屬性在CSS動畫中內容互相耦合」,如果可以將它們解藕,則我們就不會不得不使用多個Dom來避免屬性覆寫,可以簡單的通過一個Dom上使用多個@keyframes來實現目標,避免對應用動畫的元素UI結構的影響。

CSS Houdini API提供的@property 為解藕提供了一種方式:我們可以用它定義諸如 --scaleX , --translateX 之類的CSS屬性,需要動畫的元素并不直接在動畫的關鍵幀中設置transform 或 opacity的值,而是這些屬性的值,然后在CSS動畫的外部將transform 或 opacity 的值用這些屬性的值來進行設置,這樣,就可以在避免耦合的情況下,在同一個Dom中實現復雜的動畫效果了。

如下圖所示的就是使用變量CSS還原的Lottie動畫效果:

  • 關鍵幀CSS:

揭秘!如何將動效描述自動轉化為動效代碼-AI.x社區圖片

  • 代碼片段:
@property --translateX {
    syntax: '<number>';
    inherits: false;
    initial-value: 0;
}
@property --translateY {
    syntax: '<number>';
    inherits: false;
    initial-value: 0;
}
@property --scaleX {
    syntax: '<number>';
    inherits: false;
    initial-value: 1;
}
@property --scaleY {
    syntax: '<number>';
    inherits: false;
    initial-value: 1;
}
@property --opacity {
    syntax: '<number>';
    inherits: false;
    initial-value: 1;
}

.ba522056 {

    transform: translateX(calc(1px *var(--translateX))) translateY(calc(1px *var(--translateY))) scaleX(calc(var(--scaleX))) scaleY(calc(var(--scaleY)));

    opacity: calc(var(--opacity));


    animation: ba522056_opacity_0 0.13333333333333333s 0.5333333333333333s cubic-bezier(0.333, 0, 0.667, 1) forwards, ba522056_translateX_0 0.4s 0.26666666666666666s cubic-bezier(0.869, 0.774, 0.874, 0.951) forwards, ba522056_translateY_0 0.4s 0.26666666666666666s cubic-bezier(0.869, -0.64, 0.874, 0.445) forwards, ba522056_scaleX_0 0.4s 0.26666666666666666s cubic-bezier(0.333, 0, 0.667, 1) forwards, ba522056_scaleY_0 0.4s 0.26666666666666666s cubic-bezier(0.333, 0, 0.667, 1) forwards
}


@keyframes ba522056_opacity_0 {

    0% {
        --opacity: 1;
    }
    100% {
        --opacity: 0.01;
    }

}

@keyframes ba522056_translateX_0 {

    0% {
        --translateX: 0;
    }
    100% {
        --translateX: -162.4;
    }

}

@keyframes ba522056_translateY_0 {

    0% {
        --translateY: 0;
    }
    100% {
        --translateY: 303.456;
    }

}

@keyframes ba522056_scaleX_0 {

    0% {
        --scaleX: 1;
    }
    100% {
        --scaleX: 0.15;
    }

}

@keyframes ba522056_scaleY_0 {

    0% {
        --scaleY: 1;
    }
    100% {
        --scaleY: 0.15;
    }

}

優點

  • 可讀性進一步提高
  • 不會發生需要多個Dom來解一個元素上的復雜動畫耦合問題的情況
缺點:CSS的@property 仍屬于實驗性能力,兼容性不好。

3.2 總結

總的來說,最理想的解決方案是變量方案,但因為使用了比較新的CSS功能,所以兼容性不佳。關鍵幀方案適合動畫拆解比較簡單不會引入輔助嵌套Dom的場景或不介意引入輔助Dom的場景。逐幀方案適合動畫持續時間不長且不需要關鍵幀數值修改的場景,也可作為兜底的解決方案。

揭秘!如何將動效描述自動轉化為動效代碼-AI.x社區圖片

React Native Animated代碼生成

React Native Animated的描述能力比CSS更強,可以自然映射Lottie中互相獨立的位移、旋轉等非耦合Transform動畫,也可以自然映射Lottie中每一個相鄰關鍵幀之間的段上應用不同時間插值曲線的情況,唯一遜于Lottie動畫描述能力的地方在于路徑動畫的描述上,不過要實現我們上面提到的CSS程度的路徑動畫還原的話,仍是非常簡單的,其實現方式和上面提到的方式并無不同。

如下圖所示的就是使用React Native Animated還原的Lottie動畫效果:

揭秘!如何將動效描述自動轉化為動效代碼-AI.x社區圖片

  • 代碼片段:
function useLayerAnimated() {
    const opacityVal = useRef(new Animated.Value(1.00)).current;;
    const translateXVal = useRef(new Animated.Value(0.00)).current;;
    const translateYVal = useRef(new Animated.Value(0.00)).current;;
    const scaleXVal = useRef(new Animated.Value(1.00)).current;;
    const scaleYVal = useRef(new Animated.Value(1.00)).current;

    const getCompositeAnimation = useCallback(() => {
        
        const opacityAnim = 
            Animated.timing(opacityVal, {
                toValue: 0.01,
                duration: 133.333,
                useNativeDriver: true,
                delay: 533.333,
                easing: Easing.bezier(0.333, 0, 0.667, 1),
            })
                
        ;

        const translateXAnim = 
            Animated.timing(translateXVal, {
                toValue: -162.40,
                duration: 400,
                useNativeDriver: true,
                delay: 266.667,
                easing: Easing.bezier(0.869, 0.774, 0.874, 0.951),
            })
                
        ;

        const translateYAnim = 
            Animated.timing(translateYVal, {
                toValue: 303.46,
                duration: 400,
                useNativeDriver: true,
                delay: 266.667,
                easing: Easing.bezier(0.869, -0.64, 0.874, 0.445),
            })
                
        ;

        const scaleXAnim = 
            Animated.timing(scaleXVal, {
                toValue: 0.15,
                duration: 400,
                useNativeDriver: true,
                delay: 266.667,
                easing: Easing.bezier(0.333, 0, 0.667, 1),
            })
                
        ;

        const scaleYAnim = 
            Animated.timing(scaleYVal, {
                toValue: 0.15,
                duration: 400,
                useNativeDriver: true,
                delay: 266.667,
                easing: Easing.bezier(0.333, 0, 0.667, 1),
            })
                
        

        return Animated.parallel([
            opacityAnim, translateXAnim, translateYAnim, scaleXAnim, scaleYAnim
        ]);
    }, []);

    const style = useRef({
        
        transform: [
                        {translateX: translateXVal}, {translateY: translateYVal}, {scaleX: scaleXVal}, {scaleY: scaleYVal},            
        ],
    
        opacity: opacityVal
    }).current;



    const resetAnimation = useCallback(() => {
        opacityVal.setValue(1.00);
        translateXVal.setValue(0.00);
        translateYVal.setValue(0.00);
        scaleXVal.setValue(1.00);
        scaleYVal.setValue(1.00)
    }), [];

    return {
        animatedStyle: style,
        resetAnim: resetAnimation,
        getAnim: getCompositeAnimation,
    }

};

四、平臺集成

目前從Lottie導出CSS/Animated代碼的能力已經集成到公司內部的Vision動效平臺中,作為公司內動效整體解決方案的一部分。

平臺中的出碼能力詳細使用方式見:??快手前端動效大揭秘:告別低效,vision平臺來襲!??

揭秘!如何將動效描述自動轉化為動效代碼-AI.x社區圖片

在下期內容中,我們將重點介紹Vision 動效平臺在序列幀動效格式轉換方面的能力和流程:動效平臺通過提供多種序列幀格式自動轉換功能,優化動效交付流程,提高動效的兼容性和性能。敬請期待!

- END -

收藏
回復
舉報
回復
相關推薦
日韩精品亚洲专区| 精品国产91久久久久久浪潮蜜月| 亚洲综合色噜噜狠狠| 国产精品久久国产精品| 精品久久久久久久久久久国产字幕| 成人一区二区| 精品国产区一区| 丰满少妇被猛烈进入高清播放| 免费在线观看黄色网| 成人毛片视频在线观看| 国产精品久久久久久久久久尿| 伊人在线视频观看| 日韩中文av| 欧美一区二区三区公司| 99精品视频播放| 黄色网页在线观看| 久久中文字幕电影| 亚洲伊人一本大道中文字幕| 天堂网中文字幕| 黄色av成人| 色老头一区二区三区| 岛国精品资源网站| 九九九九九九精品任你躁| 色先锋久久av资源部| www.欧美黄色| 麻豆网在线观看| 久久久国产综合精品女国产盗摄| 亚洲精品免费网站| 中文字幕在线播放不卡| 国产亚洲精品久久久久婷婷瑜伽| 欧美精品一区在线播放| 国产三级在线观看完整版| 日韩成人av在线资源| 欧美mv和日韩mv国产网站| 国内外成人免费在线视频| 欧美黑人粗大| 色综合久久久久综合体| 久久久性生活视频| 美女91在线| 亚洲色图欧美在线| 亚洲视频精品一区| 在线免费观看黄| 国产亚洲综合性久久久影院| 国产一区二区久久久| 亚洲精品国产一区二| 国产一区在线观看麻豆| 91精品视频在线| 97人妻精品一区二区三区| 免费人成网站在线观看欧美高清| 国产成人综合精品| 精品人妻一区二区三区潮喷在线 | 影视一区二区三区| 日韩欧美在线观看视频| 日韩av资源在线| 桃色av一区二区| 欧美日韩免费一区| 成人综合视频在线| 韩日成人影院| 欧美性受xxxx| 岛国av免费在线| 国产精品日韩精品在线播放| 欧美精品vⅰdeose4hd| 成 人 黄 色 小说网站 s色| 欧美xxxx网站| 91精品综合久久久久久| 免费看的av网站| 91蝌蚪精品视频| 亚洲精品在线免费观看视频| 国产不卡一二三| 色综合综合色| 精品国产一区二区三区久久狼5月 精品国产一区二区三区久久久狼 精品国产一区二区三区久久久 | 亚洲精品一区二区妖精| 久久久精品国产亚洲| 日韩影院一区二区| 亚洲精品护士| 国产精品高潮呻吟视频| 亚洲专区第一页| 成人综合在线观看| 蜜桃欧美视频| 日本在线视频观看| 亚洲综合成人在线视频| 国产中文字幕免费观看| 久久av影院| 日韩精品综合一本久道在线视频| 人妻 日韩 欧美 综合 制服| 日韩三级视频| 视频在线观看一区二区| 国产一级做a爱免费视频| 亚欧美中日韩视频| 91免费看国产| 日本一级在线观看| 中文字幕一区视频| 国产资源在线视频| 日本一区二区三区中文字幕| 日韩丝袜情趣美女图片| 在线免费观看成年人视频| 日韩欧美综合| 91国语精品自产拍在线观看性色| 最近日韩免费视频| 粉嫩一区二区三区在线看| 日本不卡一区二区三区在线观看| 1区2区3区在线观看| 亚洲一区二区三区爽爽爽爽爽| 欧美韩国日本在线| jizz国产精品| 日韩一区二区欧美| 久久黄色精品视频| 国产在线看一区| 欧洲精品亚洲精品| 波多一区二区| 欧美一区二区视频网站| 久久丫精品忘忧草西安产品| 亚洲午夜av| 91免费版网站入口| 福利片在线看| 日韩欧美在线免费| 中文字幕在线观看91| 欧美电影一区| 国产精品99久久久久久白浆小说| 亚洲国产成人精品一区二区三区| 国产女主播视频一区二区| 欧美日韩性生活片| 日韩免费精品| 久久综合伊人77777蜜臀| 亚洲av无码精品一区二区| 不卡的av网站| a天堂资源在线观看| 亚洲黑人在线| 中文字幕国产日韩| 欧美一区二区三区久久久| 91亚洲资源网| 男人添女人下面高潮视频| 日韩高清一区| 大量国产精品视频| 97国产成人无码精品久久久| 国产三级三级三级精品8ⅰ区| 人妻av中文系列| 成功精品影院| 午夜精品福利电影| 黑人操亚洲女人| 亚洲一区在线观看免费| 中文字幕制服丝袜| 欧美 日韩 国产一区二区在线视频 | 国产午夜精品全部视频在线播放| 成人毛片18女人毛片| 99久久99久久免费精品蜜臀| 妞干网在线观看视频| 粉嫩一区二区三区四区公司1| 久久91亚洲精品中文字幕奶水| 一区二区www| 亚洲欧美日韩中文字幕一区二区三区 | 99精品视频网站| 91九色成人| 久久国产精品久久精品| 999久久久久| 亚洲综合免费观看高清完整版在线| 成年人性生活视频| 亚洲小说欧美另类婷婷| 精品综合在线| 成人激情综合| 色婷婷av一区二区三区在线观看| 一级aaaa毛片| 一区二区在线电影| 天堂www中文在线资源| 99亚洲精品| 日韩精品欧美专区| 欧美成人毛片| 欧美高清在线视频观看不卡| 欧美在线精品一区二区三区| 精品高清一区二区三区| 国产人妻一区二区| 免费在线一区观看| 欧美 亚洲 视频| 欧美福利在线播放网址导航| 国产精品狼人色视频一区| 黄网站在线播放| 欧美精品一区二区高清在线观看| 国产无套丰满白嫩对白| 国产精品久久99| 亚洲一区和二区| 日韩av电影一区| 真实国产乱子伦对白视频| 中文字幕精品影院| 91久久精品国产91久久性色| av在线播放资源| 一区二区三欧美| 午夜精品久久久久久久99热黄桃| 激情懂色av一区av二区av| 色欲AV无码精品一区二区久久| 国产一区二区中文字幕| 国产aaa一级片| 久久久久午夜电影| 久久av一区二区| 亚洲最大的免费视频网站| 91成人在线播放| 成人在线直播| 亚洲女同精品视频| 精品人妻无码一区二区色欲产成人| 精品欧美一区二区三区| 丝袜美腿小色网| 久久久国产午夜精品| 亚洲成人av免费观看| 久久中文欧美| 91精品国产91久久久久麻豆 主演| 国语产色综合| 国产女主播一区二区| 成人免费91| 国产黑人绿帽在线第一区| 激情av在线| 久久精品久久久久久国产 免费| 天堂视频中文在线| 欧美成人三级在线| 97成人在线观看| 欧洲人成人精品| 久久一区二区三区视频| 亚洲精选免费视频| 91ts人妖另类精品系列| 久久综合狠狠综合| 影音先锋黄色资源| 国产激情91久久精品导航| 五月婷婷六月丁香激情| 国产欧美一区二区色老头| 精品视频在线观看一区二区| 999国产精品视频| 神马影院午夜我不卡| 欧美三级午夜理伦三级在线观看| 成人在线观看91| 日韩成人视屏| 亚洲字幕在线观看| 国产免费av国片精品草莓男男| 国产剧情日韩欧美| 日韩天堂在线| 国产精品第10页| 无人区在线高清完整免费版 一区二| 91av在线视频观看| 色戒汤唯在线| 91黄色8090| 色综合桃花网| 91国在线精品国内播放| 精品丝袜在线| 欧美亚洲另类制服自拍| 中文一区一区三区高中清不卡免费| 久久久久久久电影一区| 国产蜜臀在线| 久久欧美在线电影| av在线视屏| 538国产精品视频一区二区| 黄色视屏在线免费观看| 91av在线影院| 黑人巨大精品| 国产精品久久久久9999| 婷婷久久综合九色综合99蜜桃| 国产精品你懂得| 少妇高潮一区二区三区99| 成人免费网站在线| av成人综合| 久久精品中文字幕一区二区三区 | 美女av免费在线观看| 一区二区激情| 无码人妻丰满熟妇区毛片| 天堂成人国产精品一区| 国产精品v日韩精品v在线观看| 国产一区二区三区免费| 国产精品一区二区无码对白| jiyouzz国产精品久久| 最近中文字幕免费视频| 国产精品青草久久| 久久成人国产精品入口| 欧美日韩国产在线播放| 国产成人自拍偷拍| 7777精品久久久大香线蕉| 亚洲国产精品国自产拍久久| 亚洲高清久久网| 成年人在线视频免费观看| 久久久国产精品亚洲一区| ririsao久久精品一区| 青青在线视频一区二区三区| 成人福利一区二区| 亚洲永久免费观看| 亚洲人成网www| 自拍偷拍一区二区三区| 在线看片成人| 亚洲国产高清av| 成人免费黄色大片| 欧日韩不卡视频| 亚洲一区视频在线观看视频| 天天干天天色综合| 日韩一级二级三级| 视频国产在线观看| 久久伊人91精品综合网站| 女海盗2成人h版中文字幕| 国产精品一久久香蕉国产线看观看| 日韩欧美久久| 亚洲国产一区二区在线| 亚洲福利久久| 日韩在线一区视频| 91玉足脚交白嫩脚丫在线播放| 亚洲女人久久久| 欧美日韩色婷婷| 国产日韩在线观看一区| 亚洲人成电影在线播放| av在线网页| 91久久久久久久久久久久久| 亚洲人成亚洲精品| 久久福利一区二区| 九九久久精品视频| 一区二区三区四区免费| 亚洲国产日韩a在线播放| 国产精品嫩草影院桃色| 国产一区二区三区高清在线观看| 成人免费高清观看| 成人网中文字幕| 国产精品中文字幕亚洲欧美| 欧美视频免费看欧美视频| 国产乱淫av一区二区三区| 九一在线免费观看| 欧美午夜无遮挡| 天堂网2014av| 欧美高清视频一区二区| 国产一区二区视频在线看| 无遮挡亚洲一区| 丝袜美腿亚洲色图| aaaaa级少妇高潮大片免费看| 亚洲国产一区二区视频| 国产农村妇女毛片精品久久| 视频一区视频二区国产精品| 亚洲天堂一区二区| 欧美日韩一区二区三| 老司机一区二区三区| 一级欧美一级日韩片| 亚洲福中文字幕伊人影院| 国产肥老妇视频| 久久天堂av综合合色| 欧美日韩伦理一区二区| 日韩影视精品| 久久精品国产亚洲a| 天天干天天操天天拍| 欧美性感一区二区三区| av在线首页| 国产精品老女人视频| 日韩.com| 九九九九九国产| 亚洲三级在线免费观看| 99久久婷婷国产一区二区三区| 久久香蕉国产线看观看av| 亚洲天堂网站| 潘金莲一级淫片aaaaa免费看| 激情图片小说一区| 欧美日韩午夜视频| 日韩免费一区二区三区在线播放| 在线网址91| 国产美女精品久久久| 亚洲看片一区| 中文字幕高清视频| 欧美午夜不卡视频| 精品国产白色丝袜高跟鞋| 51午夜精品| 99人久久精品视频最新地址| 野花社区视频在线观看| 91久久人澡人人添人人爽欧美| 国产高清一区在线观看| 91精品久久久久久久久久久久久久| 91欧美日韩| 黑人无套内谢中国美女| 性久久久久久久| 欧洲成人av| 国产日韩在线免费| 国内精品美女在线观看| 中文字幕人妻熟女在线| 日韩欧美黄色动漫| 国产在线更新| 韩国成人av| 秋霞国产午夜精品免费视频| 日韩黄色免费观看| 日韩高清中文字幕| 成人精品一区二区三区电影| 一二三四中文字幕| 91啦中文在线观看| 中文字幕av久久爽| 欧美夫妻性生活视频| 特黄特色欧美大片| 手机精品视频在线| 欧美性猛交xxxx偷拍洗澡| 国产精品扒开做爽爽爽的视频| 国产伦精品一区二区三区视频黑人 | 久久综合五月天| 亚洲免费专区| 色偷偷中文字幕| 91福利资源站| 九色91在线| 亚洲黄色成人久久久| 成人高清伦理免费影院在线观看| 日韩欧美国产另类| 久久久免费电影| 日韩在线观看| 日本免费福利视频| 欧美一区二区美女| 久久91导航| 国产精品后入内射日本在线观看| 亚洲天堂成人网|