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

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

在这里插入图片描述

这里主要使用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>

猜你喜欢

转载自blog.csdn.net/weixin_43245095/article/details/108403352