vue 使用canvas实现序列帧动画效果

vue yekong

数据可视化大屏 项目开发中,会添加动画来点缀页面,有些可以通过css来实现动画,有些图片序列帧动画,序列帧动画可以通过css+雪碧图的方式来实现,也可以通过js的方式来实现,今天是使用canvas+js来实现。

准备序列帧图片

我们首先需要准备序列帧图片,这里我们准备了75张序列帧图片,我们可以通过fileLength来告知组件帧动画图片的数量,使用IntervalTime来控制帧动画的执行间隔。

准备序列帧图片

组件使用

<template>
  <div class="pageTop wow fadeInDown">
    <sequence fileLength="74" IntervalTime="50"></sequence>
    <div class="left"></div>
    <div class="title">
      <span>{{ title }}</span>
    </div>
    <div class="right">
    </div>
  </div>
</template>

<script>
import sequence from "./sequence/index.vue";
export default {
  name: "pageTop",
  components: {sequence},
  data() {
    return {
    };
  },
  props: {
    title: {
      type: String,
      default() {
        return '';
      }
    }
  }
}
</script>

vue2不用import写法

图片要放在public/bg目录下

<template>
  <canvas ref="animationCanvas" class="animation_canvas" id="animation_canvas"></canvas>
</template>

<script>
export default {
  props: {
    // 文件数量
    fileLength: {
      type: Number,
      default: 74
    },
    // 动画间隔
    IntervalTime: {
      type: Number,
      default: 80
    }
  },
  data() {
    return {
      animationCanvas: null
    };
  },
  methods: {
    loadImages2(sources) {
      for (let i = 1; i <= this.fileLength; i++) {
        const image = `./bg/${i}.png`; // 假设图片存放在 public/bg 目录下
        sources.push(image);
      }
    },
    loadImages(sources, callback) {
      let loadedImages = 0;
      const numImages = sources.length;
      const images = [];

      for (let i = 0; i < numImages; i++) {
        images[i] = new Image();
        images[i].onload = () => {
          if (++loadedImages >= numImages) {
            callback(images);
          }
        };
        images[i].src = sources[i];
      }
    },
    playImages(images, ctx, width, height, intervalTime) {
      let imageNow = 0;
      setInterval(() => {
        ctx.clearRect(0, 0, width, height);
        ctx.drawImage(images[imageNow], 0, 0, width, height);
        imageNow = (imageNow + 1) % images.length;
      }, intervalTime);
    }
  },
  mounted() {
    this.$nextTick(() => {
      const canvas = this.$refs.animationCanvas;
      if (canvas) {
        const ctx = canvas.getContext('2d');
        if (!ctx) {
          console.error('Failed to get canvas context');
          return;
        }
        const sources = [];

        // 先加载第一张图片并展示
        const firstImage = `./bg/0.png`; // 假设图片存放在 public/bg 目录下
        sources.push(firstImage);

        const width = canvas.offsetWidth;
        const height = canvas.offsetHeight;
        canvas.width = width;
        canvas.height = height;

        const firstImgElement = new Image();
        firstImgElement.src = sources[0];
        firstImgElement.onload = () => {
          ctx.drawImage(firstImgElement, 0, 0, width, height);
        };

        // 加载剩余的图片
        this.loadImages2(sources);

        // 执行图片预加载,加载完成后执行动画
        this.loadImages(sources, (images) => {
          this.playImages(images, ctx, width, height, this.IntervalTime);
        });
      }
    });
  }
};
</script>

<style scoped>
.animation_canvas {
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
  z-index: -1;
}
</style>

vue2 import写法

<template>
  <canvas ref="animationCanvas" class="animation_canvas" id="animation_canvas"></canvas>
</template>

