web如何緩存Lottie動畫資源

前言

Hello 你們好! 我是前端 無名css

背景

在實際工做中,咱們常常會用到Lottie-web來實現動畫效果。一個Lottie動畫一般由一個data.json文件和N張圖片組成。項目中通常使用Lottie動畫都是給一個CDN的動畫地址,交給lottie-web庫去播放,動畫資源下載時機是動畫播放前去下載data.json文件和圖片資源,動畫的播放受網絡因素影響較大,動畫的加載時間較長,若是須要多個lottie動畫結合顯示,時間不能完美契合,最終效果達不到UI設計師要求。html

本篇文章介紹如何提取lootie動畫資源而且利用緩存技術實現Lottie動畫從本地讀取加載,減小網絡請求。前端

技術思路

  1. 項目中配置須要緩存的lottie config文件。
  2. 利用webpack插件,在項目構建時,生成真正須要緩存的配置列表(圖片列表以及lottie的data.json列表)。
  3. 封裝預加載庫,請求圖片和data.json資源,存儲在indexdb中。
  4. 封裝Lottie加載配置類,區分從緩存讀取仍是直接網絡獲取。
  5. 緩存讀取,建立圖片Blob url,替換原始data.json中的圖片引用,生成新的data.json。
  6. lottie 加載配置,展現動畫。

瞭解Lottie JSON文件數據格式

詳細能夠參考:Web 幀動畫解決方案 - lottie-web源碼剖析node

本文借用 青舟同窗 的圖片來簡單介紹下Lottie JSON文件結構,方便後續解析JSON文件,讀取圖片資源以及替換圖片地址。android

  1. JSON文件全局信息

c5bed44b3e204b6dbf281e075dcbbf46_tplv-k3u1fbpfcp-zoom-1.png 左側爲使用 AE 新建動畫合成須要填入的信息,和右面第一層 JSON 信息對應以下:webpack

  • w 和 h : 寬高
  • v:Bodymovin 插件版本號 4.5.4
  • fr:幀率
  • ip 和 op:開始幀、結束幀
  • assets:靜態資源信息(如圖片)
  • layers:圖層信息
  • ddd:是否爲3d
  • comps:合成圖層

其中 assets 對咱們提取Lottie資源以及後期生成新的json尤其重要git

lottie JSON圖片資源提取

預加載lottie圖片有兩種方案:github

方案一:編寫webpack插件,構建時提取lottie JSON 圖片資源,結合html-webpack-plugin提供的hooks,將圖片資源地址以Link方式插入到圖片html中。web

方案二:編寫webpack插件,構建時提取lottie JSON 圖片資源,生成lottieAssets.js配置文件。利用緩存工具庫,讀取配置文件,緩存資源圖片到indexdb中。ajax

方案三:方案三和方案二大致相同,主要是提取lottie資源生成lottieAssets.js配置文件的方式不一樣,方案二是經過webpack插件,方案三是編寫npm(lottie-extract-assets)包,直接執行單獨的命令,去提取lottie資源生成lottieAssets.js。(產線方案) 插件源碼地址:點我!

lottieConfig.json編寫格式以下:

[
    //lottie動畫ceremonyBlessingBagFirst
    "https://xxx.com/effects/ceremonyBlessingBagFirst/data.json",
    //lottie動畫abc
    "https://xxx.com/effects/abc/data.json",
    //lottie動畫cde
    "https://xxx.com/effects/cde/data.json"
]
複製代碼

方案一實現:

例如:data.json的CND地址爲:

xxx.com/effects/cer…

通常咱們的圖片資源存放在 xxx.com/effects/cer… 目錄下

代碼參考:

const HtmlWebpackPlugin = require('safe-require')('html-webpack-plugin');
const lottieConfig = require("../../lottieConfig.json");
const ajax = require('../utils/ajax');
const request = require('request');
const path = require('path');

const preloadDirective = {
    '.js': 'script',
    '.css': 'style',
    '.woff': 'font',
    '.woff2': 'font',
    '.jpeg': 'image',
    '.jpg': 'image',
    '.gif': 'image',
    '.png': 'image',
    '.svg': 'image'
  };

