天天看點

開發與設計:一站式動效高效協同解決方案

作者:閃念基因

1 前言

在現代網際網路應用中,大至鍊路的銜接,小至一個按鈕的點選響應,動效是體驗無處不在的潤滑劑,為使用者每一步的操作提供了合理的預期與過渡。而通過動效銜接不同界面或不同響應狀态,無論對流暢直覺地表達流程意圖,還是精雕細琢讓體驗更絲滑的微動效,動效設計都是應用中非常重要的一環。

在業務落地動效過程中,總是伴随着三大痛點:實作成本高、溝通成本高和性能難以保證。本文将圍繞這個三個點進行剖析解決方案,提高動效的傳遞效率。

開發與設計:一站式動效高效協同解決方案

圖1 動效示範圖檔

2 背景與問題

在面向C端的業務中,許多頁面中都需要添加動畫效果(以下統稱動效),來加強頁面元素的表現力。但是在動效子產品的協作溝通經常會出現以下幾種情況,導緻添加動效會成為一個高成本的事情。同時對于已使用過的動效沒有沉澱為設計資産,導緻出現重複設計與開發的事情,加大了團隊的投入成本。

2.1 業務場景

業務夥伴覺得界面轉場不夠絲滑,害怕使用者在該環節造成較多的流失,想在該環節加入動效提高界面表現力,挽留使用者進入下一階段,在傳遞動效時,往往可能會出現一下幾個情況:

情況一:找一個第三方平台的效果,設計夥伴讓開發夥伴按照第三方動效GIF圖檔進行開發,後續進行口頭溝通驗收

情況二:設計夥伴與業務夥伴溝通動效内容并制作對應的MP4示範檔案,給到相應的素材,讓開發對照MP4進行實作,後續通過比對MP4進行效果驗收

情況三:設計夥伴給到靜态素材和部分文字參數資訊,讓開發夥伴進行組合資訊實作動效内容

就此我們可以看到進行支援動效内容的接入,要處理的問題非常多,要花費很大的成本進行溝通、了解、驗收,無法進行快速高效的傳遞動效子產品。

開發與設計:一站式動效高效協同解決方案

圖2 動效業務背景

2.2 帶來的問題

  • 溝通成本高:在動效子產品的協作中,由于缺乏統一且有效的溝通方式,導緻動效的設計、開發和驗收過程變得複雜,增加了溝通成本。
  • 動效設計資産未沉澱:現有的動效設計方式,使得動效無法成為可複用的設計資産,進而導緻重複設計和開發,增加了團隊投入成本。
  • 動效品質控制難:由于動效的複雜性和易變性,僅通過口頭溝通或MP4示範檔案進行驗收,難以確定動效品質。
開發與設計:一站式動效高效協同解決方案

圖3 動效協作中的問題

2.3 問題的根本原因

首先我們要解決高效傳遞動效的問題,要先找到導緻這個問題的根本原因是什麼?

從上述描述中不難看出,設計與開發關于動效協作主要問題發生在示範demo不完善、動效參數、素材不符合規範、驗收成本較大的4個問題上。

本着發現問題,解決問題的原則。我們分析流程中每個環節,通過分析可以得到在最關鍵的點就是輸出與輸入時都不存在動效标準,設計與開發都隻能對着效果圖進行調試參數,進而達到預期效果,這也導緻驗收階段出現開發與設計對于動效表現無法達成一緻的問題。

開發與設計:一站式動效高效協同解決方案

圖4 設計與前端夥伴協作流程

3 方案設計與實作

接下來就開始針對上訴問題,來思考解決方案。在講方案内容之前,我們需要先看下目前行業内有哪些動效的格式,我們需要從中調選出符合目前公司業務和契合團隊成本的動效格式。

3.1 常見的動畫格式

3.1.1 GIF與APNG

