阅读 107

Canvas如何做个绚丽的万花筒画笔

前言

运动和变化在不断地更新着世界,

就像不间断的时间总在更新无穷无尽的岁月的持续一样。

——马尔库·奥勒利乌斯

介绍

本期我将给大家讲解一个新作品——万花筒画笔, 相信我们很多人小的时候都玩过万花筒。它通过长长的一个筒子,通过一个镜片,我们就可以看到里边那般五彩缤纷的世界。随着我们转动,它在我们的眼前呢就呈现出千奇百怪的图像,所以,今天就想能不能通过代码绘制让这个时间再度在计算机中呈现,就想到了这款画笔。

VID_20211012_084207.gif

如上图所示,它会不断改变颜色,随着你输入设备的拖移也随即变化出相应的形态。而且它不借助任何第三方库,仅仅使用canvas api去完成绘制,接下来,我们就要从基础结构,事件绑定,绘制渲染等三个方面去讲解它。

正文

1.基础结构

我们本次还是用vite去构建,先看下HTML结构吧

<head>     <!-- .... --> <style>         body{             width: 100%;             height: 100vh;         }         canvas{             width: 100%;             height: 100%;                  }     </style> </head> <body>     <canvas id="canvas"></canvas>     <script type="module" src="./app.js"></script> </body> 复制代码

很简单,我们就放一个canvas标签和引入app.js作为主逻辑就行了。因为本次也是简短,就把这次的逻辑都写到了app.js里,不做单独拆分了。

/*app.js*/ class Application {   constructor() {     this.canvas = null;                         // 画布     this.ctx = null;                            // 环境     this.w = 0;                                 // 画布宽     this.h = 0;                                 // 画布高     this.n = 18;                                // 万花筒一次画出的线条数量     this.bgColor = "rgba(255,255,255,1)";       // 背景颜色     this.penColor = 0;                          // 线条颜色     this.lineCap = "round"                      // 线帽类型     this.state = false;                         // 当前按下状态,false是没按下,true是按下     this.point = null;                          // 当前按下的坐标点     this.rotate = 0;                            // 万花筒每条线的偏移角度     this.init();   }   init() {     this.canvas = document.getElementById("canvas");     this.ctx = this.canvas.getContext("2d");     window.addEventListener("resize", this.reset.bind(this));     this.penColor = ~~(Math.random() * 360)     this.rotate = 360 / this.n * Math.PI / 180;     this.reset();     this.bindEvent();     this.step();   }   reset() {     // 重置     this.w = this.canvas.width = this.ctx.width = window.innerWidth;     this.h = this.canvas.height = this.ctx.height = window.innerHeight;     this.penColor = ~~(Math.random() * 360)     this.clear();   }   clear() {     // 清空画布     this.ctx.clearRect(0, 0, this.w, this.h);   }   _mouseDown(e) {      // 输入设备按下   }   _mouseUp() {      // 输入设备抬起   }   _mouseMove(e) {       // 输入设备移动   }   bindEvent() {      // 绑定事件   }   drawLine(x, y) {      // 绘制线条   }   step() {      // 帧变化   } } window.onload = new Application(); 复制代码

基础结构跟之前的案例也大体相同,注释应该能看明白。

我们这里主要讲解一下这几点:

  • penColor:这是我们笔触的当前颜色,他是一个数值类型,因为每次生成要随机一个值给它,而且后面在step实时变化中,将会让它也不断增益变化,达到改变颜色的效果,所以,在绘制的时候,将通过hsl去做颜色控制,而penColor充当了色相这一栏的值,即hsl(色相,饱和度,亮度)。

  • rotate:初始化就会执行计算,算出后面笔触分裂出的每条线段偏移的基本角度。

  • reset:每次屏幕的宽高发生变化都期望重置一下整个环境。

2.绑定数据

我们先分析一下,我的场景可能在电脑上也可能在手机上,所以,分别做两套事件,即轻触和鼠标。

bindEvent() {     const {canvas} = this;     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)     } } 复制代码

他们都会分为按下,移动,抬起三个事件,然后我们来写对应的事件吧

