Pag——腾讯自创动效

背景

是腾讯公司自主研发,最初诞生于2016年,为解决复杂视频编辑场景下动画 渲染的问题,同时又覆盖了UI动画和直播场景,2022年一月在GitHub正式开源。这意味着目前还处于一个小众阶段,从网上搜索还是b站查看教学视频都没没那么多。

官网

PAG官网传送门
PAG文件线上预览传送门

官方简介

Pag的目标是降低或消除动画研发相关的成本,打通设计师创作到素材上线的自动化流程,不断输出运行时可编辑的高质量动画内容。是一套完整的动画工作流解决方案。提供从AE(Adobe After Effects)导出插件,到桌面预览工具,再到覆盖ios、Android、macOS、windows、Linux和Web等平台的渲染SDK.

然后呢,也说很多的平台和app都在应用,进一步证明这个动效的强大。

PAG在运行时,可在保留动画效果前提下,动态修改替换局部的文本或占位图内容,甚至对任意子图层进行增删改及移动,极大丰富了动画素材的使用场景,轻松实现照片和视频模板等素材的批量化生产。

PAG SDK整套方案是基于 C++ 和 OpenGL 的跨平台架构研发的,不依赖平台相关的UI框架,除了能做到跨端渲染完全一致外,还能轻松移植到各个原生平台,其中也包含服务器端的渲染能力。

在性能方面,PAG应用了游戏渲染里的大量的优化经验,设计了从中间渲染数据到局部位图的多级缓存架构,加上帧预测的技术,每帧渲染耗时平均可以做到Lottie的50%左右。

由于采用二进制格式,不存在JSON的字符串解析,解码耗时平均比Lottie文件的快12倍,相同的动画内容导出文件只有Lottie一半左右大小,同时二进制文件格式也更容易做到单文件集成图片,音频,视频等任意资源。

官方自吹优势

  1. 高效的文件格式
    采用可扩展的二进制文件格式,可单文件集成图片音频等资源,实现快速交付。导出相同的 AE 动效内容,在文件解码速度和压缩率上均大幅领先于同类型方案。
    Pag——腾讯自创动效_第1张图片

  2. AE特性全面支持
    在纯矢量导出方式上支持更多 AE 特性的同时,还引入了BMP预合成结合矢量的混合导出能力,实现支持所有 AE 特性的同时又能保持动效运行时的可编辑性。

  3. 完善的桌面工具
    提供从「导出插件」到「桌面预览」等一系列完善的桌面效率工具,让设计师以所见即所得地生产素材,研发无需介入还原效果,极大降低了设计与研发的对接成本。

  4. 性能监测的可视化
    通过导出插件内置的自动优化策略,以及预览工具集成的性能监测面板,能够观地看到每个素材的性能状态,以帮助设计师制作效果和性能俱佳的动画特效。

  5. 运行时可编辑
    通过导出插件内置的自动优化策略,以及预览工具集成的性能监测面板,能够观地看到每个素材的性能状态,以帮助设计师制作效果和性能俱佳的动画特效。
    Pag——腾讯自创动效_第2张图片

lottie VS pag

Lottie 早期的版本不支持图片类动画,导出 json 之后会自动生成一个 img 的资源文件夹,播放 .json 文件时,需要解压资源压缩包到本地目录才能正常播放。从 bodymovin V 5.1.15 之后,Lottie 将图片转为 base 64 编码,使用字符代替图像地址,并封装在 json 里,直接播放一个 .json 文件,不用再拖着一个资源文件夹了。Lottie 当前对图片类动画的支持依然会有一些小问题,有时候需要仔细排查出问题的图层,并对照官方文档灵活调整动画替代方案。

PAG最初诞生的原因,正是因为Lottie无法满足视频编辑场景里的动画以下几点需求:
1.当时它根本不支持文本图层,可编辑的字幕贴纸是视频编辑非常重要的能力。
2.在 iOS 上它依赖 CALayer 渲染,当用在非主线程的视频合成时,有部分动画会不呈现。
3.Android 上,它的遮罩实现无与伦比的卡(10×),但受限于 Java 层的阉割版 Canvas,也没有特别好的实现方式。

总结
PAG包含了当时Lottie支持的所有功能,并彻底摆脱了导出和渲染上的能力扩展相关限制。相比于Lottie方案,PAG不仅解决了在它在矢量动画渲染上的各种问题,更重要的是扩展了动画的使用场景,PAG围绕运行时可编辑性设计了灵活的架构,能够完美实现动画的局部编辑替换需求。

svga VS pag

由于这两种格式文件都说可以带音效,于是我跪求UI同学帮忙生成了对应的svga和pag的文件,然后可以明显看出,在无音频pag确实比svga小一点,但带了声音后,却大的离谱。svga vs pag对比
单从文件大小上确实很难定义谁会更好一点,那接下来咱们就再从代码上入手看下:

// svga
npm i svgaplayerweb -D
// svga首先是不能用本地资源的,所以资源一定是从服务器上去拿的
// player.onFrame 实时监控播放第几帧,回调函数第一个参数即为当前帧数
// player.stepToFrame(开始帧数, 布尔值)  第二个值为true是从指定帧开始播放,第二个值为false时是停止在指定帧数
//player.pauseAnimation    = >     暂停动效
// player.stopAnimation     = >     停止动效
// player.loops 动效轮循次数,loop表示无限循环
// player.clearsAfterStop  是否清除结束后定帧(默认清除)  布尔类型,ture => 清除  false => 不清除
 <!-- svga支持音效文件播放音效必须在根页面加这个 https://www.wenjiangs.com/article/svgaplayer-web.html-->
 <!-- <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/howler.core.min.js"></script> -->
    