GIF:是在1987年由 Compu Serve 公司為了填補跨平台圖像格式的空白而發展起來的。GIF 可以被 PC 和 Mactiontosh 等多種平台上被支援。

  • 優點:高相容性、接入成本低
  • 缺點:最高支援256種顔色、記憶體占用高、複雜動畫檔案資源較大

APNG:誕生于2004年,是一個基于 png 的位圖動畫格式,擴充方法類似主要用于網頁的 GIF 89a,仍對傳統PNG保留向下相容,2017年主流浏覽器幾乎都已經支援 APNG。

  • 優點:高相容性、接入成本低、色彩範圍覆寫廣,支援透明
  • 缺點:記憶體占用高、複雜動畫檔案資源較大、互動性差

3.1.2 Lottie

Lottie 由 Airbnb 推出,并且迅速在國内外各種大小廠快速推廣開來,目前已經是一個非常普遍常用的格式。

  • 優點:檔案資源小、支援互動操作、支援動态更新能力
  • 缺點:3D效果支援較弱、部分效果記憶體占用高

3.1.3 SVGA

針對 Lottie 對緩動曲線解析差帶來的性能問題和穩定性問題,我們會有第二種備選方案是 SVGA,不管是導出之後的記憶體占用,還是在各個端的表現穩定性都會好很多。

  • 優點:檔案資源小、支援互動操作、支援動态更新能力
  • 缺點:社群支援程度弱、AE支援特性較少、架構文檔不完善

3.1.4 MP4

MP4 是一套用于音頻、視訊資訊的壓縮編碼标準,由國際标準化組織(ISO)和國際電工委員會(IEC)下屬的“動态圖像專家組”(Moving Picture Experts Group,即MPEG)制定,第一版在1998年10月通過。目前已經是一個非常普遍常用的格式

  • 優點:高相容性、檔案資源小、性能表現高、支援所有動畫呈現
  • 缺點:不支援透明、不支援互動操作

3.2 選擇 Lottie 的原因

3.2.1 Lottie 的優勢

  • 平台相容性好:支援H5、小程式、APP多平台,AE支援特性是大于APNG/GIF/SVGA,小于MP4
  • 檔案資源小:優于GIF、APNG
  • 互動性強:對比GIF、APNG、MP4,Lottie 互動性更強,适應場景更廣
  • 沉澱資産:lottie 相比其他格式,部分場景可脫離設計稿進行二次更新,更有利于資産沉澱,建設研發成本

3.2.2 Lottie 的劣勢

  • 記憶體占用高:相比 SVGA 與 MP4 動畫格式,在用戶端記憶體占用會相對較高,影響性能表現
  • 效果存在相容性:通過 AE 制作動畫隻能通過 Bodymovin 導出,并且存在部分3D效果不支援的情況

了解到所有的動畫格式後,經過優劣勢的對比,我們選擇了lottie方案,選擇它的理由主要是以下幾點:

  • 現有業務在使用3D效果場景較少,不需要考慮該技術點的相容性問題
  • 團隊夥伴接入 Lottie 技術成本較少 可以快速落地至業務中
  • Lottie 支援脫離設計夥伴進行二次變更與沉澱資源,便于快速修改與複用
  • 綜合性能、檔案體積、互動性、可複用程度等考慮因素,Lottie 是當下較優的選擇

3.3 方案設計

通過標明 Lottie 作為動畫格式後,下面就需要圍繞這個格式設計對應的方案,針對輸出與輸入格式不一緻的核心問題,将通過以下的三個環節設計來解決。

3.3.1 三大環節

  • 建立動效輸出規範:制定統一的動效設計規範,包括動效類型、時長、顔色等,形成動效庫。這将有助于降低溝通成本,提高動效的複用性。
  • 建立互動式動效平台:借助互動式動效設計平台,設計夥伴可以更直覺地設計和展示動效,同時也能讓業務夥伴更好地了解和參與動效設計過程。
  • 制定動效驗收标準:制定明确的動效驗收标準,如動效時長、播放順序、視覺效果等,以便于驗收時能有據可依,確定動效品質。
開發與設計:一站式動效高效協同解決方案

