canvas实现动画效果(gif)对图片进行多重中心旋转动画的原理及实现 详解

tech2025-12-24  2

首先先看效果看是否是你想要效果

这里主要使用canvas 的drawImage方法

context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height); 参数描述img规定要使用的图像、画布或视频。sx可选。开始剪切的 x 坐标位置。sy可选。开始剪切的 y 坐标位置。swidth可选。被剪切图像的宽度。sheight可选。被剪切图像的高度。x在画布上放置图像的 x 坐标位置。y在画布上放置图像的 y 坐标位置。width可选。要使用的图像的宽度。(伸展或缩小图像)height可选。要使用的图像的高度。(伸展或缩小图像)

原理及用法(下方有完整代码组件)

1、首先 创建 画布 使用 id绑定

<canvas :id="`circle(1)${index}`"></canvas>

2、获取画布 获取绘图工具

const cvs = document.getElementById('circle(1)'+this.index); const ctx = cvs.getContext("2d");

3、获取需要绘制的图片 首先先创建图片,再使用canvas绘制出来 注意点:由于图片加载需要时间 是异步的,所以需要等图片加载完成创建dom后,canvas才能绘制出来

const img = document.createElement("img"); img.src = this.list.img; ctx .drawImage(img,0,0);

这里我给出参考图片

4、使用定时器setInterva进行刷新 产生帧动画 注意点:定时刷新时 如果canvas 没有设置宽度和高度会出现重影效果,因为重新设置宽度和高度相当于重绘了,关闭页面时记得清理定时器 问题效果

