canvas精灵图动画-基于Vue3

drawImage 讲解

绘制图片
在Canvas中,我们使用drawImage()方法绘制图片。drawImage()方法有如下3种调用方式:

1.drawImage(image, dx, dy)
	-- 参数image表示页面中的图片。
	-- 参数dx表示图片左上角的横坐标;
	-- 参数dy表示图片左上角的纵坐标;
2.drawImage(image, dx, dy, dw, dh)
	-- 前三参数同上
	-- 参数dw为图片宽度;
	-- 参数dh为图片高度。
3.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)
	-- 参数image, dx, dy, dw, dh 同上
	-- sx, sy 偏移坐标
    -- sw, sh 偏移宽高

今天,我们实现如下图一个精灵动画,通过选择不同的动画类型,实现动画切换
在这里插入图片描述

精灵图的实现原理,是通过不断调整偏移坐标来实现。
首先我们需要确定单独一个图形的宽高,如下图
在这里插入图片描述
这张精灵图分辨率为6876 × 5230,而横向最多有12个图像,纵向最多有10个图像,所以单个图像的分辨率为(6876 / 12 =)573 × (5230 / 10 = =)523,所以就可以定义如下变量

const spiritWidth = 573;
const spiritHeight = 523;
初始化画布和加载图片
async function init() {
  canvas.value.height = w_h;
  canvas.value.width = w_h;
  ctx.value = canvas.value.getContext('2d');
  return new Promise(resolve => {
    image.src = imgSpirit;
    image.onload = function () {
      resolve('success');
    }
  })
}

这里是通过Vue方式获取的canvas dom,如果通过纯js,如下

let canvas = document.getElementById('canvas')let canvas = document.querySelector('#canvas')

Image加载是一个异步操作过程,通过Promise封装,再结合async/await将后续逻辑同步化。

动画实现方法
function draw() {
  ctx.value.clearRect(0, 0, w_h, w_h);
  ctx.value.drawImage(image, offsetX * spiritWidth, offsetY * spiritHeight, spiritWidth, spiritHeight
      , 0, 0, spiritWidth, spiritHeight);
  if (offsetX < 6) {
     offsetX++;
   } else {
     offsetX = 0;
  }
  requestAnimationFrame(draw);
}

通过不断累加offsetX偏移量,不断切换图片位置。这事就实现了一个简单的动画效果。但是我们在想一下,可不可以在每一帧切换的之间加一点延迟,或者说是交替针,所以就了如下代码

 if (delayAccrual % delay.value === 0) {
    if (offsetX < 6) {
      offsetX++;
    } else {
      offsetX = 0;
    }
  }
  delayAccrual++;

通过取余的形式,控制offsetX累加的情况。但是发现这太简单,那我们就一种思路。就有如下代码

  let position = Math.floor(delayAccrual / delay.value) % 6;
  offsetX = position * spiritWidth;
  ctx.value.drawImage(image, offsetX, offsetY * spiritHeight, spiritWidth, spiritHeight
      , 0, 0, spiritWidth, spiritHeight);
  delayAccrual++;

Math.floor(delayAccrual / delay.value) % 7 得到的是0-6这7个数,而一行精灵图也是从0开始的。

任意一个数字 % 一个数字(可以认为x)  得到的数据规律是 0 - (x - 1)
整体数据

但是这张精灵图是12 × 10,现在我们是写死的,下面我们就做成开头所说的那样。整备数据如下。

data.ts

interface list {
    key: string,
    name: string,
    spirits: number
}
export const spiritList: Array<list> = [
    {
        key: 'idle',
        name: '空闲',
        spirits: 7
    },
    {
        key: 'jump',
        name: '跳动',
        spirits: 7
    },
    {
        key: 'fall',
        name: '落下',
        spirits: 7
    },
    {
        key: 'run',
        name: '跑',
        spirits: 9
    },
    {
        key: 'dizzy',
        name: '晕眩',
        spirits: 11
    },
    {
        key: 'sit',
        name: '坐',
        spirits: 5
    },
    {
        key: 'roll',
        name: '滚动',
        spirits: 7
    },
    {
        key: 'bite',
        name: '咬',
        spirits: 7
    },
    {
        key: 'ko',
        name: '击倒',
        spirits: 12
    },
    {
        key: 'get hit',
        name: '被击中',
        spirits: 4
    }
]

我们通过下拉框选择切换动画,拿到的数据是key数据,现在数据是一个数组,通过find或者filter有点麻烦,那我们就把数据格式化成Map形式,key就是数组中 key,这就有了如下方法