圖5 協作解決方案泳道圖

3.3.1.1 動效輸出端規範

Lottie 動效都是由 AE 軟體進行制作,在 AE 中有一個 Bodymovin 插件可以将動效導出為一份 JSON 檔案,這樣就可以讓設計夥伴導出的動效都是同一份标準。

開發與設計:一站式動效高效協同解決方案

圖6 設計與前端夥伴的新協作流程

3.3.1.2 互動式動效平台

建立動效資産管理平台,可支援設計夥伴上傳動效單元與預覽動效效果,确認設計、開發、業務夥伴對于動效互動方式達成一緻,避免重複投入的溝通成本。同時可以将已有動效進行沉澱,便于後續的快速複用,降低團隊研發成本。

開發與設計:一站式動效高效協同解決方案

圖7 加入平台的新協作流程

3.3.1.3 動效驗收标準

在解決設計側的輸出規範,通過互動式動效平台進行導入,完成動效單元的确認後,即可按照規範輸出給開發夥伴,讓開發夥伴拿到符合規範的動效單元ID,完成整體流程的串聯。

3.3.2 平台功能

通過上述分析,我們需要建立動效平台來管控設計動效标準、業務對齊的動效産物和開發夥伴需要的動效單元ID,下面我們就來看下動效平台需要做哪些工作,才能完成上述的方案。

接下來就讓我們建設一款基于 Lottie 的動效設計平台,快速生成設計師想要的動态效果,極大地提高了設計效率和設計還原度。作為一站式動效制作平台,通過大量的動效素材以及可視化編輯能力,幫助沉澱資産快速得到複用,提高動效子產品的傳遞效率。

3.3.2.1 需要的功能

  • 動效編輯功能
    • 顔色/圖層編輯
    • 基本資訊編輯
  • 動效預覽功能
    • 支援動畫的執行與暫停
    • 支援動畫的多場景預覽
  • 導入導出功能
    • 支援資産的接入與下載下傳
    • 支援未确定的動畫進行預覽确認

3.3.2.2 需要的編碼工作

  • Lottie JSON 内容解析
    • 支援 layers assets shapes 多類型的解析工作
  • Lottie preview 預覽畫布
    • lottie-web 各類 api 接入完成
    • 控制器相關子產品編碼
  • JSON轉APNG/GIF功能
    • 支援簡單類的動畫可以直接投入業務使用

3.3.3 平台實作

下面主要是介紹針對上面的功能設計需要做的核心編碼工作,由于篇幅受限我們主要講訴部分核心子產品的代碼,主要是解析和渲染子產品的處理邏輯。

3.3.3.1 架構圖

平台架構主要分為6層,包含管理端與移動端整體環節(虛線部分表示目前開發中)。

開發與設計:一站式動效高效協同解決方案

圖8 平台系統架構圖

3.3.3.2 核心代碼 - 解析子產品

核心的内容主要集中在 JSON 内容的解析子產品與子產品渲染更新環節上,下面我們就來看下該子產品的核心實作吧。

要編寫解析子產品的代碼,首先我們要知道 Lottie JSON 内容本身的含義,這樣我們才能針對不同類型的節點進行不同操作的編輯處理。

lottie 動畫 Json 檔案共分為4層結構:

  • 結構層:可以讀取到動畫畫布的寬高、幀數、背景色、時間、起始關鍵、結束幀等
  • assets:圖檔資源資訊集合,這裡放置的是制作動畫時引用的圖檔資源
  • layers:圖層集合,這裡可以擷取到圖層,每個圖層的開始幀、結束幀等
  • shapes:元素集合,内部包含圖層動畫路徑已經填充的顔色與内容等資訊

lottie json内容結構:

"v": "5.6.10",   // 使用bodymovie插件的版本
"fr": 32,       // 幀速率
"ip": 0,         // 合成開始時間
"op": 64,       // 合成持續時間
"w": 750,       // 合成寬度
"h": 1334,       // 合成高度
"nm": "合成 1",   // 合成名
"ddd": 0,       // 是否3d圖層
"assets": []    // 使用的資源
"layers": []     // 圖層集合
"shapes": []     // 元素集合
"markers": []    // 蒙層集合           