const IS = {
    isDefined: v => v !== undefined,
    isObject: v => v !== null && v !== undefined && typeof v === 'object' && !Array.isArray(v),
    isBoolean: v => v === true || v === false,
    isNumber: v => v !== undefined && (typeof v === 'number' || v instanceof Number) && isFinite(v),
    isString: v => v !== null && v !== undefined && (typeof v === 'string' || v instanceof String),
    isArray: v => Array.isArray(v),
    isFunction: v => typeof v === 'function'
  };
  
  const { isDefined, isObject, isBoolean, isNumber, isString, isArray, isFunction } = IS;
  
  /** * * 預加載圖片資源 * @class LottieWebpackPlugin */
  class LottieWebpackPlugin{

      constructor(options){
          this.options=options || {};
      }
      /** * * * 添加圖片資源 * @memberOf LottieWebpackPlugin */
      addLinks= async(compilation, htmlPluginData)=>{
          let imgArray=[];
          if(lottieConfig){
              for(let i=0;i<lottieConfig.length;i++){
               const result=  await this.requestLottie(lottieConfig[i]);
               imgArray.push(...result);
              }
          }
          //重點:添加預加載link標籤到htmlPlugin的head中
          Array.prototype.push.apply(htmlPluginData.headTags, imgArray.map(this.addPreloadType));
        return htmlPluginData;
      }
      /** * * * 請求data.json文件 * @memberOf LottieWebpackPlugin */
      requestLottie=  (url)=>{
         return new Promise((resolve,reject)=>{
            request(url,  (error, response, body)=> {
                if (!error && response.statusCode == 200) {
                  try{
                    const lottieData=JSON.parse(body);
                    const result= this.lottieParse(lottieData,url);
                    resolve(result);
                  }catch(e){
                      console.log(e);
                  }
                }else{
                    reject(url+"失敗");
                }
              })
          })
      }
      /** * * * 解析lottie文件 * @memberOf LottieWebpackPlugin */
      lottieParse=(data,url)=>{
        let urlArray=[];
        try{
            const assets=data.assets;
            const urlPre=this.getUrlPre(url);
            for(let i=0;i<assets.length;i++){
                const item=assets[i];
                if(item.p && item.u){
                    const url=`${urlPre}${item.u}${item.p}`;
                    const tag= this.createResourceHintTag(url,"preload",true);
                    urlArray.push(tag);
                }
            }
          }catch(e){
              console.log(e);
          }
          return urlArray;
      }
    /** * * * 獲取data.json的引用地址 * @memberOf LottieWebpackPlugin */
    getUrlPre=(url)=>{
        const lastIndex=  url.lastIndexOf("/");
        return url.substring(0,lastIndex+1);
    }
    /** * * * * @memberOf LottieWebpackPlugin */
    addPreloadType =(tag)=> {
        const ext = path.extname(tag.attributes.href);
        if (preloadDirective[ext]) {
          tag.attributes.as = preloadDirective[ext];
        }
        return tag;
      }
      

      /** * * * 資源加載 * @memberOf LottieWebpackPlugin */
      alterAssetTagGroups=(htmlPluginData,callback,compilation)=>{
          console.log("compilation=",compilation);
          console.log("htmlPluginData=",htmlPluginData);
        try {
            callback(null, this.addLinks(compilation, htmlPluginData));
        } catch (error) {
            callback(error);
        }        
      }

     
     /** * * * 建立資link標籤,預加載 * @memberOf LottieWebpackPlugin */
     createResourceHintTag= (url, resourceHintType, htmlWebpackPluginOptions)=> {
        return {
          tagName: 'link',
          selfClosingTag:  true || !!htmlWebpackPluginOptions.xhtml,
          attributes: {
            rel: resourceHintType,
            href: url
          }
        };
      }
  
      registerHook(compilation){
        const pluginName=this.constructor.name; 
        if (HtmlWebpackPlugin && HtmlWebpackPlugin.getHooks) {
                // HtmlWebpackPlugin >= 4
            const hooks = HtmlWebpackPlugin.getHooks(compilation);
            const htmlPlugins = compilation.options.plugins.filter(plugin => plugin instanceof HtmlWebpackPlugin);
            if (htmlPlugins.length === 0) {
                const message = "Error running html-webpack-tags-plugin, are you sure you have html-webpack-plugin before it in your webpack config's plugins?";
                throw new Error(message);
            }
            hooks.alterAssetTagGroups.tapAsync(pluginName, (htmlPluginData, callback)=>{this.alterAssetTagGroups(htmlPluginData, callback,compilation)});
        
        } else if (compilation.hooks.htmlWebpackPluginAlterAssetTags &&
            compilation.hooks.htmlWebpackPluginBeforeHtmlGeneration) {
                // HtmlWebpackPlugin 3
                compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync(pluginName,(htmlPluginData, callback)=>{this.alterAssetTagGroups(htmlPluginData, callback,compilation)});
        }else{
            const message = "Error running html-webpack-tags-plugin, are you sure you have html-webpack-plugin before it in your webpack config's plugins?";
            throw new Error(message);
        }
      }

      apply(compiler){
        const htmlPluginName = isDefined(this.options.htmlPluginName) ? this.options.htmlPluginName : 'html-webpack-plugin';
        const pluginName=this.constructor.name;
          if(compiler.hooks){
              compiler.hooks.compilation.tap(pluginName,(compilation)=>{
                  this.registerHook(compilation);
              });
          }
      }
  }

  module.exports = LottieWebpackPlugin;
