pag调研

937 阅读9分钟

一、pag介绍

pag官网定义:完整的动效工作流解决方案;目标是降低或消除动效相关的研发成本,能够一键将设计师在 AE(Adobe After Effects)中制作的动效内容导出成素材文件,并快速上线应用于几乎所有的主流平台。

1.1 特性:

  • ⾼效⽂件格式:⼆进制流存储、极限压缩策略。 采用可扩展的二进制文件格式,可单文件集成图片音频等资源,实现快速交付。导出相同的 AE 动效内容,在文件解码速度和压缩率上均大幅领先于同类型方案。
  • BMP 预合成:支持ae全特性导出,充分发挥设计师的创造力。 在纯矢量导出方式上支持更多 AE 特性的同时,还引入了BMP预合成结合矢量的混合导出能力,实现支持所有 AE 特性的同时又能保持动效运行时的可编辑性
纯BMP导出(原理和afx类似)BMP预合成

  • 完善的桌面工具:ae导出插件、物料预览工具。 提供从「导出插件」到「桌面预览」等一系列完善的桌面效率工具,让设计师可以所见即所得地生产素材,研发无需介入还原效果,极大降低了设计与研发的对接成本;安装ae插件导出pag物料教程安装pagPreview
  • 运⾏时编辑:占位替换、拼装组合、图层套壳。 运行时,可在保留动效效果前提下,动态修改替换文本或替换占位图内容,甚至对任意子图层进行增删改及移动,轻松实现照片和视频模板等素材的批量化生产。
  • 全新绘图引擎:TGFX、包体优化策略

1.2 动效方案对比:

1.2.1 整体对比

方案paglottiesvgaafx
诞⽣场景视频编辑UI动画直播礼物动画彩蛋
⽂件格式⾼压缩⼆进制编码,⽂件最⼩,解码最快JSON格式⽂本,⽂件较⼤,解码较慢PB序列化格式,解码快,⽂件较⼤Mp4 视频,资源体积和pag差不多
渲染架构跨平台⼀致的C++架构,可任意⼦线程渲染依赖平台相关渲染接⼝,各端能⼒不⼀致依赖平台相关渲染接⼝依赖平台相关渲染接⼝,各端能⼒不⼀致
AE特性⽀持混合导出能⼒⽀持所有AE特性导出仅⽀持有限的⽮量特性导出仅⽀持有限的AE特性,复杂图形⽀持不佳视频序列帧,支持所有AE特性,视觉展示细腻
运⾏时编辑完整的⽂本和占位图编辑以及图层编辑有限的⽂本和占位图编辑有限的⽂本和占位图编辑不支持
是否依赖动效sdk





1.2.2 物料体积

悬浮球物料举例:svga>afx>pag,相同画质情况,pag体积小于afx

svgaafxpag
2.svga悬浮器afx.mp41.pag



彩蛋物料举例:彩蛋使用较多特效插件和AE特性,pag纯BMP(视频序列帧)预合成导出,物料体积较大,和afx体积接近,但也比afx略小。

svgaafxpag
不支持大部分AE特性和特效插件754k.mp4665k.pag



1.2.3 色彩差异

左边afx色彩较淡,pag色彩完还原设计稿色彩。



二、接入方案

  1. 主模板添加异步依赖配置

  1. 阿拉丁卡异步引入
