Canvas如何做个新春祝愿背景墙

1,285 阅读5分钟

PK创意闹新春,我正在参加「春节创意投稿大赛」,详情请看:春节创意投稿大赛

前言

爆竹声中一岁除,春风送暖入屠苏;

千门万户瞳瞳日,总把新桃换旧符。

——王安石《元日》

介绍

春节近在眼前,本期将用canvas制作一个新春满是祝愿语的背景墙,为大家献上最真挚的祝福,愿大家虎年大吉,财源滚滚。先来康康效果怎样吧:

VID_20220129_114332.gif

还是蛮简单的吧,本身也没有引用第三方库,红色背景是由css画出来的,其他都是主要纯js来完成,里面你可以学习或者发现一些关于js动画,屏幕适配等技巧,还等什么我们马上要出发啦~

正文

1.基础结构

我们本次案例是用vite构建,所以在script用module模式。

<canvas id="canvas"></canvas>
<script type="module" src="./app.js"></script>
body {
    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(170, 0, 0) 0px, rgb(170, 0, 0) 11px, rgb(170, 0, 0) 11px, rgb(170, 0, 0) 19px, rgb(170, 0, 0) 19px, rgb(170, 0, 0) 24px, rgb(170, 0, 0) 24px, rgb(170, 0, 0) 33px, rgb(170, 0, 0) 33px, rgb(170, 0, 0) 44px, rgb(170, 0, 0) 44px, rgb(170, 0, 0) 46px);
    background-size: 60px 60px;
}

这里我们用css的repeating-radial-gradient去绘制一幅喜庆点的背景。

微信截图_20220129133058.png

2.准备祝福语

// app.js
const txt = `
申猴子鼠亥豬吉星高照恭喜
卯兔祝願你未幸福美滿廣進
辰龍醜牛福羊在寅虎年工戌
巳蛇吉祥壽萬事如意寅作狗
午馬身安快樂和睦酉為順雞
大婆體康心想事成溫馨利和
吉平健闔家健全年年有余諧
大富康團圓成財源滾滾余歡
利歡樂和紅火開心好運連連
`

我们要写一些字符串,把一些祝福语塞到里面,再混入一些无关的词,让用户乍一眼看不出来端倪。

3.逻辑结构

// app.js
class Application {
    constructor() {
        this.canvas = null;
        this.ctx = null;
        this.w = 1600;
        this.h = 1200;
        this.color = "rgba(33,33,33,.3)"
        this.activeColor = "rgb(253,190,0)"
        this.activeIndex = -1;
        this.charNum = 12;
        this.fontSize = 12;
        this.charList = Array.from(txt.trim()).filter(char => char.trim() != "");
        this.word = ["祝願你", "在寅虎年", "工作順利", "心想事成", "財源滾滾", "大吉大利", "吉星高照", "闔家團圓", "幸福美滿", "好運連連", "身體健康", "萬事如意"];
        this.duration = 800;
        this.nTime = 0;
        this.pTime = 0;
        this.fontScale = 1;
        this.init();
    }
    init() {
        this.canvas = document.getElementById("canvas");
        this.ctx = this.canvas.getContext("2d");
        window.addEventListener("resize", this.reset.bind(this));
        this.reset();
        this.step();
    }
    reset() {
        this.canvas.width = this.ctx.width = this.w;
        this.canvas.height = this.ctx.height = this.h;
        this.fontSize = this.w / this.charNum;
        let W = window.innerWidth;
        let H = window.innerHeight;
        let v = this.w / this.h;
        if (W > H) W = H * v;
        else H = W / v;
        this.canvas.style.width = `${W}px`
        this.canvas.style.height = `${H}px`
        this.render();
    }
    render(delta = 0) {
        this.clear();
        const { charList} = this;
        let activeIndexs  = [];
        charList.forEach((char, index) => {
            this.drawTxt(char, index, activeIndexs);
        })
    }
    drawTxt(char, index, activeIndexs) {
        const { ctx, fontSize, color, charNum, activeColor, fontScale } = this;
        let active = [].includes.call(activeIndexs, index)
        let _fontScale = 0.65;
        if (active) {
            _fontScale = 0.65 * fontScale;
        }
        ctx.save();
        ctx.font = `bold ${_fontScale * fontSize}px fangsong`;
        ctx.fillStyle = ctx.shadowColor = active ? activeColor : color;
        ctx.shadowBlur = active ? 15 : 3;
        ctx.textAlign = "center"
        ctx.textBaseline = "middle"
        ctx.translate(~~(index % charNum) * fontSize * 1, ~~(index / charNum) * fontSize * .975);
        ctx.fillText(char, fontSize / 2, fontSize * .6);
        ctx.restore();
    }
    clear() {
        const { w, h, ctx } = this;
        ctx.clearRect(0, 0, w, h);
    }
    step(delta) {
        requestAnimationFrame(this.step.bind(this));
        this.render(delta);
        if (this.nTime - this.pTime > this.duration) {
            this.activeIndex += 1;
            this.activeIndex %= this.word.length;
            this.pTime = delta;
            this.fontScale = 1;
        }
        this.nTime = delta;
    }
}