<script>
export default {
  props: {
    // 文件数量
    fileLength: {
      type: Number,
      default: 74
    },
    // 动画间隔
    IntervalTime: {
      type: Number,
      default: 80
    }
  },
  data() {
    return {
      animationCanvas: null
    };
  },
  methods: {
    async loadImages2(sources) {
      for (let i = 1; i <= this.fileLength; i++) {
        const image = await import(`./bg/${i}.png`);
        sources.push(image.default);
      }
    },
    loadImages(sources, callback) {
      let loadedImages = 0;
      const numImages = sources.length;
      const images = [];

      for (let i = 0; i < numImages; i++) {
        images[i] = new Image();
        images[i].onload = () => {
          if (++loadedImages >= numImages) {
            callback(images);
          }
        };
        images[i].src = sources[i];
      }
    },
    playImages(images, ctx, width, height, intervalTime) {
      let imageNow = 0;
      setInterval(() => {
        ctx.clearRect(0, 0, width, height);
        ctx.drawImage(images[imageNow], 0, 0, width, height);
        imageNow = (imageNow + 1) % images.length;
      }, intervalTime);
    }
  },
  async mounted() {
    this.$nextTick(async () => {
      const canvas = this.$refs.animationCanvas;
      if (canvas) {
        const ctx = canvas.getContext('2d');
        if (!ctx) {
          console.error('Failed to get canvas context');
          return;
        }
        const sources = [];

        // 先加载第一张图片并展示
        const firstImage = await import(`./bg/0.png`);
        sources.push(firstImage.default);

        const width = canvas.offsetWidth;
        const height = canvas.offsetHeight;
        canvas.width = width;
        canvas.height = height;

        const firstImgElement = new Image();
        firstImgElement.src = sources[0];
        firstImgElement.onload = () => {
          ctx.drawImage(firstImgElement, 0, 0, width, height);
        };

        // 加载剩余的图片
        await this.loadImages2(sources);

        // 执行图片预加载,加载完成后执行动画
        this.loadImages(sources, (images) => {
          this.playImages(images, ctx, width, height, this.IntervalTime);
        });
      }
    });
  }
};
</script>

<style scoped>
.animation_canvas {
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
  z-index: -1;
}
</style>


vue3写法

<template>
  <canvas ref="animationCanvas" class="animation_canvas" id="animation_canvas"></canvas>
</template>

<script setup>
import { ref, onMounted, nextTick } from 'vue';

// Props
const props = defineProps({
  // 文件数量
  fileLength: {
    type: Number,
    default: 74
  },
  // 动画间隔
  IntervalTime: {
    type: Number,
    default: 80
  }
});

// Refs
const animationCanvas = ref(null);

// Methods
const loadImages2 = async (sources) => {
  for (let i = 1; i <= props.fileLength; i++) {
    const image = await import(`./bg/${i}.png`);
    sources.push(image.default);
  }
};

const loadImages = (sources, callback) => {
  let loadedImages = 0;
  const numImages = sources.length;
  const images = [];

  for (let i = 0; i < numImages; i++) {
    images[i] = new Image();
    images[i].onload = () => {
      if (++loadedImages >= numImages) {
        callback(images);
      }
    };
    images[i].src = sources[i];
  }
};

const playImages = (images, ctx, width, height, intervalTime) => {
  let imageNow = 0;
  setInterval(() => {
    ctx.clearRect(0, 0, width, height);
    ctx.drawImage(images[imageNow], 0, 0, width, height);
    imageNow = (imageNow + 1) % images.length;
  }, intervalTime);
};

// Lifecycle hook
onMounted(async () => {
  await nextTick();
  const canvas = animationCanvas.value;
  if (canvas) {
    const ctx = canvas.getContext('2d');
    if (!ctx) {
      console.error('Failed to get canvas context');
      return;
    }
    const sources = [];

    // 先加载第一张图片并展示
    const firstImage = await import(`./bg/0.png`);
    sources.push(firstImage.default);

    const width = canvas.offsetWidth;
    const height = canvas.offsetHeight;
    canvas.width = width;
    canvas.height = height;

    const firstImgElement = new Image();
    firstImgElement.src = sources[0];
    firstImgElement.onload = () => {
      ctx.drawImage(firstImgElement, 0, 0, width, height);
    };

    // 加载剩余的图片
    await loadImages2(sources);

    // 执行图片预加载,加载完成后执行动画
    loadImages(sources, (images) => {
      playImages(images, ctx, width, height, props.IntervalTime);
    });
  }
});
</script>

<style scoped>
.animation_canvas {
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
  z-index: -1;
}
</style>


项目使用实例

光伏智能管理数据可视化

喜欢