擷取到 lottie json 檔案内容後,首先将 assets 進行轉換為 object,便于後續通過refId 進行查詢到對應節點,接下來進行遞歸解析最外層的 layers 字段,通過 refId 與 layers 判定是否存在子圖層節點,并在查詢過程中寫入 indexKey 字段,來保證查詢到每個階段都有唯一的辨別,函數執行完成後,将會得到一個 Tree Data 資料。

lottie結構解析函數:

async function resolvingLottieJson(data: string) {
  // 首先找到 -> layers -> refId -> assets -> layers >> 複合圖層/資源圖層
  // 其次路徑 -> layers -> layers -> shapes >> 元素圖層
  const info: any = JSON.parse(data)
  treeMeatData.value = info
  const assetsMap = keyBy(info.assets, 'id')
  let result = []


  async function queryInfo(list: any, parentNodeKey: string) {
    const infoTree = []
    for (let index = 0; index < list.length; index++) {
      const item = list[index]
      item.indexKey = `${parentNodeKey}${parentNodeKey ? '-' : ''}${index}`
      item.cl = `layer-inactive ${item.cl ? `${item.cl} ` : ''}class_${item.indexKey}`


      if (item.refId) {
        const itemAssets = assetsMap[item.refId]
        if (itemAssets.layers) item.layers = await queryInfo(itemAssets.layers, item.indexKey)
      } else if (item.layers) {
        item.layers = await queryInfo(item.layers || [], item.indexKey)
      }
      if (item.ty !== undefined) infoTree.push(item)
    }
    return infoTree
  }


  result = await queryInfo(info.layers, '')
  treeData.value = result


  return info
}           

通過解析函數的支援,将會得到一份 tree 的資料,下面就可以針對節點加入編輯相關的功能。

解析結構 解析資料
開發與設計:一站式動效高效協同解決方案
開發與設計:一站式動效高效協同解決方案

圖9 Lottie 檔案解析結構示意

3.3.3.3 核心代碼 - 渲染子產品

在通過解析子產品為每個節點建立唯一辨別與結構後,下面要處理實時編輯與更新功能,可通過兩種方式來完成實時渲染和資料同步。

畫布初始化渲染-初始化lottie渲染:

lottie.loadAnimation({
  container: document.getElementById('preview-lottie'),
  renderer: 'svg',
  loop: lottieLoop.value,
  autoplay: true,
  animationData: data
})           

方式一:修改 json fb内容,來完成動态渲染

通過操作圖層所挂載的 indexKey 來查詢到圖層節點,再通過 refId 關聯的 assets 找到指定的資源資訊,拿到指定字段 p 更換為指定資源連結,完成圖層節點的更新。

JSON操作方式:

// 找到對應 json 節點直接替換 p 屬性
const asset = lottieData.assets.find(a => a.id === '7')
asset.p = '/himo/light_bg.png'


lottie.loadAnimation({
  container: document.getElementById('preview-lottie'),
  animationData: json
})           

方式二:修改 JS 對象,來完成動态渲染

通過操作圖層所挂載的 cl 字段來查詢到圖層渲染的 DOM 元素,再通過 setAttribute 方法來修改 DOM 節點的屬性内容來實作動态更新的内容,需要注意的是該方式不适合小程式環境。

JS操作方式:

anim.addEventListener('DOMLoaded', () => {
  if (anim.renderer.rendererType === 'canvas') { // canvas 模式下的圖檔替換
    anim.renderer.elements[currentEleIndex].img.src = '/himo/light_bg.png'
  } else { // svg 模式下的圖檔替換,前兩個參數為固定值
    anim.renderer.elements[currentEleIndex].innerElem.setAttributeNS(
      'http://www.w3.org/1999/xlink',
      'href', 
      '/himo/light_bg.png'
    )
  }  
})           