function getSpiritObj() {
  spiritSelector.value.forEach(({key, spirits}, index) => {
    spiritMap.set(key, {
      spirits,
      y: index
    })
  })
}

因为数组的顺序就是按照图片顺序构造的,所以index就是y坐标。

完整代码

HTML

<template>
  <el-container class="frame">
    <el-aside width="400px" class="aside">
      <el-form class="form" label-width="80px">
        <el-form-item label="动画类型">
          <el-select v-model="animateType" style="width: 100%" placeholder="请选择" size="middle">
            <el-option
                v-for="item in spiritSelector"
                :key="item.key"
                :label="item.name"
                :value="item.key"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="延迟">
          <el-input-number v-model="delay" controls-position="right" :min="0" style="width: 100%" />
        </el-form-item>
      </el-form>
    </el-aside>
    <el-main class="main">
      <div class="spirit">
        <canvas ref="canvas" class="canvas"></canvas>
      </div>
    </el-main>
  </el-container>
</template>

JS

import {onMounted, ref} from "vue";
import {spiritList} from "@/views/spirit/data";

const imgSpirit = require('../../assets/spirit/spirit1.png')
let canvas = ref(null);
let ctx = ref(null);
let w_h = 600;
const spiritWidth = 573;
const spiritHeight = 523;
const image = new Image();
let offsetX = 2;
let offsetY = 0;
let delayAccrual =  0;
let delay = ref(5);
const spiritSelector = ref(spiritList);
let animateType = ref('idle');
let spiritMap = new Map();

async function init() {
  canvas.value.height = w_h;
  canvas.value.width = w_h;
  ctx.value = canvas.value.getContext('2d');
  getSpiritObj();
  return new Promise(resolve => {
    image.src = imgSpirit;
    image.onload = function () {
      resolve('success');
    }
  })
}

function getSpiritObj() {
  spiritSelector.value.forEach(({key, spirits}, index) => {
    spiritMap.set(key, {
      spirits,
      y: index
    })
  })
}
function draw() {
  let currentSpirit = spiritMap.get(animateType.value);
  if (!currentSpirit) {
    return;
  }
  ctx.value.clearRect(0, 0, w_h, w_h);
  let position = Math.floor(delayAccrual / delay.value) % currentSpirit.spirits;
  offsetX = position * spiritWidth;
  offsetY = currentSpirit.y  * spiritHeight
  ctx.value.drawImage(image, offsetX, offsetY, spiritWidth, spiritHeight
      , 0, 0, spiritWidth, spiritHeight);
  delayAccrual++;
  requestAnimationFrame(draw);
}

onMounted(async () => {
  await init();
  draw();
})

CSS

.frame {
  height: calc(100vh);
}
.aside {
  background-color: rgb(217, 235, 255);
}
.main {
  background-color: rgb(236, 245, 255);
}
.spirit {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
}
.form {
  padding: 10px;
}
.canvas {
  border: 2px solid #343a42;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
"hufe.club/canvas-editor" 是一个基于 Vue.js 构建的富文本编辑器插件。Vue.js 是一款流行的渐进式 JavaScript 框架,常用于构建用户界面和单页应用(SPA)。Canvas-Editor 可能提供了一个功能强大的组件,让用户能够轻松地在 Web 应用中添加可定制的文本编辑区域,支持诸如样式、图片插入、表格等常见的编辑功能。 这个编辑器可能具备的特点包括: 1. **Vue 组件化**:易于与其他 Vue 项目集成,并且可以通过组件化的思想复用代码。 2. **实时渲染**:用户输入的内容即时更新,提高用户体验。 3. **API 非侵入**:允许开发者根据需要扩展或自定义编辑行为。 4. **响应式设计**:适应不同屏幕尺寸,适配移动设备。 如果你对如何在自己的 Vue 项目中使用这个编辑器或者具体配置方法感兴趣,可以关注以下几点操作: 1. **安装**:在 Vue 项目中通过 npm 或 yarn 安装特定版本的 `canvas-editor`。 2. **导入并使用**:在 Vue 组件中导入编辑器组件,并通过 props 或数据绑定传递必要的设置参数。 3. **初始化配置**:根据需求调整编辑器的行为,如启用/禁用某些功能、设置默认样式等。 4. **事件处理**:监听编辑器的事件,以便在用户完成编辑后执行相应的业务逻辑。 相关问题: 1. 这个编辑器是否支持Markdown语法或者其他高级功能? 2. 如何在Vue组件生命周期钩子中初始化Canvas-Editor? 3. 是否有详细的文档或教程指导如何使用Canvas-Editor?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值