> 文档中心 > 【FFH】Canvas实现帧动画及封装(OpenHarmony JS UI)

【FFH】Canvas实现帧动画及封装(OpenHarmony JS UI)

目录C

Demo展示

实现思路

代码封装

canvas绘制图像

动画播放

代码调用


Demo展示

这里以Tom猫(多年前热门的移动端互动小游戏)为例:

实现思路

首先要了解帧动画播放的原理——正如我们平时看电视看视频,视频通过每一帧图片按顺序快速切换来产生“动”起来的效果。
因此可以通过canvas组件提供的drawImage加定时器的方法来实现快速绘帧、渲染的效果。

代码封装

(这里我封装在model的cv.js)
当项目中有很多动画播放的地方,代码需要复用,就要把播放动画的代码封装起来,以便提供给其它页面或组件调用,减少冗余代码量。

canvas绘制图像

首先先来看一下canvas如何绘制对象,以在页面onShow生命周期上绘制初始画面为例:

onShow(e) { //---显示初始画面 let e1e = this.$refs.canvas_1; let ctx = e1e.getContext('2d'); let img_1 = new Image(); img_1.src = 'common/images/eat/eat_01.jpg'; ctx.drawImage(img_1, 0, 0, 640, 640); //---这里绘制了一个刚好覆盖画布的图像(640px; 640px)    },

动画播放

播放动画大致能分成三步:

  1. 获取画布及待播放动画信息
  2. 动画预加载
  3. 动画播放

这里选择以下方式储存动画对象信息:

let srcS = [ //---储存动画信息---这里存了两种动画信息eat和knock    { id: "eat", //---ID src: "common/images/eat/eat_", //---路径前段 len: 40, //---图片数 setInt: 90, //---定时器间隔(ms)---根据帧数自行调整 width: 640, //---绘制宽度 height: 1024, //---绘制高度 x: 0, //---相对于画布左上角的X坐标 y: 0, //---相对于画布左上角的Y坐标 format: '.jpg'    },    { id: "knock", src: "common/images/knockOut/knockout_", len: 80, setInt: 90, width: 640, height: 1024, x: 0, y: 0, format: '.jpg'    },]

通过id匹配动画信息:

function selectInfo(id, cb) { //---找到对应的动画对象    let obj = srcS.find(e => e.id === id);    cb(obj);}

匹配后进行预加载:
 

function imgLoad(obj, cb) { //----预加载    let imgArray = []; //---存储Image对象    let len = obj.len;    for (let i = 0; i  9) j = ''; let str = j + i.toString(); let img = new Image(); img.src = obj.src + str + obj.format; //---设置Image对象路径,假设这里图片的路径编号是00,01,02...10...39 imgArray.push(img);    }    cb(imgArray, len, obj.x, obj.y, obj.width, obj.height, obj.setInt); //---回调函数中传递参数}

传入加载好的数组进行动画播放:

function Action(obj, ctx) { //---动画播放    return new Promise((resolve) => { //---采用Promise进行动画播放,执行完再释放线程,避免多次点击播放造成混乱 let i = 0, x, y, w, h; let len, imgArray, interval; imgLoad(obj, (imgArray_, len_, x_, y_, w_, h_, interval_) => {     console.info("预加载完毕");     imgArray = imgArray_; //---设置drawImage参数     len = len_;     x = x_;     y = y_;     w = w_;     h = h_;     interval = interval_;     Iv[count++] = setInterval(() => { //---定时器  if (i < len) {      if (i !== 0)ctx.clearRect(x, y, w, h); //---不断绘制新图-清除旧图      ctx.drawImage(imgArray[i], x, y, w, h);      ++i;  } else {      ctx.drawImage(imgArray[len - 1], x, y, w, h); //---可选择保留结尾动作      clearInterval(Iv[count - 1]); //---清除定时器,动画结束      resolve("false");  }     }, interval); })    })}

注意(drawImage)每绘制一次需要手动清除(clearRect)上一张的图片,不然上一张图片会存留在页面中。

对外提供的接口:

export function ActionReady(id, ctx) { //---接口---参数:(动作id,画布2d对象)    let obj;    selectInfo(id, (obj_) => { obj = obj_;    })    return Action(obj, ctx);}

代码调用

本例子展示的是Tom猫的两种动作:eat和knock,通过点击不同部位按钮的方式完成交互。
这里获取动作id的方法是通过按钮(在CSS中设置成透明)点击后获取按钮元素属性id,也可以用其它方式如 屏幕坐标判断 等方式自行定义id。

导入封装好的cv.js模块和接口

import { ActionReady } from '../../common/model/cv'import prompt from '@system.prompt';export default {    data: { isDisable: false, offsetY: 0, offsetX: 0, wight: 640, height: 1024    },    onShow(e) { //---显示初始画面 let e1e = this.$refs.canvas_1; let ctx = e1e.getContext('2d'); this.offsetX = 0; this.offsetY = 0; let img_1 = new Image(); img_1.src = 'common/images/eat/eat_01.jpg'; ctx.drawImage(img_1, this.offsetX, this.offsetY, this.wight, this.height);    },    play(e) { //---多个组件调用同一函数,通过id进行区别播放 this.showToast("开始播放"); let id = e.target.id; //---获取动作id let e1e = this.$refs.canvas_1; //---获取对应的画布 let ctx = e1e.getContext('2d'); let promise = ActionReady(id, ctx); //---调用封装好的获取异步结果 this.isDisable = true; //---动作播放结束前按钮不可点击 promise.then((res) => {     this.isDisable = res; //---动画播放完毕,恢复按钮     this.showToast("播放完毕"); })    },    showToast(mes) { prompt.showToast({     message: mes,     duration: 2000, })    }}
.container {    flex-direction: column;    justify-content: center;    align-items: center;    left: 0px;    top: 0px;    width: 100%;    height: 100%;}.eatBtn {    margin-top: 335px;    margin-left: 150px;    width: 350px;    height: 210px;    position: absolute;    opacity: 0;}.knockOutBtn{    margin-top: 180px;    margin-left: 130px;    width: 370px;    height: 10%;    position: absolute;    opacity: 0;}

这样就通过调用封装好的动画播放完成了一个小Demo,可以看到渲染页面的代码量很少。