一个不能吃的中秋月饼

海上生明月,天涯共此时.
----《望月怀远》(唐·张九龄)
不觉间又到了中秋时节,相信广大朋友们都收到了公司或其他朋友送来的月饼,我相信没有人不期待中秋(假期)的来临。
“小饼如嚼月,中有酥与饴”说的就是美味的月饼,既然月饼如此深得大家的喜爱,今天咱们就用canvas画一个月饼,送个显示器前的你。

这次使用的技术栈是vue + vite 进行开发编辑工作的,(就是简单的html+css)这么简单的功能当然应该使用静态html的啦,请各 位朋友怎么方便怎么来吧~

目录结构
一个不能吃的中秋月饼
文章图片

开始绘制
(1)第一步绘制一个富有中国元素的背景(有点密集恐惧症)
一个不能吃的中秋月饼
文章图片

canvas { width: 100%;
height: 100%;
background-image: repeating-radial-gradient(circle at center center,
transparent 0px,
transparent 8px,
rgba(255, 255, 255, 0.05) 8px,
rgba(255, 255, 255, 0.05) 11px,
transparent 11px,
transparent 17px,
rgba(255, 255, 255, 0.05) 17px,
rgba(255, 255, 255, 0.05) 25px,
transparent 25px,
transparent 38px,
rgba(255, 255, 255, 0.05) 38px,
rgba(255, 255, 255, 0.05) 42px),
repeating-radial-gradient(circle at center center,
rgb(230, 0, 0) 0px,
rgb(230, 0, 0) 11px,
rgb(230, 0, 0) 11px,
rgb(230, 0, 0) 19px,
rgb(230, 0, 0) 19px,
rgb(230, 0, 0) 24px,
rgb(230, 0, 0) 24px,
rgb(230, 0, 0) 33px,
rgb(230, 0, 0) 33px,
rgb(230, 0, 0) 44px,
rgb(230, 0, 0) 44px,
rgb(230, 0, 0) 46px);
background-size: 30px 30px;
cursor: pointer;
}
绘制月饼线条
接下来进入到绘制月饼的正式环节,首先我们要获取到画布,然后再绘制线条,月饼大家都知道,大部分的花纹是轴对称的展示,所以我们将其抽出,分为单独的一个个类去进行实现,最后再在底部添加我们的中秋祝福或诗句。
一个不能吃的中秋月饼
文章图片