注意點:在 canvas 模式下替換的圖檔需要保持與原圖檔尺寸一緻,否則實際動畫效果會有問題;svg 模式下則受益于 svg image 元素的特性,不需要如此強的限制。

3.3.3.4 核心功能 - 動态更新圖檔

目前已知 Lottie 具有這個三個階段,現在可以根據不同階段提供對應的功能。

在常見的業務場景中都有動效在生産環境運作,但此時也有業務方修改動畫資源的訴求,這個時候就需要平台具有指定動效幀數進行資源編輯的功能。

開發與設計:一站式動效高效協同解決方案

圖10 Lottie運作流程

由于部分動效素材是在不同的幀位置才出現,為了支援所有的素材都支援編輯操作,需要建立指定動效幀數的操作控件,選取指定幀後,對該位置的素材進行編輯操作。

指定動效幀數:建立可指定跳轉幀數的控制函數,與建立進度條控件支援界面操作。下面我們來看下具體代碼實作。

幀數功能代碼:

<template>
  <div class="control-panel">
    <div class="play" @click="lottiePlay">
      <img :src="playIcon" alt="" />
    </div>
    <div class="frame">
      <a-slider
        v-if="lottieFrame > 0"
        :value="currentFrame"
        :max="lottieFrame"
        :tooltip-open="true"
        @change="framgChange"
        />
    </div>
    <div class="loop" @click="setLoop">
      <img :src="loopIcon" alt="" />
    </div>
  </div>
</template>
<script lang="ts" setup>
  // 幀數變化響應函數
  function framgChange(frameNumber: number) {
    // 情況一:如在播放狀态 則跳轉指定幀數進行播放
    // 情況二:如在暫停狀态 則跳轉指定幀數進行暫停
    if (playState.value) lottieAnimation.value.goToAndPlay(frameNumber, true)
    else lottieAnimation.value.goToAndStop(frameNumber, true)
    currentFrame.value = frameNumber
  }
</script>           

資源編輯功能:根據選中的圖層節點進行建立對應的配置操作表單,通過表單控件來編輯動效資源資訊。

  • 首先根據選中的圖層節點來初始化配置表單
  • 再開始監聽配置表單項,識别到資源背景圖檔發生變化,進行 DOM 節點更新與 Lottie JSON 中繼資料進行同步

資源編輯功能:

// 選中對應的操作圖層節點
async function activeLayerNode(key: any, data: any) {
  const item = data.node.dataRef


  let backgroundImage = ''
  if (item.ty === 2) {
    const assetsMap = keyBy(treeMeatData.value.assets, 'id')
    // 圖檔節點
    backgroundImage = assetsMap[item.refId].p
  }


  editConfig.value = {
    ty: item.ty,
    backgroundImage
  } // 建立對應的編輯配置内容
  activeNode.value = item
}


// 監聽editConfig配置對象的變化
watch(
  editConfig,
  async (value, oldValue) => {
    const assetsMap = keyBy(treeMeatData.value.assets, 'id')
    if (activeNode.value.ty === 2) {
      const image = editConfig.value.backgroundImage


      // 通過JS對象來更新DOM界面
      const element = document.querySelector(`.class_${activeNode.value.indexKey} > image`)
      element.setAttribute('href', image)


      // 查詢原始lottie資料 進行更新處理
      let isQuerySuccess = false
      const queryInfo = async (list: any) => {
        for (let index = 0; index < list.length; index++) {
          const item = list[index]


          if (item.indexKey === activeNode.value.indexKey) isQuerySuccess = true
          if (item.refId) {
            const itemAssets = assetsMap[item.refId]
            if (itemAssets.layers) item.layers = await queryInfo(itemAssets.layers)
            else if (isQuerySuccess) {
              for (let index = 0; index < treeMeatData.value.assets; index++) {
                const assetsItem = treeMeatData.value.assets
                if (assetsItem.id === item.refId) break
              }
              treeMeatData.value.assets[index].p = image
              break
            }
          } else if (item.layers) {
            item.layers = await queryInfo(item.layers || [])
          }
        }
      }
      await queryInfo(treeMeatData.value.layers)
      return
    }
  },
  { deep: true }
)           