複製代碼

生成html效果以下:

code1.png

github 戳 :lottie-pre-webpack-plugin

方案二實現:自定義webpack插件,提取圖片資源,生成js或者ts文件

代碼參考:

const fs = require('fs');
const request = require('request');
const path = require('path');
const webpack = require("webpack");

/** * * lottie資源提取插件 * @class LottieExtractAssetsPlugin */
class LottieExtractAssetsPlugin {

    constructor (options) {
    	//1:獲取 lottie配置文件路徑
        this.configPath = options && options.configPath;
        //2:獲取輸出文件名稱
        this.outFileName = options && options.outFileName ? options.outFileName : "lottie-assets.js";
        //生成資源文件的全局名稱
        this.globalName = options && options.globalName ? options.globalName : "window._config";
        this.to = options && options.to ? options.to : "dist";
    }

    compilationHook(compilation) {
        const pluginName = this.constructor.name;
        //重點:webpack 適配
        if(compilation.hooks.processAssets){
            //compilation.emitAsset(name, new webpack.sources.RawSource(html, false));
           // 添加資源
            compilation.hooks.processAssets.tapAsync({ name: pluginName, stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS }, async (assets, cb) => {
                if (this.configPath) {
                    await this.readJsonFile(this.configPath, assets);
                    cb();
                } else {
                    cb();
                }
            });
        }else if(compilation.hooks.additionalAssets){
            compilation.hooks.additionalAssets.tapAsync( pluginName,  async (cb) => {
                if (this.configPath) {
                    await this.readJsonFile(this.configPath, compilation.assets);
                    cb();
                } else {
                    cb();
                }
            });
        }else{
            //throw new Error("請升級webpack版本>=4");
            compilation.errors.push("請升級webpack版本>=4");
        } 
    }
    
    /** * * * 獲取lottie 資源地址。 * @memberOf LottieExtractAssetsPlugin */
    getLink= async(lottieConfig)=>{
        let imgArray=[];
        if(lottieConfig){
            for(let i=0;i<lottieConfig.length;i++){
                const url=lottieConfig[i];
                //添加lottie json
                this.addLottieInfo(url,imgArray);
                //請求lottie json文件,獲取圖片資源
                const result=  await this.requestLottie(lottieConfig[i]);
                imgArray.push(...result);
            }
        }
      return imgArray;
    }
    /** * * * 添加lottie json 文件 * @memberOf LottieExtractAssetsPlugin */
    addLottieInfo=(url,imgArr)=>{
        const info=this.getLottieInfo(url);
        imgArr.push({
            key:info.name,
            url:url,
         })
    }
    
