最近在慕课网上找到了很好的canvas教程, 来自 @liuyubobobo 的 学写一个字 canvas绘图教程 在 @liuyubobobo 老师的系列canvas教程中,我学到不少知识。 今天,运用在视频中的所学,结合自己的代码风格,我自己尝试也写一个字帖出来,在这里分享一下思路和过程 具体代码其实已经push在github上,感兴趣的可以clone下来参考一下。 代码/canvas-demo/write 这里还有在线的效果演示 在线演示
在canvas在实现这样一个效果,有一个米字格,可以用鼠标(pc)甚至手指(手机)在上面写字,字要有点像毛笔字。 下面有控制部件,可以控制笔的颜色,还是可以清空米字格的墨迹。
下面就开始着手去写了。
css和html部分不是很多,也不是我要讲的重点,简单贴出来
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>毛笔字</title> <meta name="viewport" content="width=device-width;height=device-height;initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <link href="css/write.css" rel="stylesheet" type="text/css"> </head> <body> <canvas id="canvas"> 您的浏览器版本不支持canvas,请更新或者下载chrome </canvas> <div id="controller"> <div id="black" class="on"></div> <div id="red"></div> <div id="green"></div> <div id="yellow"></div> <div id="purple"></div> <div id="orange"></div> <div id="blue"></div> <div id="indigo"></div> <button id="reset">清除</button> </div> </body> </html>
#canvas{ display: block; margin: 20px auto; border: #ff1722 5px solid; } /*控制器*/ #controller{ text-align: center; } #controller>div{ display: inline-block; width: 30px; height: 30px; border: gray 2px solid; } #black{ background: black; } #red{ background: red; } #green{ background: green; } #yellow{ background: yellow; } #purple{ background: purple; } #orange{ background: orange; } #blue{ background: blue; } #indigo{ background: indigo; } #controller>div.on{ border-color: red; } #reset{ display: inline-block; vertical-align: top; width: 50px; height: 34px; }
js部分就很关键了,涉及到很多api,交互流程,编程思路,下面慢慢来讲:
首先,按照功能分类,我们将代码分成3个模块
1. 第1部分paper.js
负责绘制出米字格的字帖背景
代码设计属性如下
paper.js
var paper = { canvas: null,//html中的canvas对象,主要标签 context: null, //canvas对象获取的context,用于绘图 init: function (canvas) { //初始化方法 }, drawPaper: function(){ //绘制米字格图纸的接口 }, drawDotted: function(sx, sy, ex, ey){ //绘制虚线的接口 } };
2. 第2部分controller.js
负责控制画笔的颜色和清理画布,即控制面板的功能实现。
代码设计属性如下
controller.js
var controller = { canvas: null,//html中的canvas对象,主要标签 context: null, //canvas对象获取的context,用于绘图 init: function (canvas) { //初始化方法 }, bindEvent: function () { //事件监听的设置区域 }, setColor: function (target) { //设置画笔颜色的接口 }, clear: function () { //清理画布墨迹的接口 } };
3. 第3部分write.js
第三部分也是最关键最复杂的部分,负责描绘出鼠标(手指)划过的笔画
因为要协调笔画,会用到比较多的辅助函数和辅助参数,下面会一一介绍
write.js
var write = { canvas: null, //html中的canvas对象,主要标签 context: null, //canvas对象获取的context,用于绘图 isWriting: false,//是否正在下笔写字 lineWidthMax: 0, //画笔最大粗细 lineWidthMin: 1, //画笔最小粗细 lastX: 0,//画笔上次停留位置 lastY: 0, lastTime: 0, //上次移笔时间 lastLineWidth: 0,//上次画下的笔宽 init: function (canvas) { //初始化方法 }, bindEvent: function () { //事件监听的设置区域 }, //描绘区 startWrite: function (co) { //开始落笔 }, writing: function (co) { //笔移动,正在写字 }, endWrite: function(){ //收笔 }, //辅助函数区 getCo: function (clientX, clientY) { //返回落笔的位置 }, getS: function (sx, sy, ex, ey) { //返回两点间的距离 }, getLineWidth: function (s, t) { //计算笔迹的线条宽度 } };
1. 第1部分paper.js
针对paper模块,我们知道,是用来设置字帖纸的样式的。
我们要做的有两步
初始化方法init()
是一个模块最开始的地方。我们从 init
开始,边看代码边解释
外接通过编写如下代码调用paper,完成其初始化,并让运行
var canvas = document.getElementById('cnavas'); paper.init(canvas);
所以我们在paper.init中接受外界传来的canvas,并利用它完成初始化,运行绘制方法,编写如下
var paper = { canvas: null,//html中的canvas对象,主要标签 context: null, //canvas对象获取的context,用于绘图 init: function (canvas) { this.canvas = canvas;//接收外界canvas,赋值给自己的属性``canvas``,在下面的其他方法中需要用到 this.context = canvas.getContext('2d');//通过canvas获取context,赋值给自己的属性``context``,在下面的其他方法中需要用到 //动态设置canvas大小,兼容手机和pc this.canvas.width = Math.min(500, window.innerWidth - 20);// 米字格最大只能为500px this.canvas.height = this.canvas.width; this.drawPaper(); //绘制米字格背景,自己完善drawPaper方法 }, drawPaper: function() {}, drawDotted: function(sx, sy, ex, ey){} //...... };
drawPaper()
方法就更加简单了,只需要调用drawDotted()
接口绘制线段就可以了
//... drawPaper: function(){ this.drawDotted(0, 0, this.canvas.width, this.canvas.height);//左上角到右下角的斜线 this.drawDotted(this.canvas.width, 0, 0, this.canvas.height);//左下角到右上角的斜线 this.drawDotted(this.canvas.width/2, 0, this.canvas.width/2, this.canvas.height);//中间的横线 this.drawDotted(0, this.canvas.height/2, this.canvas.width, this.canvas.height/2);//中间的竖线 }, drawDotted: function(sx, sy, ex, ey){} //...
drawDotted()
实现起来需要自己构思了,最新的h5标准已经有setLineDash用来绘制虚线,ie11以上都行,支持性不错,但是为了兼容,我们也尝试手写一些虚线函数出来
//··· drawDotted: function(sx, sy, ex, ey){ var lineInterval = 5;//虚线的间隔 this.context.save();//保存当前的context状态(快照,用于恢复,防止状态设置紊乱污染) this.context.lineWidth = 3;//线宽 this.context.strokeStyle = '#ff1722';//线条颜色 //setLineDash 虚线设置接口比较新,为了保险起见,自己编写一下 if(this.context.setLineDash){ // 使用h5的setLineDash方法 this.context.setLineDash([lineInterval, lineInterval]);//[线宽, 间隔宽] this.context.moveTo(sx,sy); this.context.lineTo(ex,ey); } else{//setLineDash不存在,自己手动处理 //len 虚线要绘制成多少段 var len = Math.ceil(Math.sqrt((ex - sx)*(ex - sx) + (ey - sy)*(ey - sy)) / lineInterval /2); var lineIntervalX = (ex - sx) / len;//每一段间x轴上的间隔 var lineIntervalY = (ey - sy) / len;//每一段间y轴上的间隔 var index = 0;//计数器 this.context.beginPath(); while (index < len) {//开始定制路线 var targetX = sx + lineIntervalX;//计算当前段绘制的终点 var targetY = sy + lineIntervalY; this.context.moveTo(sx, sy);//起点 this.context.lineTo(targetX, targetY);//终点 sx = targetX + lineIntervalX;//计算下一段绘制的起点 sy = targetY + lineIntervalY; index ++; } } this.context.stroke();//绘制线条 this.context.restore();//恢复保存的状态,对应 save() 方法 } //···
如此,一个米字格图纸的绘制方法已经完成了,下面的代码汇总
var paper = { canvas: null,//html中的canvas对象,主要标签 context: null, //canvas对象获取的context,用于绘图 init: function (canvas) { this.canvas = canvas;//接收外界canvas,赋值给自己的属性``canvas``,在下面的其他方法中需要用到 this.context = canvas.getContext('2d');//通过canvas获取context,赋值给自己的属性``context``,在下面的其他方法中需要用到 //动态设置canvas大小,兼容手机和pc this.canvas.width = Math.min(500, window.innerWidth - 20);// 米字格最大只能为500px this.canvas.height = this.canvas.width; this.drawPaper(); //绘制米字格背景,自己完善drawPaper方法 }, drawPaper: function(){ this.drawDotted(0, 0, this.canvas.width, this.canvas.height);//左上角到右下角的斜线 this.drawDotted(this.canvas.width, 0, 0, this.canvas.height);//左下角到右上角的斜线 this.drawDotted(this.canvas.width/2, 0, this.canvas.width/2, this.canvas.height);//中间的横线 this.drawDotted(0, this.canvas.height/2, this.canvas.width, this.canvas.height/2);//中间的竖线 }, drawDotted: function(sx, sy, ex, ey){ var lineInterval = 5;//虚线的间隔 this.context.save();//保存当前的context状态(快照,用于恢复,防止状态设置紊乱污染) this.context.lineWidth = 3;//线宽 this.context.strokeStyle = '#ff1722';//线条颜色 //setLineDash 虚线设置接口比较新,为了保险起见,自己编写一下 if(this.context.setLineDash){ // 使用h5的setLineDash方法 this.context.setLineDash([lineInterval, lineInterval]);//[线宽, 间隔宽] this.context.moveTo(sx,sy); this.context.lineTo(ex,ey); } else{//setLineDash不存在,自己手动处理 //len 虚线要绘制成多少段 var len = Math.ceil(Math.sqrt((ex - sx)*(ex - sx) + (ey - sy)*(ey - sy)) / lineInterval /2); var lineIntervalX = (ex - sx) / len;//每一段间x轴上的间隔 var lineIntervalY = (ey - sy) / len;//每一段间y轴上的间隔 var index = 0;//计数器 this.context.beginPath(); while (index < len) {//开始定制路线 var targetX = sx + lineIntervalX;//计算当前段绘制的终点 var targetY = sy + lineIntervalY; this.context.moveTo(sx, sy);//起点 this.context.lineTo(targetX, targetY);//终点 sx = targetX + lineIntervalX;//计算下一段绘制的起点 sy = targetY + lineIntervalY; index ++; } } this.context.stroke();//绘制线条 this.context.restore();//恢复保存的状态,对应 save() 方法 } };
如此,在html中调用 paper.init(canvas), 一个米字格就会成功绘制在你的面前,是不是很简单又有趣
边幅有点长,这是绘制的第一部分,我们在接下来的一篇博客里再讲第二部分,请期待 利用canvas实现毛笔字帖(二), 跟大家真正实现毛笔写字的部分
本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。
我来说两句