自此就完成動态編輯動效資源功能,就可以在任意位置進行動效素材編輯,完成動效的二次創作,進行快速的複用了。

開發與設計:一站式動效高效協同解決方案

圖11 動态圖檔功能展示圖檔

3.3.3.5 核心功能 - 動态更新顔色

業務場景中有部分的動效是通過元素節點進行建立,這個時候就會存在修改元素節點的填充顔色的功能,下面我們就來看下如何實作該功能的。

顔色編輯功能:前置的步驟還是和資源編輯功能相同,主要的變化是在節點識别與配置表單生成邏輯上

  • 首先需要識别目前選中節點的類型
  • 确定類型後進行初始化表單内容
  • 通過 indexKey 找到對應節點後,找到 path 元素,更新對應的顔色資訊

動态顔色編輯代碼:

async function activeTreeNode(key: any, data: any) {
  const item = data.node.dataRef


  // 讀取 全部配置顔色 item
  const result = await queryLayerFillColor(item) // 遞歸全部圖層節點 找到元素類型的圖層 擷取到填充顔色
  const uniqueMap = {}
  const uniqueResult = []
  result.forEach((value: any) => {
    if (uniqueMap[value.join(',')]) return
    uniqueResult.push(value)
    uniqueMap[value.join(',')] = true
  })


  editConfig.value = {
    uniqueColors: uniqueResult,
    allColors: result,
    ty: item.ty,
  }
  activeNode.value = item
}


watch(
  editConfig,
  async (value, oldValue) => {
    if (activeNode.value.ty === 0) {
      let colorIndex = 0


      const setLayerFillColor = async (currentNode, colorList) => {
        const queryInfo = async (list: any) => {
          for (let index = 0; index < list.length; index++) {
            const item = list[index]


            if (item.indexKey === currentNode.indexKey) {
              if (!item.shapes) return
              await item.shapes.forEach(async sv => {
                sv.it.forEach(i => {
                  if (i.ty !== 'fl') return
                  i.c.k = colorList.map((cv, ci) => {
                    return ci === 3 ? cv : cv / 256
                  })
                })
              })
            } else if (item.layers) await queryInfo(item.layers || [])
          }
        }
        await queryInfo(treeMeatData.value.layers)
      }


      // 到達無圖層節點後 進行查詢填充顔色
      const updateFillHandle = (item, index, node) => {
        // 填充顔色
        item.it.forEach(i => {
          if (i.ty !== 'fl') return
          colorIndex = colorIndex + 1
          const element = document.querySelectorAll(`.class_${node.indexKey} > g`)
          forEach(element, valueEle => {
            const childrenElement = valueEle.children[0]
            const childrenBgColor = allColors[index]
            childrenElement.setAttribute(
              'fill',
              `rgb(${childrenBgColor[0]},${childrenBgColor[1]},${childrenBgColor[2]})`
            )
            childrenElement.setAttribute('fill-opacity', childrenBgColor[3])


            // 找到目前圖層dom 進行修改fill
            if (childrenBgColor) setLayerFillColor(node, childrenBgColor)
          })
        })
      }


      // -> shapes / -> layers
      if (activeNode.value.shapes) {
        activeNode.value.shapes.forEach(item => {
          updateFillHandle(item, colorIndex, activeNode.value)
        })
      } else if (activeNode.value.layers) {
        // 找到填充圖層資訊
        const queryInfo = async (list: any) => {
          for (let index = 0; index < list.length; index++) {
            const item = list[index]


            if (item.refId) {
              const itemAssets = assetsMap[item.refId]
              if (itemAssets.layers) item.layers = await queryInfo(itemAssets.layers)
            } else if (item.layers) {
              item.layers = await queryInfo(item.layers || [])
            }


            if (item.shapes) {
              await item.shapes.forEach(async sv => {
                await updateFillHandle(sv, colorIndex, item)
              })
            }
          }
        }
        queryInfo(activeNode.value.layers)
      }
    }
  },
  { deep: true }
)           