constructor(options) {
this.x = 0; // x轴坐标
this.y = 0; // y轴坐标
this.name = "五仁" // 馅名
this.strokeStyle = "rgb(180,110,48)"; // 线条色
this.fillStyle = "rgb(251,216,96)"; // 填充色
this.fontSize = 36; // 字体大小
this.scale = 1; // 缩放大小
Object.assign(this, options)
this.ctx = null;
this.progress = 0; // 绘制进度
this.stepFn = [] // 绘制步骤
this.isComplete = false; // 是否绘制结束
this.nowDate = new Date(); // 当前时间
this.lastDate = new Date(); // 结束时间
return this;
}
render(ctx) {
// 渲染
if (!ctx)
throw new Error("context is undefined.");
this.ctx = ctx;
this.stepFn.length = 0;
this.stepFn.push(() => this.drawEdge(180, 20))
this.stepFn.push(() => this.drawEdge(140, 12))
this.stepFn.push(() => this.drawRoundRectPath(140, 220, 40))
this.stepFn.push(() => this.drawRoundRectPath(220, 140, 40))
this.stepFn.push(() => this.drawLine(30, -110, 30, 110))
this.stepFn.push(() => this.drawLine(0, -110, 0, 110))
this.stepFn.push(() => this.drawLine(-30, -110, -30, 110))
this.stepFn.push(() => this.drawLine(-110, -30, 110, -30))
this.stepFn.push(() => this.drawLine(-110, 0, 110, 0))
this.stepFn.push(() => this.drawLine(-110, 30, 110, 30))
this.stepFn.push(() => this.drawRect(140, 140))
this.stepFn.push(() => this.drawBox(140))
this.stepFn.push(() => this.drawText())
return this;
}
draw() {
// 绘制
for (let i = 0; i < this.progress; i++) {
this.stepFn[i] && this.stepFn[i]()
}
if (this.progress > this.stepFn.length) return this.isComplete = true;
this.nowDate = new Date();
if(this.nowDate-this.lastDate>200){
this.progress++;
this.lastDate = this.nowDate;
}
}
drawBox(size) {
// 绘制折线盒子
const {ctx, x, y, strokeStyle, scale} = this;
let v = 17,
n = -size / 2;
ctx.save()
ctx.translate(x, y);
ctx.scale(scale, scale)
ctx.beginPath();
ctx.lineCap = "round";
ctx.lineWidth = 4;
ctx.strokeStyle = strokeStyle;
ctx.moveTo(v + n, n)
ctx.lineTo(v + n, size - v + n)
ctx.lineTo(size - v + n, size - v + n)
ctx.lineTo(size - v + n, v + n)
ctx.lineTo(v * 2 + n, v + n)
ctx.lineTo(v 2 + n, size - v 2 + n)
ctx.lineTo(size - v 2 + n, size - v 2 + n)
ctx.lineTo(size - v * 2 + n, 45 + n)
ctx.stroke()
ctx.restore();
}
drawLine(x1, y1, x2, y2) {
// 绘制线条
const {ctx, x, y, strokeStyle, scale} = this;
ctx.save()
ctx.translate(x, y);
ctx.scale(scale, scale)
ctx.beginPath();
ctx.lineCap = "round";
ctx.lineWidth = 4;
ctx.strokeStyle = strokeStyle;
ctx.moveTo(x1, y1)
ctx.lineTo(x2, y2)
ctx.stroke()
ctx.restore();
}
drawRect(width, height) {
// 绘制矩形
const {ctx, x, y, strokeStyle, fillStyle, scale} = this;
ctx.save()
ctx.translate(x, y);
ctx.scale(scale, scale)
ctx.beginPath();
ctx.lineCap = "round";
ctx.lineWidth = 4;
ctx.strokeStyle = strokeStyle;
ctx.fillStyle = fillStyle;
ctx.rect(-width / 2, -height / 2, width, width)
ctx.fill();
ctx.stroke()
ctx.restore();
}
drawRoundRectPath(width, height, radius) {
// 绘制圆角矩形
const {ctx, x, y, strokeStyle, fillStyle, scale} = this;
let w = -width / 2,
h = -height / 2
ctx.save()
ctx.translate(x, y);
ctx.scale(scale, scale)
ctx.lineCap = "round";
ctx.strokeStyle = strokeStyle;
ctx.fillStyle = fillStyle;
ctx.lineWidth = 5;
ctx.beginPath();
ctx.arc(width - radius + w, height - radius + h, radius, 0, Math.PI / 2);
ctx.lineTo(radius + w, height + h);
ctx.arc(radius + w, height - radius + h, radius, Math.PI / 2, Math.PI);
ctx.lineTo(w, radius + h);
ctx.arc(radius + w, radius + h, radius, Math.PI, Math.PI * 3 / 2);
ctx.lineTo(width - radius + w, h);
ctx.arc(width - radius + w, radius + h, radius, Math.PI 3 / 2, Math.PI 2);
ctx.lineTo(width + w, height - radius + h);
ctx.closePath();
ctx.stroke()
ctx.restore();
}
drawEdge(radius, lineWidth) {
// 绘制花边
const {ctx, x, y, strokeStyle, fillStyle, scale} = this;
let n = 12,
v = 360 / n,
m = 30;
ctx.save()
ctx.translate(x, y);
ctx.scale(scale, scale)
ctx.beginPath();
ctx.lineCap = "round";
for (let i = 0; i < n; i++) {
let angle1 = i v Math.PI / 180;
let angle2 = (i + 1) v Math.PI / 180;
let angle3 = (i + 0.5) v Math.PI / 180;
ctx.lineWidth = lineWidth;
ctx.strokeStyle = strokeStyle;
ctx.fillStyle = fillStyle;
let _sx = radius * Math.cos(angle1),
_sy = radius * Math.sin(angle1);
ctx.lineTo(_sx, _sy);
let _ex = radius * Math.cos(angle2),
_ey = radius * Math.sin(angle2);
let _mx = (radius + m) * Math.cos(angle3),
_my = (radius + m) * Math.sin(angle3);
ctx.bezierCurveTo(_mx, _my, _ex, _ey, _ex, _ey)
}
ctx.closePath();
ctx.stroke()
ctx.fill();
ctx.restore();
}
填充文字
drawText(n) {
// 绘制文字
const {ctx, x, y, name, fontSize, strokeStyle, scale} = this;
let size = fontSize;
ctx.save()
ctx.translate(x, y);
ctx.scale(scale, scale)
ctx.fillStyle = strokeStyle;
ctx.textAlign = "center";
ctx.font = bolder ${size}px fangsong,self
ctx.shadowColor = strokeStyle;
ctx.shadowBlur = 1;
if (name.length == 2) {
ctx.fillText(name.charAt(0), 0, -size * 0.5 + 5);
ctx.fillText(name.charAt(1), 0, size * 0.5 + 5);
}
if (name.length >= 3) {
size *= 0.7;
ctx.font = bolder ${size}px fangsong,self
ctx.fillText(name.charAt(0), 0, -size * 1 + 2);
ctx.fillText(name.charAt(1), 0, size * 0 + 2);
ctx.fillText(name.charAt(2), 0, size * 1 + 2);
}
ctx.restore();
}
一个不能吃的中秋月饼
文章图片

添加诗句
使用canvas的filltext进行对应诗句的填充
drawText() {
const { ctx, w, h, text } = this;
ctx.save();
【一个不能吃的中秋月饼】ctx.fillStyle = "rgb(253,190,0)";
ctx.textAlign = "center";
ctx.font = bolder 32px fangsong,self;
ctx.shadowColor = "rgb(253,190,0)";
ctx.shadowBlur = 10;
ctx.fillText(text[0].substr(0, this.textIndex), w / 2, h * 0.36 + 240);
if (text[0].length < this.textIndex) {
ctx.fillText(
text[1].substr(0, this.textIndex - text[0].length),
w / 2,
h * 0.36 + 240 + 52
);
}
ctx.restore();
},
一个不能吃的中秋月饼
文章图片

切换不同口味
一个不能吃的中秋月饼
文章图片


v-for="(item, index) in types"
:key="index"
class="wur"
@click="constructor(item)"
{{ item }}
data中定义口味
types: ["黑芝麻", "五仁", "蛋黄", "莲蓉", "豆沙"],
结束
这次运用到的一些图形绘制希望各位小伙伴能够灵活运用,举一反三,在中秋即将到来之际,送给朋友、同事一个不一样的中秋月饼吧~

点赞支持、手留余香、与有荣焉,动动你发财的小手哟,感谢各位大佬能留下您的足迹。
如果你觉得这篇文章对你有点用的话,麻烦请给我们的开源项目点点star: http://github.crmeb.net/u/defu不胜感激 !

    推荐阅读