// PAG依赖加载
getPAG() {
    if (this.PAG) {
        return Promise.resolve(this.PAG);
    }
    return new Promise((resolve, reject) => {
        (window as any).require(['dep/libpag'], ({PAGInit}) => {
            PAGInit({
                // 指定lipag.wasm资源地址
                locateFile: () => 'https://search-operate.cdn.bcebos.com/test/ea89c95c674842599c9ad28056016bc6.wasm'https://search-operate.cdn.bcebos.com/test/ea89c95c674842599c9ad28056016bc6.wasm                locateFile: () => 'https://search-operate.cdn.bcebos.com/test/ea89c95c674842599c9ad28056016bc6.wasm'
            }).then(async PAG => {
                this.PAG = PAG;
                resolve(PAG);
            });
        });
    });
}
// pag物料加载
getPagFile() {
    if (this.pagFile) {
        return Promise.resolve(this.pagFile);
    }
    return new Promise(async (resolve, reject) => {
        const PAG = await this.getPAG();
        // 加载pag物料
        const arrayBuffer =  await customFetch(this.data.get('src')).then(res => {
            return res.arrayBuffer();
        });
        // pag物料解码
        const pagFile = await PAG.PAGFile.load(arrayBuffer);
        const ratio = pagFile.height() / pagFile.width();
        const canvasId = this.data.get('pagId');
        // pag sdk默认会设置canvas宽高
        document.getElementById(canvasId).width = pagFile.width();
        document.getElementById(canvasId).height = pagFile.height();
        // 前端自定义等比缩放canvas至父容器宽度
        this.nextTick(() => {
            document.getElementById(canvasId).style.width = '100%';
            document.getElementById(canvasId).style.height = 100 * ratio + '%';
        });
        this.pagFile = pagFile;
        resolve(pagFile);
    });
}
// 创建pagView
getPagView() {
    if (this.pagView) {
        return Promise.resolve(this.pagView);
    }
    return new Promise(async (resolve, reject) => {
        const PAG = await this.getPAG();
        const pagFile = await this.getPagFile();
        // pagView初始化
        const pagView = await PAG.PAGView.init(pagFile, `#${this.data.get('pagId')}`);
        pagView.setRepeatCount(this.data.get('pagId'));
        // 监听动画开始
        pagView.addListener('onAnimationStart', () => {
            this.fire('start');
        });
        // 监听动画结束
        pagView.addListener('onAnimationEnd', () => {
            this.fire('end');
        });
        resolve(pagView);
        this.pagView = pagView;
        this.fire('loadSuccess');
    });
}
async play() {
    const pagView = await this.getPagView();
    pagView.play();
}
async pause() {
    const pagView = await this.getPagView();
    pagView.pause();
}
async stop() {
    const pagView = await this.getPagView();
    pagView.stop();
}
// 创建pagImage
getPagImage(url) {
    return new Promise(async (resolve, reject) => {
        const PAG = await this.getPAG();
        const image = new Image();
        image.setAttribute('crossOrigin', 'Anonymous');
        image.onload = async () => {
            resolve(PAG.PAGImage.fromSource(image));
        };
        image.onerror = (error) => {
            reject(error);
        };
        image.src = url;
    });
}
// 替换图片
async setImage(index, imgUrl) {
    const pagImage = await this.getPagImage(imgUrl);
    const pagFile = await this.getPagFile();
    pagFile.replaceImage(index, pagImage);
}
// 替换文本,详细文本参数:https://pag.art/apis/web/classes/types.TextDocument.html#applyFillhttps://pag.art/apis/web/classes/types.TextDocument.html#applyFill
async setText(index, text: {
    text: string;
    fontSize: number;
    fontStyle: string;
    fontFamily: string;
    fillColor: string;
    backgroundColor: string;
    backgroundAlpha: number;
}) {
    const pagFile = await this.getPagFile();
    const PAG = await this.getPAG();
    // 拿到当前原始文本数据,在原始文本数据上修改
    let textDocument = pagFile.getTextData(index);
    textDocument = Object.assign(textDocument, text);
    pagFile.replaceText(index, textDocument);
}
  1. pagView api:pag.art/apis/web/cl…
  2. demo示例:yq01-ecom-nova07115436f.yq01.baidu.com:8081/activity/pa…

录屏2024-07-17 14.30.20.mov

三、性能、动画稳定性

3.1 依赖资源(目前都是异步)

资源大小耗时备注
libpag.js70.8klibpag sdk 代理层,暴露主要的api
libpag.wasm3.1MB395mslibpag sdk 核心层,引入 libpag.js 后调用 PAGInit() 接口进行实例化时候,加载libpag.wasm,默认加载同级目录的libpag.wasm,可以使用 PAGInit() 上的钩子 locateFile 去指定 libpag.wasm 的路径。
动画物料——



备注:

  • 可确定的依赖里面,libpag.wasm文件是最大的,wasm 文件是可以 GZIP 的,公共 CDN 默认启用了 wasm 文件的 GZIP,如果是自己的静态资源服务需要手动配置。
  • 目前bos服务不支持gzip压缩(尝试手动gzip压缩后是3MB -> 900k),百度云部门反馈预计4月底支持(3.22询问测试中);

官方pag wasm文件(支持gzip):cdn.jsdelivr.net/npm/libpag@…,860k,网络请求耗时:

无异常:270ms
链接cdn服务超长:1.79s
服务响应超长:694ms
协议通信期间发生了协议错误:12.04s



以下是一些可能导致 ERR_QUIC_PROTOCOL_ERROR 错误的原因:

  1. 网络问题:可能是由于网络连接不稳定或网络延迟导致的错误。在某些情况下,QUIC 协议可能对网络环境敏感。
  2. 服务器配置问题:可能是由于服务器端的配置或设置问题导致的。服务器可能没有正确地支持 QUIC 协议,或者与客户端的通信存在问题。
  3. 防火墙或代理问题:防火墙或代理可能会干扰 QUIC 协议的通信,导致错误发生。
  4. 浏览器问题:可能是由于浏览器本身的问题或 bug 导致的。尝试更新浏览器版本或使用其他浏览器可能会解决问题。