注意點:AE 導出的 Lottie 動效,顔色 rgba 值不是256進制,需要進行轉換才能正常使用。

開發與設計:一站式動效高效協同解決方案

圖12 動态顔色功能展示圖檔

通過以上的代碼和功能已經完成平台的主要功能,平台動效單元的導入導出功能,由于篇幅受限就不展開講解啦,感興趣可以通過 Lottie 官網了解更新使用 Lottie 的小技巧。

開發與設計:一站式動效高效協同解決方案

圖13 動效平台功能預覽圖

4 業務落地場景

我們都知道 Lottie 提供了一系列的 API 來控制動畫,正是通過它我們才能實作各種複雜的業務場景,接下來我們可以看下如何通過該方式來支援複雜的業務動效。

4.1 營銷域

4.1.1 新年接好運

簡單說明下場景,使用者進入活動後可以看到好運紅包雨,最終通過開啟紅包拿到獎勵金額的互動動效。動效一共分為四個階段:倒計時、好運雨、紅包出現、紅包開啟。

  • 可以看到其中的變量分别是:獎勵金額、卡片圖檔、好運名稱
  • 可以通過上述的資源編輯功能來完成不同的動效展示内容

做過營銷頁的前端小夥伴都知道,這裡通常會伴随着一個 ajax 請求去擷取開啟獲得那個紅包,大緻流程是:

  1. 發起 ajax 請求,同時播放開啟紅包的動畫
  2. ajax 請求傳回後,播放對應的好運卡片與獎勵金額

,時長00:14

圖14 新年接好運活動案例

4.1.2 HIMO藝術館

業務場景是使用者通過一段互動動畫進入場館觀看HIMO集團的照片展覽。該業務場景的動效共分為兩個階段:場館出現、進入場館。

該位置的動畫互動需要使用者點選按鈕互動執行下一階段的動畫,讓我們來看下如何使用Lottie來管控分段執行吧。

  • 0~10 幀是場館出現互動按鈕
  • 11~199 幀是場館進入動畫

,時長00:16

圖15 海馬體藝術館案例

大緻流程是:

  1. 先自動執行0~10幀動畫,這個時候進行暫停,等待使用者互動指定
  2. 使用者點選按鈕 進行執行11~199幀動畫,動畫執行完成切換下一個場景

可以通過圖層的 cl 字段來擷取到開啟按鈕的節點,在動效加載完成後綁定 click 事件,等該事件被觸發後繼續執行動畫。

藝術館處理代碼:

const lottieAnimation = lottie.loadAnimation({
  container: document.getElementById('guide-an'),
  renderer: 'svg',
  loop: false,
  autoplay: false,
  path: guideAn.value
})
lottieAnimation.addEventListener('enterFrame', event => {
  const currentFrame = event.currentTime
  if (currentFrame >= 10) lottieAnimation.pause()
})
lottieAnimation.addEventListener('DOMLoaded', () => {
  const btnJoin = document.querySelector('.btn-join')
  if (btnJoin) {
    btnJoin.addEventListener('click', debounce(1000, () => {
      lottieAnimation.play(11, true)
    }))
  }
})

           

4.1.3 抽獎活動工具

業務場景是點選子產品,觸發「開始抽獎」的階段,同步釋出接口出去,接口傳回後,将獎勵通過彈窗的方式告知使用者,互動場景比較簡單,但是仔細分析還是發現不少問題。

  • 0~109 幀「開始抽獎」
  • 110~180 幀「命中獎勵」

,時長00:07

圖16 海馬體抽獎活動案例