import React,{Component} from 'react'
import './index.css'
import SVGA from 'svgaplayerweb'

export default class svga extends Component{
  constructor(props){
    super(props)
    this.state = {

    }
    this.start1 = 'colorv/resource/2e8096b11512561a74c661c42575d37c.svga'
    this.start = 'colorv/resource/0112be02f5b2b635dfd05c238107d92a.svga' // 进场开始
    // this.svgaV = '../../../public/花灯音.svga'  // 无法播放本地资源
    this.svgaV = 'colorv/resource/cc427c9fa0e7dea05e5351fbb1d91b2c.svga'
  }
  componentDidMount() {
    this.setSvga(this.start1, 1)
  }

  // svga动效
  setSvga = (src, count) => {
    let element = document.getElementById('banner')
    if (!element) return
    let player = new SVGA.Player('#' + element.id)
    let parser = new SVGA.Parser('#' + element.id) // 如果你需要支持 IE6+,那么必须把同样的选择器传给 Parser。
    parser.load('https://xhr.res.cs-video.com/' + src, (videoItem) => {
      player.loops = count
      player.setVideoItem(videoItem)
      player.startAnimation()
      player.onFrame((i) => {
        if (i === 29 && count === 1) {
          this.setSvga(this.svgaV, 'loop')
        }
      })
    })
  }
  render() {
    return  <div className="banner" id="banner" style={{ zIndex: 99 }}>
      <img src='https://xhr.res.cs-video.com/colorv/resource/59c403e702ca3251b536163497ee4eee.png' alt='' />
    </div>
  }
}

// pag 是可以直接读取本地资源的
npm i libpag -D<script async src="https://unpkg.com/libpag@latest/lib/libpag.min.js"></script>

// onAnimationStart 开始
// onAnimationEnd 结束
// onAnimationCancel 取消
// onAnimationRepeat 重复次数
// onAnimationUpdate 更新
// onAnimationPlay 播放
// onAnimationPause 暂停
// onAnimationFlushed 刷新


import './App.css';
import { useEffect,useRef } from 'react';
import { PAGInit } from 'libpag';
import Svga from './components/svg'

const FlowerV = './花灯音.pag';
const Like = 'https://cdn-app-tx-bj.colorv.com/colorv/resource/aca8a2d907495223464a716709e836df.pag'
function App() {
const pagRef=useRef(null)

  useEffect(() => {
    initPag(Like,1)
  },[]);

  const initPag = (url, count) => {
    // if(pagRef.current)return
      PAGInit().then((PAG) => {
        fetch(url)
          .then((response) => response.arrayBuffer())
          .then(async (buffer) => {
            const pagFile = await PAG.PAGFile.load(buffer);
            const canvas = document.getElementById('pag');
            const width = pagFile.width(); // pag文件的宽度
            const height = pagFile.height(); // pag文件的高度
            const duration = pagFile.duration(); // pag文件的时长
            const frameRate = pagFile.frameRate(); // pag文件的帧率
            // pagFile.setAlpha(.3) // 设置透明度
            console.log('宽:'+ width,'高:'+height,'总时长:'+duration,'帧率:'+frameRate)
            canvas.width = 400;
            canvas.height = 400;
            pagRef.current = await PAG.PAGView.init(pagFile, canvas,{ useScale: false });
            pagRef.current.setRepeatCount(count); // 设置动画重复的次数。默认值是1,这意味着动画只播放一次。0表示动画将播放无限次。
            await pagRef.current.play();
            if(count===1){
              await pagRef.current.addListener('onAnimationEnd',(i)=>{
                console.log('222',i)
                initPag(FlowerV, 0)
              }) // onAnimationEnd 结束的生命周期名称
            }
            // console.log(pagRef.current.videoEnabled()) // 是否绘制图层,默认true
            // await pagRef.current.setVideoEnabled(false) // 更改是否绘制属性,传布尔值 false为不绘制
          });
      });
  }
  
  const play = ()=>{ // 开始
    pagRef.current.play()
  }
  const stop = ()=>{ // 停止
    pagRef.current.stop()
  }
  const pause = ()=>{ // 暂停
    pagRef.current.pause()
  }
  const destroy = ()=>{ // 销毁

    pagRef.current.destroy();
  }

  

  return (
    <div className="App" style={{background:'red'}}>
      <img src="https://img1.baidu.com/it/u=3009731526,373851691&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500" alt="" />
      <canvas id="pag" style={{background: 'rgba(0,0,0,0)'}} />
      <button onClick={play}>开始</button>
      <button onClick={pause}>暂停</button>
      <button onClick={stop}>停止</button>
      <button onClick={destroy}>销毁</button>

      <Svga/> 
    </div>
  );
}

export default App;


总结
当然除了上面用到的方法外,还有很多别的api,篇幅有限这里就简单对比一下。到目前为止,我本人并没有找到pag带音频的播放方法,包括pag线上预览器也无法播放声音,只有UI同学那边AE自带的pag查看器才能正常播放。
pag毕竟是今年才刚开源,一些基础生态更偏向于腾讯内部使用,官方文档也大多是源码直接抛出的方法,也非常混乱,没有像vue和react官网那样有条理,方法又多是英文描述,对于像我一样英文很菜小伙伴来说阅读是一个很大的障碍。就目前为止,我个人感觉对web前端来说,pag不是非常友好,更适合于ios、Android原生开发,也是苦于自己实力有限,只能暂时寄希望于腾讯或者别的大佬能有进一步的完善,那么,今天的分享就到这里吧,喜欢的小伙伴记得点赞哦!

你可能感兴趣的:(前端动效,javascript,动画,webview,html5,前端)