    /** * * * 讀取配置文件,生成js文件。 * @memberOf LottieExtractAssetsPlugin */
    readJsonFile= async(assetPath,assets)=>{
        //讀取lottieCofig.json配置文件
        let lottieConfig = await new Promise((resolve, reject) => {
            try {
                //讀取配置文件
                fs.readFile(assetPath, (err, data) => {
                    if (err) {
                        reject(err);
                    } else {
                        let curData = data.toString();
                        const config = JSON.parse(curData);
                        resolve(config);
                    }
                });
            } catch (e) {
                reject(e);
            }
        }).catch(()=>{
            console.warn("讀取配置文件錯誤:"+assetPath);
        });
        if(!lottieConfig){
            return;
        }
        //根據配置獲取資源連接(包含當前的lottie和lottie中圖片)
        const imgLink = await this.getLink(lottieConfig);
        // 採用js文件,方便咱們前端代碼集成使用。
        let content = this.globalName + " = " + JSON.stringify(imgLink, null, 4) + ";";
        const assetsInfo = {
            // 寫入新文件的內容
            source: function () {
                return content;
            },
            // 新文件大小(給 webapck 輸出展現用)
            size: function () {
                return content.length;
            }
        }
        const fileName = path.join(this.to, this.outFileName);
        assets[fileName]= assetsInfo;
    }
    /** * * * 請求lottie json文件 * @memberOf LottieExtractAssetsPlugin */
    requestLottie=  (url)=>{
       return new Promise((resolve,reject)=>{
          request(url,  (error, response, body)=> {
              if (!error && response.statusCode == 200) {
                try{
                  const lottieData=JSON.parse(body);
                  const result= this.lottieParse(lottieData,url);
                  resolve(result);
                }catch(e){
                    console.log(e);
                }
              }else{
                  reject(url+"==失敗");
              }
            })
        })
      
    }

    /** * * 解析lottie * @memberOf LottieExtractAssetsPlugin */
    lottieParse=(data,url)=>{
      let urlArray=[];
      try{
          const assets=data.assets;
          const lottieInfo=this.getLottieInfo(url);
          for(let i=0;i<assets.length;i++){
              const item=assets[i];
              if(item.p && item.u){
                  const imgUrl=`${lottieInfo.url}/${item.u}${item.p}`;
                  urlArray.push({
                      key:`${lottieInfo.name}_${item.p}`,
                      url:imgUrl,
                      source:url,
                      lottieName:lottieInfo.name
                  });
              }
          }
        }catch(e){
            console.log(e);
        }
        return urlArray;
    }
    /** * * 根據url獲取lottie信息,方便生成配置文件。 * @memberOf LottieExtractAssetsPlugin */
    getLottieInfo=(url)=>{
      const lastIndex=  url.lastIndexOf("/");
      const curUrlPre=url.substring(0,lastIndex);
      const nameLastIndex=  curUrlPre.lastIndexOf("/");
      return {url:curUrlPre,name:curUrlPre.substring(nameLastIndex+1,nameLastIndex.length)}
    }
    /** * * webpack 插件入口 * @param {any} compiler * * @memberOf LottieExtractAssetsPlugin */
    apply(compiler) {
        const pluginName=this.constructor.name;
        if(compiler.hooks){
            // Webpack 4+ Plugin System
            //TODO 使用該hooks目前能夠知足需求,可是會警告,後期查看webpack具體生命週期,替換。
            compiler.hooks.compilation.tap(pluginName, (compilation, compilationParams) => {
                //注意註冊事件時機。
                this.compilationHook(compilation);
            });
        }else{
            compilation.errors.push("請升級webpack版本>=4");
        }
    }
  }
  
  module.exports = LottieExtractAssetsPlugin;

複製代碼

測試插件:webpackConfig.js:

const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const LottieExtractAssetsPlugin=require("./src/plugins/index.js");
const assert = require('assert');

const to=path.join("lottie", "test");

module.exports = {
  entry: './src/index.js',
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new CleanWebpackPlugin(),
    new LottieExtractAssetsPlugin({configPath:"./lottieConfig.json",to:to,outFileName:"lottie-assets.js",globalName:"window._lottieConfig"})
  ]
}
複製代碼

生成效果:lottie-assets.js

code4.png

github 戳 :lottie-extract-assets-plugin

