评论

让我心心念的转场动画-「微剪」1.5版本剖析

记于西安初雪日,微剪插件1.5.1版本剖析,转场动画和动效

周末听闻好多地方降雪了,西安也迎来了2020年的第一场雪,想着这么美好的日子不写点代码可惜了,刚打开小程序开发者工具没多久就收到了「微剪」插件发布新版本的提醒,赶紧去官网一探究竟。看完更新内容后,我直呼“好家伙”,之前一直吐槽“素材切换的时候没有转场动画”的问题,在新版本中得到了改善。新版本增加了对转场动画和播放动效的支持,使得上述问题迎刃而解。另外云函数的加持也使得插件使用在线资源变得更加便捷,接下来就让我们体验下此次升级的几大亮点。

动效和转场动画

看过我之前文章的同学,应该记得我不止一次提到转场动画的缺失的问题,动画的缺失会导致最终输出的作品在素材切换的时候显得突兀与僵硬。作为插件的使用者,我也向官方团队提到过这个需求,当时得到的答复是“内部评审通过的话会进需求池的”,没想到这么快就实现了这个需求,效率之高着实让人欣慰,开发者的意见被采纳也让我觉得自己有被重视到,这里向官方团队来一波彩虹屁❤️❤️❤️

动效和转场动画与之前的特效、滤镜、文字等一样,都是作为插件的内置资源供开发者使用,可以在app.js中通过下述方式,获取插件内置动效和动画:

var myPluginInterface = requirePlugin('myPlugin');
App({
  onLaunch: function () {
    let resources = myPluginInterface.resources
    let {
    	…… // 其他资源信息
    		transitions, // 转场动画
    		operations  // 动效
    } = resources
  }
})

我滴妈耶,插件内置的转场动画多达66个,爱了爱了😻,够我研究一段时间了。

获取到内置资源后,通过以下几个步骤为我们的素材添加动效和转场动画(不想看过程也可直接拉到最后看最终效果和代码):

1. 准备基础素材

这里我们准备三张图片作为基础素材,每张的展示时长为3s

const MEDIA_LIST_ONLINE = [
  {
    url: 'https://cdn.pixabay.com/photo/2020/11/12/13/44/labrador-retriever-5735582__480.jpg',
    width: 720,
    height: 480
  },
  {
    url: 'https://cdn.pixabay.com/photo/2020/11/08/10/25/dog-5723334__480.jpg',
    width: 386,
    height: 480
  },
  {
    url: 'https://cdn.pixabay.com/photo/2020/09/22/11/36/pine-cones-5592802__480.jpg',
    width: 720,
    height: 480
  }
]
let { TRACK_TYPES, CLIP_TYPES, Clip, ClipSection, Track } = global['wj-types']
// 构造基础素材轨道
let mediaTrack = new Track({
  type: TRACK_TYPES.MEDIA,
  clips: []
})
let startAt = -3
let mediaClipList = MEDIA_LIST_ONLINE.map((item, index) => {
  startAt += 3
  return new Clip({
    type: CLIP_TYPES.IMAGE,
    id: `clip_${index}`,
    startAt,
    info: {
      width: item.width,
      height: item.height,
      tempFilePath: item.url
    },
    section: {
      start: 0,
      end: 3
    }
  })
})
mediaTrack.clips = mediaClipList

2. 添加转场动画

按照文档说明,转场动画也是以clip的形式存在于两个素材clip之间,so easy

// 素材之间添加转场动画
const transitionDuration = 1 // 转场动画持续时长
for (let i = 1; i < mediaClipList.length; i++) {
  const clipFrom = mediaClipList[i - 1], clipTo = mediaClipList[i]
  let transClip = new Clip({
    type: CLIP_TYPES.TRANSITION,
    id: `trans_${i - 1}_${i}`,
    info: { // 标记转场的两个片段
      fromId: clipFrom.id,
      toId: clipTo.id
    },
    startAt: clipFrom.startAt + clipFrom.section.end - clipFrom.section.start - transitionDuration,
    section: {
      start: 0,
      end: transitionDuration
    },
    key: 'Blur' // 上述转场动画中挑一个取其key,放这里完事
  })
  mediaClipList.splice(i, 0, transClip) // 插入转场动画至两个素材之间(⚠️不能这么写,下面会说明)
  ++i
}
mediaTrack.clips = mediaClipList

