我正在参加「兔了个兔」创意投稿大赛,详情请看:「兔了个兔」创意投稿大赛
2022年过去了,迎来了2023兔年,逝去的青春不会重来,但在新的一年我们可以许下新的愿望,你最希望实现的梦想是什么呢?是升职加薪、家庭幸福、暴富亦或是脱单呢?许下愿望很简单,然而去实现它并不是那么容易,希望每一个怀揣着愿望的人们能够带着梦想,去努力去奋斗!
本篇用canvas制作新年愿望签,希望大家都能够带着梦想前进。
关键思路讲解
几个关键动画要点:
- 奔跑的人物
- 飞舞的蝴蝶及蝴蝶周围的光罩
- 移动的背景图片
- 向上漂浮的星星点点
分析
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重新移动的时候画面不会太突兀。
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~源代码戳这里👇