這裡我們來分析下業務場景可能帶來的問題:

  • 「開始抽獎」點選子產品 -> 接口響應很慢 -> 動效已完成。需要設計循環播放「開始抽獎」階段,等待接口傳回後,在關閉循環處理。通過 animation.loop = true 可以開啟循環
  • 「開始抽獎」點選子產品 -> 接口發生異常 -> 動效進行中。中斷動畫的執行,通過 animation.goToAndStop(0, true) 來中斷,并且回到第0幀
  • 還需要注意的是,使用 playSegments,需要在 DOMLoaded 事件觸發後執行,不然會出現播放完整動畫後再執行片段動畫的問題。這個是 lottie 目前存在的一個 bug

抽獎動效互動處理代碼:

// 初始化動效
function initAnimation() {
  lottieAnimation.value = lottie.loadAnimation({
    container: animationContainer.value,
    renderer: 'svg',
    loop: false,
    autoplay: false,
    path: props.animationConfig.animationFile
  })
  proxy.$nextTick(() => {
    // 添加點選開始執行動畫
    openLotteryElement.addEventListener('click', handleAnimationClick)
  })
  lottieAnimation.value.addEventListener('DOMLoaded', () => animationExecHandler('initExecuteFramesRange'))
}


// 動效執行處理函數
async function animationExecHandler(type) {
  const animationActions = {
    initExecuteFramesRange: config => { // 初始執行幀範圍
      return config.initExecuteFramesRange.length === 0 ? [0, true] : config.initExecuteFramesRange
    },
    openExecuteFramesRange: config => { // 開啟執行幀範圍
      return config.openExecuteFramesRange.length === 0 ? [0, true] : config.openExecuteFramesRange
    },
    endExecuteFramesRange: config => { // 結束幀執行範圍
      return config.endExecuteFramesRange.length > 0 ? config.endExecuteFramesRange : null
    },
    reset: config => { // 重置處理
      return config.initExecuteFramesRange.length === 0 ? [0, true] : [config.initExecuteFramesRange[1] - 1, config.initExecuteFramesRange[1]]
    }
  }
  const config = props.animationConfig
  const action = animationActions[type]
  if (!action) return
  
  const framesRange = action(config)
  if (framesRange) {
    const [frame, isStop] = framesRange
    if (isStop) {
      lottieAnimation.value.goToAndStop(frame, true)
    } else {
      await playAnimation(lottieAnimation.value, framesRange)
    }
  }
}


// 片段播放封裝
function playAnimation(animation, segements) {
  return new Promise(resolve => {
    animation.playSegments(segements, true)


    animation.addEventListener('complete', () => {
      resolve()
    })
  })
}

           

5 總結

文章最開始我們有講到動效存在:實作成本高、溝通成本高和性能難以保證的問題。

但在以上的技術方案中,我們通過 Lottie 解決了動效接入的成本問題,在 Lottie 的基礎上建立了動效平台,解決了動效溝通成本的問題,并且可以快速沉澱資産後快速複用,提高了動效的互動效率。通過采用 SVG 的渲染方式,我們相比使用 CSS3 實作方式,性能也得到較大的提升,已滿足目前業務所需的目标。

動效平台方案最優秀的地方在于解決了研發成本的同時也解放了開發與設計,讓夥伴可以投入到更有意義的工作當中去,不需要針對一個點進行反複溝通,最後得到一個品質不是很佳的動效。通過平台我們可以完成「設計産出動效」、「平台接入動效」、「開發夥伴渲染動畫」三個環節的互相連接配接。

一路走來,從最開始的序列幀 webp 到支援小動畫的 svga,再到現在能實作高互動的 Lottie 動效平台,發揮工程師的想法和解決問題的辦法,一步步的提高動畫子產品的傳遞效率和降低研發投入的成本,讓實作複雜動畫不再是想象。

本文作者

高爾,來自缦圖網際網路中心前端團隊。

來源-微信公衆号:缦圖coder

出處:https://mp.weixin.qq.com/s/4Qd5bhzbPWD3adGUrzkFlA

繼續閱讀