论如何将dom动画转为canvas动画

357 阅读1分钟

平时当我们遇到要将dom元素转canvas进行截图时,大家应该都知道有个html2canvas的库可以很好的解决这个问题。那么如果我们想要将dom元素转canvas时,将dom的动画效果也能一块转过去,该怎么做呢?本文给大家提供一个参考方案。

效果演示

001.gif

方案概述

  • @tweenjs/tween.js: 这是一个js缓动动画库,通过js实现动画可以兼容dom动画效果和canvas中元素动画效果。
  • pixijs: 2D绘图引擎,用来

关键代码讲解

<template>
  <div class="box">
    <a-space direction="vertical">
      <a-row>
        <a-space>
          <a-button @click="() => transformToCanvas()">转canvas</a-button>
          <a-select
            ref="select"
            v-model:value="animationValue"
            style="width: 120px"
            @change="handleChange"
          >
            <a-select-option
              v-for="(i, index) in animateFuncsList"
              :value="index"
              >{{ i.name }}</a-select-option
            >
          </a-select>
        </a-space>
      </a-row>
      <a-row>
        <div class="wrap">
          <img class="animate-img" src="@/assets/imgs/duck.jpeg" alt="" />
        </div>
      </a-row>
      <a-row>
        <div class="canvas-wrap"></div>
      </a-row>
    </a-space>
  </div>
</template>

<script lang="ts" setup>
import TWEEN from "@tweenjs/tween.js";
import * as PIXI from "pixi.js";
import { onMounted, ref } from "vue";
import { animateFuncsList } from "./anmiteList";

const animationValue = ref(0); // 动画方式
let app: PIXI.Application;
let imgDom: HTMLElement;
let sprite: PIXI.Sprite;

onMounted(() => {
  let wrapDom = document.querySelector(".wrap");
  imgDom = document.querySelector(".animate-img") as HTMLElement;

  // 初始化画布
  app = new PIXI.Application({
    width: wrapDom?.clientWidth,
    height: wrapDom?.clientHeight,
    backgroundAlpha: 0,
  });
  document.querySelector(".canvas-wrap")?.appendChild(app.view);

  // Setup the animation loop.
  function animate(time: number | undefined) {
    requestAnimationFrame(animate);
    TWEEN.update(time);
  }
  requestAnimationFrame(animate);

  showAnimation(imgDom, animationValue.value);
});

function transformToCanvas(onComplete?: Function) {
  app.stage.removeChildren();

  // create a new Sprite from an image path
  sprite = PIXI.Sprite.from("/assets/imgs/duck.jpeg");

  sprite.width = imgDom.clientWidth;
  sprite.height = imgDom.clientHeight;
  sprite.alpha = 0;

  app.stage.addChild(sprite);

  showAnimation(sprite, animationValue.value, onComplete);
}

function handleChange(value: number) {
  showAnimation(imgDom, value);
}

function showAnimation(
  target: HTMLElement | PIXI.Sprite,
  index: number,
  onComplete?: Function
) {
  animateFuncsList[index].call(target, onComplete);
}
</script>

<style scoped lang="scss">
.box {
  padding-top: 60px;
  .wrap {
    width: 300px;
    height: 300px;
    background-color: black;
  }
  &-canvas {
    width: 300px;
    height: 300px;
  }
}
</style>

anmiteList.ts: 中封装动画函数

import * as PIXI from "pixi.js";
import TWEEN from "@tweenjs/tween.js";

function fadeInRight(target: HTMLElement | PIXI.Sprite, onComplete?: Function) {
  const style = { x: 300, opacity: 0 };
  const tween = new TWEEN.Tween(style) // Create a new tween that modifies 'coords'.
    .to({ x: 0, opacity: 1 }, 1000)
    .easing(TWEEN.Easing.Quadratic.Out) // Use an easing function to make the animation smooth.
    .onUpdate(() => {
      if (target instanceof Element) {
        target.style.setProperty("transform", `translate(${style.x}px)`);
        target.style.setProperty("opacity", `${style.opacity}`);
      } else if (target instanceof PIXI.Sprite) {
        target.x = style.x;
        target.alpha = style.opacity;
      }
    })
    .onComplete(() => {
      onComplete && onComplete();
    })
    .start(); // Start the tween immediately.
}

function bounceIn(target: HTMLElement | PIXI.Sprite, onComplete?: Function) {
  const style = { scale: 0, opacity: 0 };
  let scale = 1;
  if (target instanceof PIXI.Sprite) {
    target.anchor.set(0.5);
    target.x = target.x + target.width / 2;
    target.y = target.y + target.height / 2;
    // scale = target.scale.x;
    // console.log(111, scale)
  }
  const tween = new TWEEN.Tween(style) // Create a new tween that modifies 'coords'.
    .to({ scale, opacity: 1 }, 1000)
    .easing(TWEEN.Easing.Bounce.Out) // Use an easing function to make the animation smooth.
    .onUpdate(() => {
      if (target instanceof Element) {
        target.style.setProperty("transform", `scale(${style.scale})`);
        target.style.setProperty("opacity", `${style.opacity}`);
      } else if (target instanceof PIXI.Sprite) {
        target.scale.set(style.scale);
        target.alpha = style.opacity;
      }
    })
    .onComplete(() => {
      onComplete && onComplete();
    })
    .start(); // Start the tween immediately.
}

function fadeInLeft(target: HTMLElement | PIXI.Sprite, onComplete?: Function) {
  const style = { x: -300, opacity: 0 };
  const tween = new TWEEN.Tween(style) // Create a new tween that modifies 'coords'.
    .to({ x: 0, opacity: 1 }, 1000)
    .easing(TWEEN.Easing.Quadratic.Out) // Use an easing function to make the animation smooth.
    .onUpdate(() => {
      if (target instanceof Element) {
        target.style.setProperty("transform", `translate(${style.x}px)`);
        target.style.setProperty("opacity", `${style.opacity}`);
      } else if (target instanceof PIXI.Sprite) {
        target.x = style.x;
        target.alpha = style.opacity;
      }
    })
    .onComplete(() => {
      onComplete && onComplete();
    })
    .start(); // Start the tween immediately.
}

export let animateFuncsList = [
  { name: "右滑入场", call: fadeInRight },
  { name: "左滑入场", call: fadeInLeft },
  {
    name: "弹入",
    call: bounceIn,
  },
];