window.onload = new Application();

这里我们做了这么几件事:

  • 定义charList就是把刚才的祝福字符串转成数组,后面会方便查找,还有要定义激活文字和未激活文字的颜色,然后写一个数组word,里面就是要逐个显示出的祝福语,duration定义了每隔多少秒出现一个。
  • reset方法里来做屏幕的适配,始终让canvas在屏幕中显示的比较完全。
  • render和drawTxt方法,就是渲染和绘制文字,目前就是简单绘制,activeIndexs为空数组就是还没找到匹配的激活祝福语,后面我们将会来做如何找出这些祝福语的位置。
  • 当然drawTxt方法里面要先写上如果激活后的样式,比如填充颜色和大小的改变。
  • step方法就是让这套逻辑不停地执行,每隔duration一个周期再让其activeIndex改变来激活另一组祝福语。

微信截图_20220129142249.png

4.搜索祝福语

class Application {
    // ...
    render(delta = 0) {
        this.clear();
        const { activeIndex, charList, word } = this;
        let activeIndexs = activeIndex >= 0 ? this.getTxtIndexs(word[activeIndex]) : [];
        this.fontScale += (delta - this.nTime) * .001;
        if (this.fontScale) {
            this.fontScale = Math.max(1, Math.min(1.5, this.fontScale))
        }
        else {
            this.fontScale = 1;
        }

        charList.forEach((char, index) => {
            this.drawTxt(char, index, activeIndexs);
        })
    }
    findIndex(str, list, arr, tagIndex = 0, index = 0) {
        if (index >= list.length) return arr;
        if (str[tagIndex] != list[index].value) {
            return this.findIndex(str, list, arr, tagIndex, index + 1)
        }
        arr.push(list[index])
        return this.findIndex(str, list, arr, tagIndex + 1, index + 1)
    }
    getTxtIndexs(str) {
        const { charList } = this;
        let _list = charList.map((char, index) => {
            if (str.indexOf(char) != -1) return {
                value: charList[index],
                index
            }
        }).filter(value => value);
        _list = this.findIndex(str, _list, [])
        return _list.map(item => item.index);
    }
    // ...
}

window.onload = new Application();

这里要做的是每天更换activeIndexs都要从里面找祝福语,找到了祝福语,就要知道这些祝福语在里面的位置,又因为很多祝福语都具有相同的文字,我们不能一下把激活的文字全给找出来,这里就在findIndex递归去查询具有连贯性的文字,然后返回出来。再修改下render方法,使用这些需要激活文字的信息填充到activeIndexs里面,在遍历中就可以出现效果了。

微信截图_20220129144559.png

结语

在线演示,源码和演示都在codepen里,虽然是个很简单的小效果吧,但是这面祝福却是真心实意的,干技术的不容易时时刻刻又要有不停学习不停进步,还有各方面给的压力也很大,明天就是除夕了,新的一年祝大家工作顺利,身体健康。