本以为到这里就可以看到成果了,结果一运行直接打脸,控制台报错“过渡加载失败”,emmmm,年轻人你不讲武德啊。

这里调试了好久,确认数据的正确性,毫无头绪,后来又仔细看了下官方文档,发现文档是把所有的转场clip放在了clips数组的最后面,试了下果然是这个原因,正确的代码如下所示:

// 素材之间添加转场动画
const transitionDuration = 1 // 转场动画持续时长
for (let i = 1, j = mediaClipList.length; i < j; i++) {
  const clipFrom = mediaClipList[i - 1], clipTo = mediaClipList[i]
  let transClip = new Clip({
   ……
  })
  mediaClipList.push(transClip)
}
mediaTrack.clips = mediaClipList

3. 添加动效

动效是作为clip的operations,作用在当前clip上的,目前插件暴露的动效列表如下所示:

{
  zoom: [ // 变焦
    "zoomIn",  // 拉近
    "zoomOut", // 拉远
    "moveLeft", // 放大左移
    "moveRight" // 放大右移
    "moveUp", // 放大上移
    "moveDown" // 放大下移
  ],
  leave: [ // 出场
    'fadeOut' // 渐隐消失
  ],
  enter: [ // 入场
  	'fadeIn' // 渐现
  ]
}

为clip添加动效很简单,代码如下所示:

const testOpts = [
  {
    key: 'zoomIn',
    type: 'zoom'
  },
  {
    key: 'moveRight',
    type: 'zoom'
  },
  {
    key: 'zoomOut',
    type: 'zoom'
  }
]
let mediaClipList = MEDIA_LIST_ONLINE.map((item, index) => {
  const { key: optKey, type: optType } = testOpts[index % testOpts.length]
  return new Clip({
   ……
   operations: [  // 添加动效数组
      new ClipOperation({
        id: `opt_${index}`,
        key: optKey, // 动效key
        type: optType, // 动效type
      })
    ]
  })
})

4. 阶段性成果

至此我们就为原始的3张图片添加了转场动画和动效,运行上述代码,会发现虽然转场动画有了,但是素材切换的时候还是有点不自然,这里引用下官方的解决方案:

如上图所示,将后面素材的startAt时间向前移动「Transition时长」的距离,这样两个相邻的clip就有了重叠的部分,转场的时候就更加自然,代码如下:

……
let mediaClipList = MEDIA_LIST_ONLINE.map((item, index) => {
  startAt += 3
  const { key: optKey, type: optType } = testOpts[index % testOpts.length]
  return new Clip({
  ……
  })
// 素材之间添加转场动画
const transitionDuration = 1 // 转场动画持续时长
for (let i = 1, j = mediaClipList.length; i < j; i++) {
  const clipFrom = mediaClipList[i - 1], clipTo = mediaClipList[i]
  let transClip = new Clip({
  ……
  })
  clipTo.startAt -= transitionDuration*i // ⚠️更新开始时间,使转场更自然
  mediaClipList.push(transClip)
}

5. 添加离场动画

我们还可以为最后一个素材添加简单的离场动画,如下所示:

// 为最后一个素材添加离场动画
let fadeoutOpt = new ClipOperation({
  id: 'fadeout',
  key: "fadeout",
  type: "leave",
  workOnAllSiblings: true,
});
mediaClipList[mediaClipList.length-1].operations.push(fadeoutOpt)
  • workOnAllSiblings 这个属性在有贴纸、文字等素材的时候,为true则会为其也添加离场效果,否则只作用到当前的素材clip

最终成果

  // 转场动画
  getTransTrack() {
    let { TRACK_TYPES, CLIP_TYPES, Clip, ClipSection, ClipOperation, Track } = global['wj-types']
    const testOpts = [
      {
        key: 'zoomIn',
        type: 'zoom'
      },
      {
        key: 'moveRight',
        type: 'zoom'
      },
      {
        key: 'zoomOut',
        type: 'zoom'
      },
    ]
    // 构造基础素材轨道
    let mediaTrack = new Track({
      type: TRACK_TYPES.MEDIA,
      clips: []
    })
    let startAt = -3
    let mediaClipList = MEDIA_LIST_ONLINE.map((item, index) => {
      startAt += 3
      const { key: optKey, type: optType } = testOpts[index % testOpts.length]
      return new Clip({
        type: CLIP_TYPES.IMAGE,
        id: `clip_${index}`,
        startAt,
        info: {
          width: item.width,
          height: item.height,
          tempFilePath: item.url
        },
        section: {
          start: 0,
          end: 3
        },
        operations: [
          new ClipOperation({
            id: `opt_${index}`,
            key: optKey,
            type: optType,
          })
        ]
      })
    })
    // 为最后一个素材添加离场效果
    let fadeoutOpt = new ClipOperation({
      id: 'fadeout',
      key: "fadeout",
      type: "leave",
      workOnAllSiblings: true,
    });
    mediaClipList[mediaClipList.length-1].operations.push(fadeoutOpt)
    // 素材之间添加转场动画
    const transitionDuration = 1 // 转场动画持续时长
    for (let i = 1, j = mediaClipList.length; i < j; i++) {
      const clipFrom = mediaClipList[i - 1], clipTo = mediaClipList[i]
      let transClip = new Clip({
        type: CLIP_TYPES.TRANSITION,
        id: `trans_${i - 1}_${i}`,
        info: { // 标记转场的两个片段
          fromId: clipFrom.id,
          toId: clipTo.id
        },
        startAt: clipFrom.startAt + clipFrom.section.end - clipFrom.section.start - transitionDuration,
        section: {
          start: 0,
          end: transitionDuration
        },
        key: 'Blur' // 转场的key
      })
      clipTo.startAt -= transitionDuration * i // 更新开始时间,使转场更自然
      mediaClipList.push(transClip)
    }
    mediaTrack.clips = mediaClipList
    return [mediaTrack]
  }

gif图看着不流畅,可以看下导出的视频效果 https://v.qq.com/x/page/h3204o6wxfj.html

PS:插件最新版本增加了【影集】模式,就包含了上述转场动画和动效,大家可以搜索「微剪插件演示」小程序自行体验一下。

自定义资源白名单配置

1.5.0版本之前如果涉及在线资源,比如图片,水印等,需要在微信公众平台配置相应的域名白名单;新版本优化了这部分配置。以文章开头那个demo为例,我们使用了3张网络图片,1.5.0版本之前需要将其域名https://cdn.pixabay.com添加到我们的小程序白名单中,新版本则无需额外配置便可使用。详情参考版本说明

一些思考

加入转场动画之后,为了使转场更加自然,相邻两个素材会产生重叠,所以导出视频的总时长就会缩短。文章开头的例子,没有转场动画导出时长为9s,加了转场动画后,导出为7s。

  • 如果是图片的话其实还好处理,只需要将有重叠部分的clip的section延长相应时长即可;
  • 比较复杂的是视频,由于我们没办法改变原始视频的时长,这个时候如果有一些与视频的时间点强依赖的效果,比如字幕,处理起来就比较麻烦:以图片+视频为例,重叠部分已经开始播放视频了,但是上一个素材(图片)还未完全消失,此时字幕的显隐就需要好好考究一下了,而且随着视频startAt值的改变,若有与时间点强依赖的clip(贴纸、特效等)的startAt都需要对应的调整,略微有些繁琐,不知道各位读者有什么好的解决方案,可以在下面讨论一下。

总结

插件此次更新还是很深得我心的,转场动画和动效使得导出的最终成果更加流畅自然,内置60多种转场动画也是很有良心了;另外新版本使用在线资源更加便捷,我体验了一下,由于需要预先下载,所以相比于本地资源,player组件加载的时间略长。

示例代码片段已同步更新了,感兴趣的同学自行 下载

写完文章看了眼时间已经凌晨了,一直很喜欢下雪天的日子,纵使窗外车水马龙,也总能让人静下心来。不过西安的降雪还是略小,突然有点怀念大连的冬天了,寒风刺骨却又让人精神抖擞……

最后一次编辑于  2020-12-09  
点赞 2
收藏
评论

2 个评论

  • 冬瓜
    冬瓜
    2021-03-23

    厉害,可以拿来做个影集给老婆。。。

    2021-03-23
    赞同
    回复
  • 杨
    2020-11-23

    很赞👍

    2020-11-23
    赞同
    回复
登录 后发表内容