利用緩存,緩存lottie資源

  1. 資源indexdb存儲

    rloader基於idb庫實現。

    rloader 特色:

    1. 資源加載支持簡單的依賴關係

    2. 緩存支持版本

    3. 小巧

    4. 進度,錯誤,完畢等事件支持

該庫的封裝由 xiangenhu 同窗完成。已開源:rloader,測試案例首次加載耗時3秒,後面加載僅需22毫秒左右。效率大幅提高。rloader演示demo,現已發表掘金:徒手擼一個資源加載器

具體使用:index.js

import ResourceLoader from "./r-loader.js";
//全局緩存資源 key:string,blobUrl:string;data:Blob,url:string;
 window.GlobCache={};

//默認自定義緩存的文件,可選
let resourcesInfo = [
    {
        key: "mqtt2",
        url: "//xxx.com/libs/js/mqtt.min.js"
    },{
        key: "lottie",
        url: "//xxx.com/lib/lottie.min.js",
        ver: "1.9"
    },{
        key: "flv",
        pre: ["mqtt"],
        url: "//xxx.com/libs/js/flv.1.5.min.js"
    },{
        pre: ["lottie"],
        key: "mqtt",
        url: "//xxx.com/libs/js/mqtt.min.js"
    },
];


//重點 index.html 中引入lottie-assets.js
if(window._config){
    //加載lottie-extract-assets-plugin 插件提取出來的lottie資源文件
    resourcesInfo=resourcesInfo.concat(window._config);
}



let startTime=Date.now();

const rl = new ResourceLoader(resourcesInfo, window.idb);

rl.on("progress", (progress, info)=>{
    //緩存完一個文件,添加到全局緩存中。
    window.GlobCache[info.key]=info;
});

rl.on("completed", (datas)=>{
    console.log("加載完成:completed event:", datas);    
    console.log("total time:", Date.now() - startTime)
});

rl.on("loaded", (datas)=>{
    console.log("所有正常加載:loaded event:", datas);    
    console.log("total time:", Date.now() - startTime)
});

rl.on("error", (error, info)=>{
    console.log("error event:", error.message, info);
});
rl.reset();
rl.startLoad();


複製代碼

效果:

image.png

編寫lottie option 生成方法,判斷緩存讀取仍是直接取網絡

上面咱們已經把lottie圖片資源以及data.json資源緩存到indexdb中,而且保存到了window.GlobCache中,如今咱們根據緩存生成不一樣的lottie option對象

const jsonPath="https://xxx/acts-effects/luck-draw-act-effect1/data.json";
  const defaultOptions = {
       loop: false,
       autoplay: false,
       path: jsonPath,
       rendererSettings: {
          preserveAspectRatio: "xMidYMid slice",
       },
     }; 
複製代碼

衆所周知,lottie加載動畫的時候須要一個option對象:

  • 資源從緩存獲取,給option中animationData賦值,animationData的json數據是咱們修改後的json對象,圖片資源地址爲blob url
  • 資源從網絡獲取,給option中path賦值

直接上代碼: LottieOptionFactory.js

/** * * 獲取json對象 * @export * @param {any} rul */
 export function fetchResourceJson(url){
   return fetch(url,{
        headers: {
            'content-type': 'application/json'
          },
    }).then(res=>{
        if(res.status >= 400){
            throw new Error(res.status + "," + res.statusText);
        }
        return res.json();
    }).then(res=> res);
 }

/** * * lottie option 生成 * @export * @param {any} option * @returns  */
export default async function LottieOptionFactory(option={},globCache=window.GlobCache) {
   //獲取原始option
    const { path, ...other } = option;
    const originalOption = {
        path,
        ...other,
    };
    try{
        const result = getLottieInfo(path);
        //獲取lottie動畫名稱
        const { name } = result;
        //從全局緩存中獲取lottie的data.json 
        const lottieCache= globCache[name];
        //若是緩存中不存在,則返回原始配置option,正常從網絡上獲取。
        if(!lottieCache || !lottieCache.blobUrl){
                return originalOption;
        }
        //利用緩存中獲取的data.json資源的blobUrl獲取data.json 對象
        const jsonData= await getLottieJson(lottieCache.blobUrl);
        //修改lottie json對象中的圖片字段
        const transJson= transformLottieJson(jsonData,name);
        //返回blob URL 的data.json
        return {
                ...other,
                animationData:transJson
            }
    }catch(e){
        console.log("LottieOptionFactory err:",e);
    }
    return originalOption;
   
}

