Canvas如何做个吉利画笔

2,164

这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战

前言

我们喜欢别人的敬重并非因为敬重本身,

而是因为人们的敬重所带给我们的好处。

——爱尔维修

介绍

前端毕竟主要是通过视觉表达的一种艺术,我们如何在有限的空间内,让用户新心若狂呢,最简单的就是说点吉利话,毕竟人的本性都是喜欢听好的,开心最重要~

VID_20210824_155806.gif

怎么样?是不是很有年味了,目的就是让大家天天都像过年一样开心。

本期要做的就是一种拖动鼠标生成吉利话的画笔!我们大致会从基础机构,画笔类,绘制字体几方面入手,来讲解。

非常简单,我们要开始咯~

出发

1.基础结构

<canvas id="canvas"></canvas>
<script type="module" src="./app.js"></script>

这里把我们的画布方式去,然后再利用module模式引入js方便其模块导入。

* {
    padding: 0;
    margin: 0;
}

html,
body {
    width: 100%;
    height: 100vh;
    position: relative;
    overflow: hidden;
}

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;
}

#canvas {
    width: 100%;
    height: 100%;
}

在css里我们为了背景美观,用repeating-radial-gradient绘制了一张背景图,如下:

微信截图_20210824162026.png

/*app.js*/

// 引入画笔
import fontPen from "./js/FontPen.js";

class Application {
  constructor() {
    this.canvas = null;        // 画布
    this.ctx = null;           // 环境
    this.w = 0;                // 画布宽
    this.h = 0;                // 画布高
    this.init();
  }
  init() {
    // 初始化
    this.canvas = document.getElementById("canvas");
    this.ctx = this.canvas.getContext("2d");
    window.addEventListener("resize", this.reset.bind(this));
    this.reset();
    this.render();
  }
  reset() {
    // 屏幕改变
    this.w = this.canvas.width = this.ctx.width = window.innerWidth;
    this.h = this.canvas.height = this.ctx.height = window.innerHeight;
  }
  render() {
    // 主渲染
    const { w, h, ctx } = this;
    // 初始化画笔(画笔内容后面会具体讲到)
    this.fontPen = fontPen.init(ctx);
  }
}

window.onload = new Application();

我们这期可以看到没有用到重绘,因为我们写完是固定到画布上的不会消失,所以不需要清空画布了。

而且我们期望引入画笔的时候已经是一个实例化好的画笔,就是采取单例模式,还有画笔要在初始化的时候,把***ctx(canvas.context)***传过去,使其可以在绘图环境内绘制。

2.画笔类

/*FontPen.js*/
class FontPen {
    constructor() {
        this.ctx = null;                    // 环境
        this.index = 0;                     // 当前文字标记位
        
        // 要写的文字集
        this.text = "恭喜发财万事如意吉星高照身体健康福寿安康吉祥心想事成平安阖家幸福快乐开心"; 
        
        this.isDown = false;                // 是否按下
        this.color = "rgb(253,190,0)"       // 文字颜色
        this.fontSize = 16;                 // 最小文字大小
        this.startX = 0;                    // 起始点x轴坐标
        this.startY = 0;                    // 起始点y轴坐标
        return this;
    }
    init(ctx) {
        // 初始化
        this.ctx = ctx;
        let canvas = this.ctx.canvas;
        if (navigator.userAgent.match(/(iPhone|iPod|Android|ios)/i)) {
            canvas.addEventListener("touchstart", this._mouseDown.bind(this), false);
            canvas.addEventListener("touchmove", this._mouseMove.bind(this), false);
            canvas.addEventListener("touchend", this._mouseUp.bind(this), false);
        } else {
            canvas.addEventListener("mousedown", this._mouseDown.bind(this), false)
            canvas.addEventListener("mousemove", this._mouseMove.bind(this), false)
            canvas.addEventListener("mouseup", this._mouseUp.bind(this), false)
            canvas.addEventListener("mouseout", this._mouseUp.bind(this), false)
        }
        return this;
    }
    _mouseDown(event) {
        // 输入设备按下
        this.isDown = true;
        const { offsetX, offsetY } = event;
        this.startX = offsetX || event.changedTouches[0].clientX;
        this.startY = offsetY || event.changedTouches[0].clientY;
    }
    _mouseMove(event) {
        // 输入设备移动
        if (!isDown) return;
        let offsetX = event.offsetX || event.changedTouches[0].clientX;
        let offsetY = event.offsetY || event.changedTouches[0].clientY;
    }
    _mouseUp(event) {
        // 输入设备抬起
        this.isDown = false;
    }
}

// 返回一个画笔的实例化的结果
export default new FontPen();

这里我们先把要想写的那些话写好,初始化后再判断当前的设备是电脑还是手机绑定不同的事件。我们现在的精力就全放在这输入设备的几个事件上了。

我们想要的是按下拖动会绘制文字结果,所以我们在按下将isDown设置成true,抬起变成false,这时我们在_mouseMove要通过isDown去做判断,只有在按下事再做操作。接下来我们就可以在里面绘制文字了~

3.绘制文字

/*FontPen.js*/
_mouseMove(event) {
    const { isDown, text, index, ctx, fontSize, color, startX, startY } = this;
    if (!isDown) return;
    let offsetX = event.offsetX || event.changedTouches[0].clientX;
    let offsetY = event.offsetY || event.changedTouches[0].clientY;
    let dx = startX - offsetX;
    let dy = startY - offsetY;
    let d = Math.sqrt(dx ** 2 + dy ** 2);
    let char = text[index];
    if (d < ctx.measureText(char).width) return;
    this.startX = offsetX;
    this.startY = offsetY;
    ctx.fillStyle = color;
    ctx.font = `bold ${fontSize + d * .52}px fangsong`;
    ctx.shadowColor = color;
    ctx.shadowBlur = 10;
    ctx.save();
    ctx.translate(offsetX, offsetY);
    ctx.fillText(char, 0, 0);
    ctx.restore();
    this.index += 1;
    this.index %= this.text.length;
}

一开始可能会有人疑问,问什么会做个初始位置呢,不能直接拖到哪就写到哪么?其实是不可以的,因为我们要用找个起始位置计算字体间的间距与大小做一个判断。不然就会变成下面这样:

微信截图_20210824165430.png

字体过于密集,而且没有大小变化是不符合我们期望的!

所以我们将利用初始位置x,y轴与拖动位置的x,y轴,利用两点间距离公式求出他们之前的距离,并根据距离与字体的宽度(即ctx.measureText(char).width)做判断,如果超过找个距离再做绘制,还要重新给初始位置的值重新定义的成刚满足条件的拖动位置。再利用其距离的大小跟字体大小做正向的关联。这样我们绘制的距离大了自己也就跟着大了。

微信截图_20210824170324.png

至于,怎么改变字再简单不过了,我们当初定义了一个字体位置标记index,我们每绘制一个字就在标记上加一,如果大于字体集合的长度,就取模从0再次开始。


就这样我们就得到了一只专门绘制吉利话的画笔!!你学废了么?赶紧试试吧~ 在线演示

拓展

很显然我们还可以改造很多东西,比如绘制完后,一定期间内逐个透明消失。再或者可以绘制来改字体的方向

let angle = Math.atan2(dy, dx) + Math.PI;
ctx.rotate(angle);

微信截图_20210824171102.png

同时,我们也可以跳开这些,做一个独一无二的画笔~~


我就喜欢听好话,快夸我~~~