精品欧美一区二区三区在线观看 _久久久久国色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懂色av粉嫩av| 美女精品视频在线| 午夜精品一区在线观看| 天堂资源在线亚洲资源| 亚洲精品久久久久久久久久 | 丁香五六月婷婷久久激情| 日韩欧美一区二区视频在线播放| 国产乱叫456在线| 激情偷拍久久| 少妇高潮久久77777| 91九色蝌蚪porny| 久久国内精品| 无码av中文一区二区三区桃花岛| 亚洲国产一区二区在线| 国产综合视频在线| 极品少妇xxxx精品少妇偷拍| 97久久久免费福利网址| 成人黄色短视频| 欧洲亚洲一区二区三区| 在线不卡一区二区| 成人中文字幕av| free性m.freesex欧美| 日本一区二区成人| 国产精品一码二码三码在线| 一级片在线观看视频| 久久av在线| 91国产精品91| 国产免费无码一区二区视频| 成人在线免费小视频| 日韩av在线网| 国产香蕉精品视频| 精品入口麻豆88视频| 欧美色男人天堂| 欧美xxxxx在线视频| 国产不卡人人| 亚洲国产日韩一区二区| 伊人狠狠色丁香综合尤物| 欧美91精品久久久久国产性生爱| 国产精品888| 亚洲一区亚洲二区亚洲三区| 亚洲中文字幕一区二区| 老司机午夜精品99久久| 国产成人精品午夜| 中文字幕精品视频在线观看| 国产免费成人| 欧美一级视频在线观看| 天天操天天爽天天干| 亚洲高清在线| 午夜精品一区二区三区av| 国产亚洲欧美精品久久久www| 91精品婷婷色在线观看| 久久夜色精品国产| 三级av在线免费观看| 婷婷丁香综合| 久久天天躁日日躁| 欧美日韩免费做爰视频| 国产精品mv在线观看| 久久久久国产精品免费| 国产第一页在线播放| 亚洲国内精品| 秋霞午夜一区二区| www.久久网| 久久狠狠亚洲综合| 91在线观看免费高清| 99这里有精品视频| 国产.欧美.日韩| 国产综合色一区二区三区| 天堂在线资源库| 不卡一卡二卡三乱码免费网站| 国产精品久久久久av福利动漫| 少妇一区二区三区四区| 久久久久久久综合日本| 亚洲欧美成人一区| 中文字幕免费高清电视剧网站在线观看| 亚洲欧美日韩成人高清在线一区| 国产精品无码免费专区午夜| 色吧亚洲日本| 欧美日韩在线播| 绯色av蜜臀vs少妇| 久久不见久久见免费视频7| 日韩中文字幕视频在线| 精品无码一区二区三区电影桃花| 国产日韩一区二区三区在线| 国产精品久久久久久亚洲调教| 国产又大又长又粗| 成人av在线影院| 色播亚洲视频在线观看| av片在线观看免费| 欧美日韩国产丝袜美女| 亚洲老女人av| 粉嫩av一区二区| 一区二区成人av| 国产精品久久久精品四季影院| 99日韩精品| 成人黄色激情网| 午夜视频免费在线| 中文字幕人成不卡一区| 国产精品一区二区免费在线观看| 成人午夜sm精品久久久久久久| 欧美成人精品高清在线播放| 久久精品无码一区| 激情婷婷久久| 91久久精品在线| 日本天堂影院在线视频| 一区二区在线观看免费视频播放| 青青在线视频观看| 一本色道69色精品综合久久| 一区二区三区四区精品| 懂色av.com| 国产精品资源在线看| 欧美日韩亚洲在线| 国精一区二区三区| 欧美高清精品3d| 一区二区三区四区免费| 国内自拍一区| 91欧美激情另类亚洲| 精品无人乱码| 欧美特黄级在线| 国产视频精品视频| 永久亚洲成a人片777777| 国产精品久久久久福利| 亚洲 欧美 精品| 亚洲精品你懂的| 亚洲 中文字幕 日韩 无码| 国产色噜噜噜91在线精品| 久久天天躁狠狠躁老女人| 中文字字幕在线中文乱码| 91色porny在线视频| 无码熟妇人妻av在线电影| 国产精品18| 中文字幕日韩在线视频| 免费又黄又爽又猛大片午夜| 99这里都是精品| 男的插女的下面视频| av男人一区| 色综合久久久888| 国产高清精品软件丝瓜软件| 一区二区中文视频| 男生操女生视频在线观看| 成人在线国产| 国产欧美精品一区二区| 97超碰人人在线| 欧美伊人久久久久久午夜久久久久| 爱爱的免费视频| 午夜亚洲一区| 免费亚洲精品视频| 欧美magnet| 这里只有精品丝袜| 亚洲天堂男人网| 国产精品女同一区二区三区| 中文字幕天天干| 国产精品99久久精品| 成人国产在线视频| 亚洲夜夜综合| 欧美精品一区二区在线播放 | 欧美日韩一区二区免费视频| 成年人的黄色片| 欧美一级视频| 色一情一乱一伦一区二区三区 | 在线亚洲一区| 欧美日韩日本网| 久久亚洲资源中文字| 久久精品国产欧美亚洲人人爽| 国产精品人人妻人人爽| 一区二区三区在线观看国产 | 91极品身材尤物theporn| 国产精品久久久久久久蜜臀| 在线观看岛国av| 在线免费观看日本欧美爱情大片| caoporn国产精品免费公开| 9999在线视频| 亚洲人精选亚洲人成在线| 中文字幕人妻色偷偷久久| 日韩久久一区二区| 老司机午夜免费福利| 久久婷婷激情| 中国成人亚色综合网站 | 日韩亚洲欧美中文三级| 日产精品久久久久| 国产欧美精品国产国产专区| 爽爽爽在线观看| 99xxxx成人网| 亚洲看片网站| 农村少妇一区二区三区四区五区 | 免费精品一区二区三区在线观看| 欧美激情免费视频| 国产精品麻豆一区二区三区| 91精品麻豆日日躁夜夜躁| 久久草视频在线| 一区二区中文字幕在线| 精品中文字幕在线播放| 久久精品国产秦先生| 亚洲中文字幕无码av永久| 青青草国产免费一区二区下载| 福利视频一区二区三区| 97人人做人人爽香蕉精品| 色中色综合影院手机版在线观看| 国产小视频福利在线| 日韩三级高清在线| 自拍偷拍校园春色| 亚州成人在线电影| h色网站在线观看| 久久九九久久九九| 在线播放第一页| 老司机一区二区| 日韩手机在线观看视频| 欧美精品国产一区二区| 无遮挡亚洲一区| 国产欧美啪啪| 99在线视频播放| 九七影院97影院理论片久久| 91精品国产91久久久久| av超碰免费在线| 色播久久人人爽人人爽人人片视av| 欧美一级一区二区三区| 欧美一区二区在线免费播放 | 91精品视频网| 国产无遮挡又黄又爽又色视频| 亚洲妇熟xx妇色黄| 欧美第一页在线观看| 国产精品少妇自拍| 素人fc2av清纯18岁| 粉嫩13p一区二区三区| 亚洲综合20p| 蜜臀国产一区二区三区在线播放| 男人天堂1024| 极品裸体白嫩激情啪啪国产精品| 香蕉视频在线网址| 欧美韩国日本在线观看| 天堂一区二区三区 | 在线观看毛片视频| 在线精品视频免费观看| av中文在线播放| 精品久久久久久亚洲精品 | 欧美中文字幕一区| 高潮毛片又色又爽免费| 色综合色狠狠综合色| 国产情侣在线视频| 精品久久香蕉国产线看观看gif| 久久97人妻无码一区二区三区| 国产精品国产三级国产aⅴ无密码 国产精品国产三级国产aⅴ原创 | 日韩在线观看电影完整版高清免费| 日韩伦理一区二区三区| 久久久久久久免费| 宅男在线一区| 欧洲精品亚洲精品| 久久一区二区三区电影| 亚洲一区二区三区午夜| 国产精品毛片久久| 穿情趣内衣被c到高潮视频| 亚洲一区二区三区| 日韩在线视频在线| 亚洲国产专区| 99精品免费在线观看| 日韩精品欧美精品| 亚欧激情乱码久久久久久久久| 久久99精品久久久久久动态图| 最新天堂在线视频| 国产电影精品久久禁18| 国产艳妇疯狂做爰视频| 91一区一区三区| 久久久久无码精品国产sm果冻| 国产精品狼人久久影院观看方式| 永久免费看片视频教学| 亚洲精品久久久久久国产精华液| 久久久久久免费观看| 欧美日韩国产精品一区二区不卡中文| 国产精品一区二区三区四| 色欧美日韩亚洲| 国产又粗又大又爽视频| 精品日韩在线观看| 欧美捆绑视频| 久久精品国产电影| 超碰97免费在线| 国产精品精品国产| 久久99精品久久久野外观看| 精品日产一区2区三区黄免费| 久久99影视| 国产日韩第一页| 亚洲日本免费| 精品亚洲一区二区三区四区| 国产精品一区专区| 成人午夜剧场视频网站| 中文字幕一区不卡| 国产欧美日韩另类| 欧美日韩在线播放| 香蕉视频免费看| 色偷偷av一区二区三区| 草草在线视频| 91精品久久久久久综合乱菊 | 亚洲高潮无码久久| 鲁大师影院一区二区三区| av噜噜在线观看| 99久久99久久免费精品蜜臀| 国产视频精品免费| 精品国产乱码久久久久久婷婷| 中文字幕人妻互换av久久| 亚洲国模精品一区| h片在线免费| 国产精品久久久久久久久久99| 成人偷拍自拍| 制服丝袜综合日韩欧美| 麻豆精品网站| 免费黄色a级片| 综合色天天鬼久久鬼色| 久草视频一区二区| 亚洲成人动漫在线播放| 九七电影韩国女主播在线观看| 秋霞成人午夜鲁丝一区二区三区| 欧美久久一区二区三区| 亚洲视频sss| 天堂久久一区二区三区| 婷婷五月精品中文字幕| 亚洲女女做受ⅹxx高潮| 中文字幕av网站| 亚洲欧美激情四射在线日| 国产在线xxx| 99中文视频在线| 亚洲一区 二区 三区| 中文字幕线观看| 欧美高清在线精品一区| 国产精品熟女视频| 亚洲剧情一区二区| 欧美伦理91| 国产在线精品一区二区三区》| 欧美jizzhd精品欧美巨大免费| 高清一区在线观看| 日本一区免费视频| 日韩中文字幕高清| 亚洲天堂一区二区三区| 天堂中文在线播放| 国产亚洲欧美一区二区| 亚洲无线一线二线三线区别av| 天堂网成人在线| 亚洲男人的天堂av| 国产欧美久久久精品免费| 久久精品视频中文字幕| 亚洲伊人精品酒店| 熟女视频一区二区三区| 极品少妇一区二区三区精品视频| 在线观看天堂av| 欧美久久久影院| 91亚洲天堂| 2014国产精品| 国内精品嫩模av私拍在线观看| 韩国一区二区三区四区| 亚洲777理论| 日本一区二区三区在线观看视频| 啪一啪鲁一鲁2019在线视频| 国产精品一区2区3区| 久草福利视频在线| 国产精品久久久久久久久快鸭 | 亚洲天堂av在线免费观看| 午夜欧美巨大性欧美巨大| 欧洲国产精品| 九九视频精品免费| 国产高潮国产高潮久久久91 | 亚洲乱亚洲乱妇| 91热福利电影| 在线欧美不卡| 成人国产精品久久久网站| 欧美三级三级三级爽爽爽| 老司机在线永久免费观看| 91久久夜色精品国产网站| 黄色成人在线网站| 国产亚洲无码精品| 欧美日精品一区视频| 黄色动漫在线| 国产欧美日韩在线播放| 久久青草久久| 我要看黄色一级片| 亚洲福利精品在线| 精品3atv在线视频| 好吊色这里只有精品| 成人av在线播放网址| 成人午夜精品视频| 毛片精品免费在线观看| 久久精品色综合| 免费涩涩18网站入口| 一区二区国产视频| 国产人成在线观看| 999日本视频| 久久精品系列| 清纯粉嫩极品夜夜嗨av| 亚洲欧美综合v| 日韩精品一区二区三区中文字幕 | 久久er精品视频| 日韩免费一二三区| 中文字幕日韩欧美在线| 亚洲国产中文在线| 国产精品久久久久9999小说| 亚洲综合一区二区三区| 成人精品一区二区三区免费| 成人91视频| 久久精品国产亚洲高清剧情介绍| 国产无遮挡又黄又爽| 日韩亚洲精品电影| 蜜桃一区二区三区|