/** * * 根據url獲取lottie信息。 * @memberOf getLottieInfo */
function getLottieInfo(url) {
    const lastIndex = url.lastIndexOf("/");
    const name = url.substring(lastIndex + 1, url.length);
    const curUrlPre = url.substring(0, lastIndex);
    const nameLastIndex = curUrlPre.lastIndexOf("/");
    return { url: curUrlPre, name: curUrlPre.substring(nameLastIndex + 1, nameLastIndex.length), jsonName: name };
}

/** * * 獲取lottie json 對象 * @param {any} lottieCacheData * @returns  */
 function getLottieJson (url){
     //兩種實現方式,
     //1:從緩存中獲取到'data.json'的Blob對象,轉換Blob對象爲json對象 
     // const reader = new FileReader();
     // return new Promise((resolve,reject)=>{
     // reader.readAsText(lottieCacheData,'utf8');
     // reader.onload = function(){
     // const receive_data = this.result;//這個就是解析出來的數據
     // try{
     // resolve(JSON.parse(receive_data));
     // }catch(e){
     // console.log("解析",e);
     // reject("失敗");
     // }
     // }
     // })
    // 2:直接訪問'data.json'的blob url, 獲取 json對象
    return fetchResourceJson(url);
    
}

/** * * 修改lottie json對象中的圖片字段,生成使用blob url的圖片地址 * @param {any} lottieJson * @param {any} lottieName * @returns  */
function transformLottieJson (lottieJson,lottieName){
    //先備份
    const newLottieJson={...lottieJson};
    try{
        const assets=newLottieJson.assets;
        for(let i=0;i<assets.length;i++){
            const item=assets[i];
            //p 爲 圖片名稱 u:圖片相對路徑
            if(item.p && item.u){
                const name=`${lottieName}_${item.p}`;
                const lottieCache= window.GlobCache[name];
                if(lottieCache && lottieCache.blobUrl){
                    newLottieJson.assets[i].u="";
                    newLottieJson.assets[i].p=lottieCache.blobUrl;
                }
            }
        }
    }catch(e){
        console.log(e);
    }
    return newLottieJson;

}
複製代碼

具體使用:

import ResourceLoader from "./r-loader.js";
    import LottieOptionFactory from "./LottieOptionFactory.js";

    //全局緩存資源 key:string,blobUrl:string;data:Blob,url:string;
     window.GlobCache={};

    let resourcesInfo = [];

    if(window._config){
        //加載lottie-extract-assets-plugin 插件提取出來的lottie資源文件
        resourcesInfo=resourcesInfo.concat(window._config);
    }

    const rl = new ResourceLoader(resourcesInfo, window.idb);

    rl.on("progress", (progress, info)=>{
        console.log("progress:", progress,  info);
        window.GlobCache[info.key]=info;
    });

    rl.on("completed", (datas)=>{
        console.log("加載完成:completed event:", datas);    
        console.log("total time:", Date.now() - startTime)
    });
 
    rl.reset();
    rl.startLoad();

    //這裏使用timeout是因爲從indexdb中讀取緩存須要時間。產線方案中解決。
     setTimeout(async ()=>{
      const option= await LottieOptionFactory({
            container: document.getElementById('bm'),
            renderer: 'svg',
            loop: true,
            autoplay: true,
            path : "https://xxx.comacts-effects/luck-draw-act-effect1/data.json",
        });
        console.log("option==",option);
        const animation = bodymovin.loadAnimation(option)
    },1000)

複製代碼

效果:

首次加載:

image.png

二次加載:

image.png

lottie最終效果

image.png

至此,咱們已經實現了lottie資源加載時機提早,屢次加載均採用緩存資源,成功的減小了網絡請求。

應用產線

