使用 apng 实现动画的暂停播放等一系列功能

2021/08/28 功能实现 共 2645 字,约 8 分钟

背景

最近来了一个需求:

  1. 用户长按的时候有一个的动画A,用户长按时间达到了3s就停止在最后一帧。用户长按的时间不够3s动画A就在用户手指抬起的时候回到原来的状态

  2. 满足长安的需求之后,用户手指抬起的时候,执行动画B,动画B执行的一半的时候,加入动画C。此时动画BC同时进行。BC动画进行到某一帧的时候,页面加入一个随机的DOM元素内容

  3. 这个动画的播放和用户的操作有关系,文案效果展示和后端api有关系

那么该如何实现呢?

css 动画+background-positoin定位能动画播放效果,但是这播放时机,帧率,速率等都不可以控制

gif 不支持Alpha透明通道,颜色展示也不太行,和css实现有一样的问题

那么要怎么完美的实现这个需求呢?

调研发现可以用apng,简单说就是PNG动图,跟Gif动图一样,是由很多帧构成的,第1帧就是一张标准的PNG图片,后面的帧不仅包含PNG图片,还有剩余的动画和帧速等数据。于是,如果浏览器不认识APNG后面的动画数据,没关系,因为第1帧是标准的,可以无障碍显示;如果支持,自然就后面的帧走起,动画效果就有了!

如何实现呢?

我们借用canvas,然后不断的去读图片上面的每一帧,然后把读取到的每一帧绘制在canvas上面,展示给用户看。不停的读取绘制,中间不间断就形成了一个动画。

如果中间打断一下,就实现了暂停播放。你想在哪一帧暂停,那里播放,停止在哪都可以实现了。

如何把图片的每一帧绘制到canvas上面去呢?我选择了三方库 https://github.com/davidmz/apng-js

我测试了一些安卓6都是支持的,效果表现的都很不错。

import parseAPNG from "apng-js";
  // 图片转成buffer流
  const getImgBuffer = (url) => {
    return new Promise(async (resolve) => {
      const blob = await fetch(url).then((res) => res.blob());
      const reader = new FileReader();
      reader.readAsArrayBuffer(blob);
      reader.onload = () => {
        resolve(reader.result);
      };
    });
  };
  // 动画开始播放
  const createApngPlayer = async (url, ctx, options = {}) => {
    const imgBuffer = await getImgBuffer(url);
    const apng = parseAPNG(imgBuffer);
    Object.keys(options).forEach((key) => {
      apng[key] = options[key];
    });

    const player = await apng.getPlayer(ctx);
    return player;
  };

  // 手指移动上去,页面动画
  const handlerTouchStart = () => {
    console.log("手指移动上去");
  };
  // 手指离开页面动画
  const handlerTouchEnd = async () => {
    // 谈起的动画A
    const jumpPlayer = await createApngPlayer(pigJump, pigEle, { numPlays: 1 });
    jumpPlayer.play();

    // 动画A执行到一半加入动画B
    setTimeout(async () => {
      const coinPlayer = await createApngPlayer(coin, coinEle, { numPlays: 1 });
      coinPlayer.play();
    }, 400);

    // 动画A执行到某一帧的时候
    jumpPlayer.on("frame", async (values) => {
      if (values == 16) {
        
        console.log("这里要做别的操作了");
      }
    });
    // 动画A完了
    jumpPlayer.on("end", async () => {
      
    });
  };

  // 长按操作
  const handlerPress = async () => {

    const pressPlayer = await createApngPlayer(
      pigPress,
      pigEle,
      { numPlays: 1 },
      "time"
    );

    pressPlayer.play();

    pressPlayer.on("end", async () => {
     console.log('长按操作动画完成')
    });
  };


总结

这个需求预计下周就能上线吧,有兴趣的可以去去体验一下,

在做这个需求的时候,要感谢一下张老师给予了很多帮助,让我的css又厉害了一点点

  1. ios 安卓,手机不一样,元素的宽高也会不一样,如果这个时候出现了定位在两端表现不一样,那就是你定位父级写错了,你不要基于祖先元素定位,而是基于里面的某一个小盒子定位,设置bottom:100%或者top:100%,然后移动marge-top或者marge-bottom去实现位置便宜 => 这个问题我卡了一天,后来张老师分分钟帮忙搞定

  2. apng 绘制到canvas上面的时候,apng画布多大,canvas元素的width和height就多大,否则会出现apng只绘制了一部分在canvas上面。

  3. 如果apng给的是三倍图,你的元素只要一倍图的大小,不要使用zoom或者transform去缩放,完全没有必要,我当时使用这种方式缩放,结果遇到了别的问题。张老师说,你就给canvas的stylewidth和height缩小三分之一就好了,canvas元素上面width和height一定要和大于等于原图片大小

  4. 如果第一帧或者最后一帧你是单独用img实现的,最后一帧有位置上对不上的时候,你就用canvas里面的最后一帧展示给你的用户,不要自己用img了

  5. 必须是长按动画的,点击无效,这时候你可能会加很多的逻辑判断,慢慢来一定可以解决的

  6. 有张老师的技术支持,感觉很不错,又学到了新的知识,这次时间赶,用了第三方的差距,有机会我要自己写一个类似这样的,自己写的可控性更好

  7. 给自己给一碗鸡汤,你想要的生活都像在你走来

    参考资料

    使用 apng-js 控制 apng 动画的播放

APNG历史、特性简介以及APNG制作演示

关于gif:Web-端-APNG-播放实现原理

聊聊APNG

Web 端 APNG 播放实现原理

解锁前端动画新姿势-APNG动画


在技术的历史长河中,虽然我们素未谋面,却已相识已久,很微妙也很知足。互联网让世界变得更小,你我之间更近。

在逝去的青葱岁月中,虽然我们未曾相遇,却共同经历着一样的情愫。谁的青春不曾迷茫或焦虑亦是无奈,谁不曾年少过

在未来的日子里,让我们共享好的文章,共同学习进步。有不错的文章记得分享给我,我不会写好的文章,所以我只能做一个搬运工

我叫 sunseekers(张敏) ,千千万万个张敏与你同在,18年电子商务专业毕业,毕业后在前端搬砖

如果喜欢我的话,恰巧我也喜欢你的话,让我们手拉手,肩并肩共同前行,相互学习,互相鼓励

文档信息

Search

    Table of Contents