_mouseDown(e) {     let {clientX, clientY, touches} = e;     let x = clientX,         y = clientY;     if (touches) {         x = touches[0].clientX;         y = touches[0].clientY;     }     this.state = true     this.point = {x,y} } _mouseMove(e) {     if (!this.state) return;     let {clientX, clientY, touches} = e;     let x = clientX,         y = clientY;     if (touches) {         x = touches[0].clientX;         y = touches[0].clientY;     }     this.drawLine(x, y)     this.point = {x,y} } _mouseUp() {     this.state = false } 复制代码

  • 按下:将state状态变成true,并且记录一下初始点

  • 移动:只有state状态变为true时才会向下执行,拿到点先在drawLine去绘制线条,然后在用point去保存坐标,这样下次就能用结束的坐标开始绘制。

  • 抬起:将state状态变成false

3.绘制渲染

我们在刚刚的输入设备移动事件中,可以拿到要移动到x和y坐标值,接下来就用drawLine先去做绘制。

drawLine(x, y) {     const {w, h, ctx, penColor, point, rotate, n, lineCap} = this;     ctx.lineWidth = 10;     ctx.strokeStyle = `hsl(${~~penColor}, 100% , 50%)`;     ctx.lineCap = lineCap;     ctx.lineJoin = "round";     ctx.shadowColor = "rgba(255,255,255,.1)";     ctx.shadowBlur = 1;     for (let i = 0; i < n; i++) {         ctx.save();         ctx.translate(w / 2, h / 2);         ctx.rotate(rotate * i);         if ((n % 2 === 0) && i % 2 !== 0) {             ctx.scale(1, -1);         }         ctx.beginPath();         ctx.moveTo(point.x - w / 2, point.y - h / 2);         ctx.lineTo(x - w / 2, y - h / 2);         ctx.stroke();         ctx.restore();     } } 复制代码

其实,说来也简单,最关键的是,找准坐标原点,一原点为中心去绘制你要绘制的线条。而且他是怎么分裂的呢,看了代码就会发现,我用了ctx.rotate方法做了旋转,因为最早我们在初始化中也有个rotate计算了他的偏移角度,每条先根据原点和当前下标i做计算应当偏移到那个角度上做绘制,而且当总线条数是偶数时,每隔一条让他在外周反正形成对称。剩下的就是canvas api最基础的绘制操作了,太过基础就不做过多说明了。

微信截图_20211012094300.png

看,我们现在就可以在画布中绘制东西了,但是万花筒是变化的,我们不期望画过的线段一直保留下去,而且慢慢消失掉,接下来,我们就要不断的重绘他了。

step() {     window.requestAnimationFrame(this.step.bind(this))     const {ctx, w, h} = this;     ctx.fillStyle = "rgba(255,255,255,.008)"     ctx.fillRect(0, 0, w, h);     this.penColor += 0.05;     this.penColor %= 360; } 复制代码

我们通过fillRect一层层的覆盖白色透明层,想达渐渐消失的效果,另外,这里我们可以让笔触颜色渐渐的发生变化。

微信截图_20211012095003.png

但是问题来了,我们发现绘制之后的虽然颜色淡了,但是久久不能消失,他的路径轨迹依然会让人看到非常的丑。

所以,我们还要改进一下step方法。

step() {     window.requestAnimationFrame(this.step.bind(this))     const {ctx, w, h} = this;     ctx.globalCompositeOperation = "lighter";     ctx.fillStyle = "rgba(255,255,255,.008)"     ctx.fillRect(0, 0, w, h);     ctx.globalCompositeOperation = "source-over";     this.penColor += 0.05;     this.penColor %= 360; } 复制代码

相信,大家已经明白了,这里直接用了globalCompositeOperation去做了混合重叠对比直接消除了颜色轨迹的色值。

现在我们就实现了想要的结果,一款万花筒画笔——在线演示

VID_20211012_084207.gif

结语

通过这个案例,一些绘制偏移技巧,色值变化,globalCompositeOperation的使用,相信大家也有了自己的新想法,现在仅仅是个万花筒画笔,我们也可以生成三角形圆形等等图形做个真实的万花筒也可以哦,或者是多重福字门帘也可以再此基础上去实现,还等什么赶紧发挥自己的创意吧


作者:jsmask
链接:https://juejin.cn/post/7017995850756390920


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