在應用產線的過程當中,咱們發現了一些能夠優化的地方,對上面的邏輯進行了一些優化

  • 利用node命令(npm包 lottie-extract-assets)提取資源,再也不使用webpack插件(節省編譯時間),已支持多項目,多目錄配置。
  • lottie json文件轉化對象提早,不在建立lottieOption的時候去讀取,修改成監聽 rloader的 progress 事件,在從緩存中讀取到lottie json的blobUrl後直接轉化爲json對象。
  • index.html 中引入編譯提取出來的資源文件配置不夠便利,建議在index.js中導入(lottie-assets.js)

部分代碼參考:

startLottieCache.ts

import ResourceLoader from "./r-loader";
import idb from "./idb";


import { fetchResourceJson } from "./cacheUtils";

enum CacheFileType {
    LOTTIE_JSON = "lottie-json-file",
    LOTTIE_IMG = "lottie-img"
}

function parserLottie(globalName: string) {
    const lottieResourcesInfo = [];
    if ((window as any)[globalName]) {
        // 加載lottie-extract-assets-plugin 插件提取出來的lottie資源文件
        const lottieConfig = (window as any)[globalName];
        for (let i = 0; i < lottieConfig.length; i++) {
            const item = lottieConfig[i];
            const {
                key, url, source, imgs
            } = item;
            lottieResourcesInfo.push({
                key, url, source, fileType: CacheFileType.LOTTIE_JSON
            });
            lottieResourcesInfo.push(...imgs);
        }
    }
    return lottieResourcesInfo;
}

/** * * 獲取lottie json 對象 * @param {any} lottieCacheData * @returns */
function getLottieJson(url) {
    // 2:直接訪問'data.json'的blob url, 獲取 json對象
    return fetchResourceJson(url);
}


export function mergeLottieCache(resourcesInfo = [], globalName) {
    let curResourcesInfo = [...resourcesInfo];
    const lottieResourcesInfo = parserLottie(globalName);
    curResourcesInfo = resourcesInfo.concat(lottieResourcesInfo);
    return curResourcesInfo;
}
/** * * 重點,緩存讀取到之後,特殊處理 */
function handleCacheResult(info) {
    if (!info.fileType) return;
    if (info.blobUrl) {
        getLottieJson(info.blobUrl).then((data) => {
            console.log("data==", data);
            (window as any).GlobCache[info.key].lottieJson = data;
        }).catch((e) => {
            console.log("解析失敗", e);
        });
    }
}
/** * * 開啓緩存 */
export function startCache(resourcesInfo) {
    (window as any).GlobCache = {};
    const curResourcesInfo = [...resourcesInfo];
    const startTime = Date.now();
    const rl = new ResourceLoader(curResourcesInfo, idb);
    rl.on("progress", (progress, info) => {
        console.log("progress:", progress, info);
        (window as any).GlobCache[info.key] = info;
        //重點,能夠針對特殊緩存處理
        handleCacheResult(info);
    });

    rl.reset();
    rl.startLoad();
}

複製代碼

接入:

  1. index.js

    //導入生成的lottie-assets.js文件
       import "./lottie-assets";
       //須要合併的緩存數組和生成的lottie-assets.js文件中window對象名稱
      const resources=mergeLottieCache([],"_lottieConfig");
      //開啓緩存
      startCache(resources);
    複製代碼
  2. 使用

    const defaultOptions = {
         loop: false,
         autoplay: false,
         path: jsonPath,
         rendererSettings: {
             preserveAspectRatio: "xMidYMid slice",
         },
     };
     //新增,用LottieOptionFactory包裝原來的參數便可
     LottieOptionFactory(defaultOptions);
    複製代碼

後語

演示DEMO 代碼戳我

ps:(替換lottie圖片的思路實現之後,咱們之後能夠用同一個lottie,動態的替換json中的圖片,實現一些定製的動畫效果。例如:用戶名片展現,名片是個lottie動效,用戶頭像是定製的,咱們可讓設計師把默認用戶頭像設計到lottie動畫中,動態替換用戶頭像)

本篇文章主要介紹的是技術實現思路,前提是支持indexDB。 歡迎社區的小夥伴多提意見。

相關文章
相關標籤/搜索