setInterval(() => { // 重绘 宽高 防止重影 cvs.width = radius * 2; cvs.height = radius * 2; // 或者使用 clearRect() 方法清空给定矩形内的指定像素 // ctx.clearRect(0, 0, radius * 2, radius * 2); },20)

5、旋转 及计算出旋转的角速度 使用ctx.rotate();进行旋转 注意点1:旋转时需要设置偏移,保证为中心旋转,先偏移再旋转后在纠正回来。 注意点2:旋转了多少 后面就要 反旋转回来 不然会导致旋转累加,实际效果和最终效果会产生有很大的区别 计算旋转角速度(1°/s):转换为rad/s及(1度/秒),定时器设置的刷新时间为fps(毫秒) ,1°=π/180,设置speed单位为(1°/s),最终可以算出 旋转度数= speed*fps/10000

ctx.translate(radius, radius); ctx.rotate(rotate); ctx.translate(-radius, -radius); ctx.drawImage(img,0,0); ctx.translate(radius, radius); ctx.rotate(-rotate); ctx.translate(-radius, -radius);

6、画布上居中 context.drawImage();方法有三种写法

//1 context.drawImage(img,sx,sy); //2 context.drawImage(img,sx,sy,swidth,sheight); //3 context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height);

图片在画布上居中需要使用第三种完整写法 计算居中

x = 画布宽度 /2 - 图片宽度 /2; y = 画布高度 /2 - 图片高度 /2;

vue组件完整代码(供参考缺失部分css样式)

<template> <div id="paralist" class="row"> <div class="list-left"> <canvas :id="`circle(1)${index}`"></canvas> <!-- <div> <span class="span-mini">{{this.list.parameter}}</span> </div> --> </div> <!-- <div class="list-right"> <div class="normal" v-if="this.list.status=='正常'"> <span class="span-mini">正常</span> </div> <div class="unusual" v-else-if="!(this.list.status=='正常')"> <span class="span-mini"> {{this.list.describe}} <br /> <span class="unusual-bottom span-mini" @click="this.popup">处理</span> </span> </div> </div> --> </div> </template> <script> export default { props: ["list", "index"], data() { return { // isShow:true, angleTimer: "", radius: 105 / 2, imgModule: [ { src: this.list.img, dom: "", // 储存img 生成的dom distance: 0, speed: 0, // 旋转速度 r/s speed*( Math.PI / 180)/(fps/1000) 单位 弧度/秒 (rad/s)。(1rad = 360°/(2π) ≈ 57°17'45″) angle: 0, // 旋转方向 1为顺时针, -1为逆时针 0为不旋转 rotate: 0, // 开始旋转角度 sx: 0, // 开始剪切位置 x sy: 0, // 开始剪切位置 y swidth: this.radius * 2, // 被剪切图像宽度 sheight: this.radius * 2, // 被剪切图像高度 x: "", // 画布在图像x 坐标位置 y: "", // 画布在图像y 坐标位置 width: this.radius * 2, // 要使用图像的宽度 height: this.radius * 2, // 要使用图像的高度 }, { src: require("@/assets/images2/circle1/资源 88.png"), dom: "", // 储存img 生成的dom distance: 0, speed: 0.5 * 360, // 旋转速度 r/s speed*( Math.PI / 180)/(fps/1000) 单位 弧度/秒 (rad/s)。(1rad = 360°/(2π) ≈ 57°17'45″) angle: 1, // 旋转方向 1为顺时针, -1为逆时针 0为不旋转 rotate: 0, // 开始旋转角度 sx: 0, // 开始剪切位置 x sy: 0, // 开始剪切位置 y swidth: this.radius * 2, // 被剪切图像宽度 sheight: this.radius * 2, // 被剪切图像高度 x: "", // 画布在图像x 坐标位置 y: "", // 画布在图像y 坐标位置 width: this.radius * 2, // 要使用图像的宽度 height: this.radius * 2, // 要使用图像的高度 }, { src: require("@/assets/images2/circle1/资源 87.png"), dom: "", // 储存img 生成的dom distance: 0, speed: 0, // 旋转速度 r/s speed*( Math.PI / 180)/(fps/1000) 单位 弧度/秒 (rad/s)。(1rad = 360°/(2π) ≈ 57°17'45″) angle: 0, // 旋转方向 1为顺时针, -1为逆时针 0为不旋转 rotate: 0, // 开始旋转角度 sx: 0, // 开始剪切位置 x sy: 0, // 开始剪切位置 y swidth: this.radius * 2, // 被剪切图像宽度 sheight: this.radius * 2, // 被剪切图像高度 x: "", // 画布在图像x 坐标位置 y: "", // 画布在图像y 坐标位置 width: this.radius * 2, // 要使用图像的宽度 height: this.radius * 2, // 要使用图像的高度 }, { src: require("@/assets/images2/circle1/资源 86.png"), dom: "", // 储存img 生成的dom distance: 0, speed: 0.2 * 360, // 旋转速度 r/s speed*( Math.PI / 180)/(fps/1000) 单位 弧度/秒 (rad/s)。(1rad = 360°/(2π) ≈ 57°17'45″) angle: -1, // 旋转方向 1为顺时针, -1为逆时针 0为不旋转 rotate: 0, // 开始旋转角度 sx: 0, // 开始剪切位置 x sy: 0, // 开始剪切位置 y swidth: this.radius * 2, // 被剪切图像宽度 sheight: this.radius * 2, // 被剪切图像高度 x: "", // 画布在图像x 坐标位置 y: "", // 画布在图像y 坐标位置 width: this.radius * 2, // 要使用图像的宽度 height: this.radius * 2, // 要使用图像的高度 }, ], }; }, mounted() { this.draw("circle(1)" + this.index); }, beforeDestroy() { if (this.angleTimer) { clearInterval(this.angleTimer); } }, methods: { // 点击进入窗口 routerTo() { this.$store.commit("setIsBack", true); this.$router.push({ path: "/dw/equipmentAlert", }); }, popup() { // console.log(this.list); alert("弹窗!"); }, /** * id 渲染canvas id * fps 定时刷新 时间 */ draw(id, fps = 20) { const radius = this.radius; const imgModule = this.imgModule; const img = document.createElement("img"); img.src = this.list.img; imgModule.forEach((item) => { item.dom = document.createElement("img"); item.dom.src = item.src; }); const cvs = document.getElementById(id); const ctx = cvs.getContext("2d"); if (this.angleTimer) { clearInterval(this.angleTimer); } this.angleTimer = setInterval(() => { // 重绘 宽高 防止重影 cvs.width = radius * 2; cvs.height = radius * 2; let rotateTotal = 0; // ctx.clearRect(0, 0, radius * 2, radius * 2); imgModule.forEach((item, index) => { item.distance += (item.speed * fps) / 1000; // 旋转总距离 if (item.distance % 360 == 0) { item.distance = 0; } let rotate = (-item.rotate * Math.PI) / 180 + (item.angle * item.distance * Math.PI) / 180; rotateTotal += rotate; let naturalWidth = item.dom.naturalWidth; //图片原始宽度 let naturalHeight = item.dom.naturalHeight; //图片原始高度 ctx.translate(radius, radius); ctx.rotate(rotate); ctx.translate(-radius, -radius); ctx.drawImage( item.dom, item.sx || 0, item.sy || 0, item.swidth || cvs.width, item.sheight || cvs.height, (item.x = (radius * 2) / 2 - naturalWidth / 2 || 0), //居中 (item.y = (radius * 2) / 2 - naturalHeight / 2 || 0), //居中 item.width || cvs.width, item.height || cvs.height ); // ctx.rotate(-rotate); // 注意! 旋转了多少 后面就要 反旋转回来 // 不然会导致旋转累加,实际效果和最终效果会产生有很大的区别 ctx.translate(radius, radius); ctx.rotate(-rotate); ctx.translate(-radius, -radius); }); }, fps); }, }, }; </script> <style lang="less" scoped> .row { background: transparent; margin-top: 0; } #paralist { width: 220px; min-width: 220px; height: 100%; display: flex; justify-content: center; align-items: center; .list-left { flex: 1; img { max-width: 105px; max-height: 113px; // background-color: red; } div { box-sizing: border-box; span { font-size: 15pt; color: #def5fe; } } } .list-right { flex: 1; height: 100%; position: relative; .normal { // line-height: 100%; //不知为何无法居中 height: 100%; position: relative; span { font-size: 14pt; position: absolute; left: 10%; top: 50%; transform: translateY(-100%); } } .unusual { position: absolute; top: 10%; text-align: left; span { display: inline-block; font-size: 8pt; color: red; } .unusual-bottom { color: hsl(190, 99%, 51%); border-bottom: 1px solid #06d6fe; cursor: pointer; } } } } </style>
最新回复(0)