新年愿望签gif制作

353 阅读3分钟

我正在参加「兔了个兔」创意投稿大赛,详情请看:「兔了个兔」创意投稿大赛

2022年过去了,迎来了2023兔年,逝去的青春不会重来,但在新的一年我们可以许下新的愿望,你最希望实现的梦想是什么呢?是升职加薪、家庭幸福、暴富亦或是脱单呢?许下愿望很简单,然而去实现它并不是那么容易,希望每一个怀揣着愿望的人们能够带着梦想,去努力去奋斗!

本篇用canvas制作新年愿望签,希望大家都能够带着梦想前进。

关键思路讲解

几个关键动画要点:

  1. 奔跑的人物
  2. 飞舞的蝴蝶及蝴蝶周围的光罩
  3. 移动的背景图片
  4. 向上漂浮的星星点点

分析

1、其中人物奔跑和舞蝶扑腾的动画都是通过drawImage图片裁剪来实现的,将一张多帧的精灵图分时间段展示,连续播放就形成连续的动图。

drawGif(gifId, next) {
  const point = this[gifId + "_getPoint"](next);
  if (!this[gifId + "_img"]) {
    const img = document.getElementById(gifId);
    img.onload = () => {
      this[gifId + "_img"] = img;
      this.ctx.drawImage(img, ...point);
      this.checkAllImgLoaded();
    };
  } else {
    this.ctx.drawImage(this[gifId + "_img"], ...point);
  }
  this.ctx.restore();
}

_getPoint函数定义,下一帧或者重复帧的图片点位,当next为true时,表示动画进行到下一帧,否则重复当前帧。 point对应drawImage函数中的后8位参数。

this[gifId + "_getPoint"] = (next) => {
    const { cur_fly_frame } = this;
    this.ctx.save();
    this.ctx.translate(x, y);
    this.ctx.rotate((Math.PI / 180) * 15);
    this.ctx.translate(-x, -y);
    const point = [
      butterFlyFrameWidth * (cur_fly_frame - 1),
      0,
      butterFlyFrameWidth,
      butterFlyFrameHeight,
      x,
      this[gifId + "_y"],
      imgWidth,
      imgHeight
    ];

    if (next) {
      this.cur_fly_frame =
        cur_fly_frame >= fly_frame ? 1 : cur_fly_frame + 1;
    }
    return point;
};

2、背景图片的移动通过更改图片的x坐标来实现,每一帧都移动-1个像素点,当图片移动到剩下一个屏幕可见的宽度时,再回到坐标0重新开始移动,要注意的是我粘贴了开头一部分的像素到背景图片尾部,这样从x=0重新移动的时候画面不会太突兀。

image.png

3、蝴蝶光罩使用canvas的createRadialGradient圆形渐变函数,将圆心和蝴蝶的中心点对齐。

drawLight() {
  const drawId = "drawLight";
  const gifId = "butterfly";
  const x = this[gifId + "_x"];
  const y = this[gifId + "_y"];
  const imgWidth = this[gifId + "_imgWidth"];
  const imgHeight = this[gifId + "_imgHeight"];
  this.ctx.beginPath();
  const centerX = x + imgWidth / 2 - 8;
  const centerY = y + imgHeight / 2;
  const r = 40;
  const color = this.ctx.createRadialGradient(
    centerX,
    centerY,
    0,
    centerX,
    centerY,
    r
  );
  color.addColorStop(0, "#fff");
  color.addColorStop(0.2, "rgba(251,249,234,1)");
  color.addColorStop(1, "rgba(251,249,234,0)");
  this.ctx.fillStyle = color;
  this.ctx.arc(centerX, centerY, r, 0, Math.PI * 4);
  this.ctx.closePath();
  this.ctx.fill();
}

4、向上漂浮的星星通过位置随机生成到画布上,定时从底部创建新的星星。因为星星的移动和星星的创建所需要的时间不同,所以用drawStar、drawStarMove两个动画来实现,drawStar创建和消除离开屏幕的星星,drawStarMove移动星星。

drawId表示动画名称,drawId + "_interval"用来设定动画的间隔时间。

this[drawId + "_interval"] = 100;

文字排版

在文字上面加一个胡萝卜和一只小兔子,这个还是drawImage来绘制的,注意的是要计算好文字的大小和位置。胡萝卜放在第一个文字上,兔子在最后一个文字上,最少两个字,最多允许4个字,再多就不好看了。

initText() {
  const { ctx, text, main, canvas } = this;
  if (!text) return;
  const len = text.length;
  const fontSize = (main.clientWidth - 60 - 15 * (len - 1)) / len;
  canvas.height = fontSize + 80;
  canvas.style.letterSpacing = "7.5px";
  canvas.style.marginTop =
    (main.clientHeight / 2 - canvas.height) / 2 + "px";
  const startX = 30;
  const startY = 40;
  this.fontSize = fontSize;
  this.startX = startX;
  this.startY = startY;
}

如何计算和摆放胡萝卜和兔子我就不详细说明了,大家自己安排哈。

生成gif

重点来了,如何将canvas图片转为gif动图呢?我采用的是gifjs,通过添加图片帧的方式生成gif图片。

用html2canvas将#main下的元素转为img图片:

html2canvas(document.querySelector("#main")).then((canvas) => {
  const url = canvas.toDataURL("image/png");
  const img = document.createElement("img");
  img.className = "gif_img download_img";
  img.src = url;
  img.onload = () => {
    // 可自定义帧数
    if (imgFrams.length < 8) {
      imgFrams.push(img);
      setTimeoutFunc(gif);
    } else {
    // 将帧重复5次
      for (let i = 0; i < imgFrams.length * 5; i++) {
        gif.addFrame(imgFrams[i % 8], { copy: true });
      }
      downloadController.show();
      luckyFactory.restartRunning();
      gif.render();
      maskController.changeText("下载中...");
    }
  };
  document.body.appendChild(img);
});

stopRunning函数先使动画停止在某一帧处,然后用html2canvas转图片:

stopRunning(step_num) {
  cancelAnimationFrame(this.animationKey);
  this.ctx.clearRect(
    0,
    0,
    this.main.clientWidth,
    this.main.clientHeight
  );
  this.cur_fly_frame = (step_num % this.fly_frame) + 1;
  this.cur_run_frame = (step_num % this.run_frame) + 1;
  this["xkbg_x"] = this["xkbg_x"] - 5;// 背景图片移动加大一点,使每一帧移动明显一点
  this.drawAnimate(true);
}

gif.addFrame将图片帧记录下来,GIF监听gif.render函数执行之后,通过FileReader下载gif文件:

let gif = new GIF({
  // 创建gif对象
  repeat: 0,
  workers: 2,
  quality: 10,
  workerScript: "./js/gif/gif.worker.js"
});
gif.on("finished", function (blob) {
  // 渲染完毕
  maskController.hide();
  var file = new FileReader();
  file.readAsDataURL(blob);
  file.onload = function () {
    const a = document.createElement("a");
    a.download = "新年愿望签.gif";
    a.href = file.result;
    document.body.appendChild(a);
    a.click();
  };
});

最终得到的gif文件:

因为图片有点多,不好放到码上掘金里面,掘金上传图片都有水印,所以我上传到gitee上了。

Ending~源代码戳这里👇