3.2 动画起播时间(卡片attach -> 开始播放):

afx 方案pagpage-lite
FCP指标说明:developer.chrome.com/docs/lighth…5.3 szhengmengmei.bcc-bdbl.baidu.com_2024-05-16_20-31-01.report.html5.3 szhengmengmei.bcc-bdbl.baidu.com_2024-05-16_20-31-23.report.html5.3s
TBT指标说明:developer.chrome.com/docs/lighth…100ms510ms220ms
首次进入播放动画时间(app清理所有数据首次进入)起始时间:AFX/PAG逻辑开始执行结束时间:动画开始播放IOS平均:2.4s安卓平均:2.8sIOS平均:3.8s安卓平均:4.7sIOS平均:1.7s安卓平均:2.4s,偶现清除数据冷启后无法正常播放,目前看跟手百安卓端框架的支持度有关: github.com/Tencent/lib…
首次进入播放动画时间(已经进入过该页面重新进)IOS平均:1.7s安卓平均:1.8sIOS平均:1.7s安卓平均:2.2sIOS平均:0.86s安卓平均:1.13s
播放第二个动画(afx/pag素材体积相似)IOS平均:3.1s安卓平均:1.9sIOS平均:1.1s安卓平均:1.5s-




物料\动画方案svgaafxpag(bos平台cdn不支持gzip)预计支持gzip后能优化240ms
悬浮球svga:32kafx:174kpag:7k1. 468 'svga首帧渲染时长'
  1. 753 'svga首帧渲染时长'

  2. 449 'svga首帧渲染时长'

  3. 572 'svga首帧渲染时长'

  4. 596 'svga首帧渲染时长'

  5. 637 'svga首帧渲染时长'

  6. 739 'svga首帧渲染时长'

  7. 519 'svga首帧渲染时长'

  8. 795 'svga首帧渲染时长'

  9. 481 'svga首帧渲染时长' | 1. 357 'afx首帧渲染时长'

  10. 445 'afx首帧渲染时长'

  11. 533 'afx首帧渲染时长'

  12. 424 'afx首帧渲染时长'

  13. 583 'afx首帧渲染时长'

  14. 464 'afx首帧渲染时长'

  15. 379 'afx首帧渲染时长'

  16. 483 'afx首帧渲染时长'

  17. 457 'afx首帧渲染时长'

  18. 529 'afx首帧渲染时长' | 1. 1437 'pag首帧渲染时长'

  19. 1783 'pag首帧渲染时长'

  20. 1501 'pag首帧渲染时长'

  21. 2051 'pag首帧渲染时长'

  22. 1702 'pag首帧渲染时长'

  23. 1826 'pag首帧渲染时长'

  24. 2071 'pag首帧渲染时长'

  25. 1929 'pag首帧渲染时长'

  26. 1905 'pag首帧渲染时长'

  27. 1777 'pag首帧渲染时长' | | 彩蛋afx:754kpag:665k |  | 1. 600 'afx首帧渲染时长'

  28. 742 'afx首帧渲染时长'

  29. 444 'afx首帧渲染时长'

  30. 453 'afx首帧渲染时长'

  31. 426 'afx首帧渲染时长'

  32. 396 'afx首帧渲染时长'

  33. 530 'afx首帧渲染时长'

  34. 539 'afx首帧渲染时长'

  35. 603 'afx首帧渲染时长'

  36. 773 'afx首帧渲染时长' | 1. 1974 'pag首帧渲染时长'

  37. 1742 'pag首帧渲染时长'

  38. 1847 'pag首帧渲染时长'

  39. 1588 'pag首帧渲染时长'

  40. 1753 'pag首帧渲染时长'

  41. 1667 'pag首帧渲染时长'

  42. 1775 'pag首帧渲染时长'

  43. 1799 'pag首帧渲染时长'

  44. 1537 'pag首帧渲染时长'

  45. 1431 'pag首帧渲染时长'1918:153(js)+76(pag)+800 (wasm)+other |



3.3 动画稳定性

cpu占用较afx相比更高,wasm编解码以及处理canvas播放逻辑可能导致js线程的占用;动画稳定性较好,中低端机支持较为良好,无导致崩溃;

afxpag
5.4%11.8%



四、调研总结

pag首次播放速度较慢,在能接受起播速度较慢情况下,可以使用;

  1. 动画简单、尺寸较小且有编辑场景,使用svga;
  2. 动画效果丰富且无编辑场景,使用afx;
  3. 动画效果丰富、尺寸较大,且有编辑场景,使用pag;



参考文档

pag官网

pag-web api

afx介绍