内容借鉴慕课网 学写一个字 ,老师@liuyubobobo 讲的不错,值得学习。
学一个技术,最重要的就是练。我自己还是个没啥想法的人,那就借鉴别人的想法,跟着老师学习,模仿大神。
本练习目标是在 PC 和移动端完成一个手写板,以此来练习 canvas 的基础知识。
绘制米字格 首先要绘制米字格,第一步当然是在 html 中添加 canvas:
1 <canvas id ="canvas" > 您的浏览器不支持canvas</canvas >
然后定义相应的样式。
注意不推荐在 CSS 中定义 canvas 的大小,因为 CSS 定义的 canvas 大小只是它在 html 文档中显示的大小,而不是具体的 canvas 的分辨率。如果想要定义分辨率的话,通常要定义 width 和 height,在 JS 中定义会更好。
进行一段绘制的时候,可以将代码进行封装,另外,如果进行了状态设置,为了不然设置的状态影响到我们其他部分的绘制,要使用 context.save(); 和 context.restore(); 这样就不会影响其他部分的状态。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 let canvasWidth;let canvasHeight = (canvasWidth = 800 );let canvas = document .getElementById("canvas" );let context = canvas.getContext("2d" );canvas.width = canvasWidth; canvas.height = canvasHeight; function drawGrid ( ) { context.save(); context.strokeStyle = "rgb(230,11,9)" ; context.beginPath(); context.moveTo(3 , 3 ); context.lineTo(canvasWidth - 3 , 3 ); context.lineTo(canvasWidth - 3 , canvasHeight - 3 ); context.lineTo(3 , canvasHeight - 3 ); context.closePath(); context.lineWidth = 6 ; context.stroke(); context.beginPath(); context.moveTo(0 , 0 ); context.lineTo(canvasWidth, canvasHeight); context.moveTo(canvasWidth, 0 ); context.lineTo(0 , canvasHeight); context.moveTo(canvasWidth / 2 , 0 ); context.lineTo(canvasWidth / 2 , canvasHeight); context.moveTo(0 , canvasHeight / 2 ); context.lineTo(canvasWidth, canvasHeight / 2 ); context.lineWidth = 1 ; context.stroke(); context.restore(); }
鼠标响应 我们的目的是要在 PC 端用鼠标来写字,那我们就需要对鼠标的时间做一个响应。
分别有 onmousedown, onmouseup, onmouseout, onmousemove。我们对每一个事件做响应的操作。
注: e.preventDefault(); 组织浏览器默认动作。(虽然在这个练习的鼠标事件处并没有什么用,但是为了规范写上好一些)
坐标转换 画布位置的屏幕和鼠标点击的屏幕位置需要调整。 我们获取鼠标给回来的文档坐标,然后减去 canvas 的 left 和 top 即可。
使用 canvas.getBoundingClientRect() 获取 canvas 距离上边缘和左边缘的距离即可。
通过对象进行数据传递
鼠标绘制实现 在鼠标移动的时候,利用循环在短时间绘从上一次的位置到当前位置绘制直线。
粗线条问题:如果线条过粗,绘制的直线就变成了一个个矩形,会导致绘制的线条有毛边。
利用 context.lineCap = ‘round’ 以及 context.lineJoin = ‘round’ 使得线条更加自然。
运笔速度对画笔影响 类似一条线的绘制,从初始到终止位置有距离 S 和时间 T,我们通过计算出速度 V 来改变笔画的速度。速度越快画笔越细。
由于速度的变化其实很快,带来的数值变化也非常的大,所以我们需要记录 lastLineWidth 来使得画笔的宽度变化变得更平滑一些。
代码如下:
1 2 3 4 5 6 if (lastLineWidth === -1 ) { return resultLineWidth; } else { return lastLineWidth * (7 / 10 ) + resultLineWidth * (1 - 7 / 10 ); }
完整代码 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <!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,minimum-scale=1.0,user-scalable=no" /> <script src ="../../jquery-3.3.1/jquery-3.3.1.min.js" > </script > <link rel ="stylesheet" href ="css/main.css" /> </head > <body > <canvas id ="canvas" > 您的浏览器不支持canvas </canvas > <div id ="controller" > <div id ="black_btn" class ="color_btn color_btn_selected" > </div > <div id ="blue_btn" class ="color_btn" > </div > <div id ="green_btn" class ="color_btn" > </div > <div id ="red_btn" class ="color_btn" > </div > <div id ="orange_btn" class ="color_btn" > </div > <div id ="yellow_btn" class ="color_btn" > </div > <div id ="clear_btn" class ="op_btn" > 清除</div > <div class ="clearFix" > </div > </div > <script src ="js/main.js" > </script > </body > </html >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 #canvas { display : block; margin : 0 auto; border : 1px solid #aaa ; } #controller { margin : 0 auto; } .op_btn { float : right; margin : 10px 0 0 10px ; border : 2px solid #aaa ; width : 80px ; height : 40px ; line-height : 40px ; font-size : 20px ; text-align : center; -webkit-border-radius : 5px 5px ; -moz-border-radius : 5px 5px ; border-radius : 5px 5px ; cursor : pointer; background-color : white; font-weight : bold; font-family : "Microsoft Sans Serif" ; } .op_btn :hover { background-color : #def ; } .clearFix { clear : both; } .color_btn { float : left; margin : 10px 10px 0 0 ; border : 5px solid white; width : 40px ; height : 40px ; border-radius : 5px 5px ; cursor : pointer; } .color_btn :hover { border : 5px solid violet; } .color_btn_selected { border : 5px solid blueviolet; } #black_btn { background-color : black; } #blue_btn { background-color : blue; } #green_btn { background-color : green; } #red_btn { background-color : red; } #orange_btn { background-color : orange; } #yellow_btn { background-color : yellow; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 let canvasWidth;let canvasHeight = (canvasWidth = Math .min(800 , $(window ).width() - 20 ));let strokeColor = "black" ;let isMouseDown = false ;let lastLoc = { x : 0 , y : 0 };let lastTimeStamp = 0 ;let lastLineWidth = -1 ;let canvas = document .getElementById("canvas" );let context = canvas.getContext("2d" );canvas.width = canvasWidth; canvas.height = canvasHeight; function windowToCanvas (x, y ) { let bbox = canvas.getBoundingClientRect(); return { x : Math .round(x - bbox.left), y : Math .round(y - bbox.top) }; } $("#controller" ).css("width" , canvasWidth + "px" ); drawGrid(); $("#clear_btn" ).click(function ( ) { context.clearRect(0 , 0 , canvasWidth, canvasHeight); drawGrid(); }); $(".color_btn" ).click(function (e ) { $(".color_btn" ).removeClass("color_btn_selected" ); $(this ).addClass("color_btn_selected" ); strokeColor = $(this ).css("background-color" ); }); function beginStroke (point ) { isMouseDown = true ; lastLoc = windowToCanvas(point.x, point.y); lastTimeStamp = new Date ().getTime(); } function endStroke ( ) { isMouseDown = false ; } function moveStroke (point ) { let curLoc = windowToCanvas(point.x, point.y); let curTimeStamp = new Date ().getTime(); let s = calcDistance(curLoc, lastLoc); let t = curTimeStamp - lastTimeStamp; let lineWidth = calcLineWidth(t, s); context.beginPath(); context.moveTo(lastLoc.x, lastLoc.y); context.lineTo(curLoc.x, curLoc.y); context.strokeStyle = strokeColor; context.lineWidth = lineWidth; context.lineCap = "round" ; context.lineJoin = "round" ; context.stroke(); lastLoc = curLoc; lastTimeStamp = curTimeStamp; lastLineWidth = lineWidth; } canvas.onmousedown = function (e ) { e.preventDefault(); beginStroke({ x : e.clientX, y : e.clientY }); }; canvas.onmousemove = function (e ) { e.preventDefault(); if (isMouseDown) { moveStroke({ x : e.clientX, y : e.clientY }); } }; canvas.onmouseup = function (e ) { e.preventDefault(); endStroke(); }; canvas.onmouseout = function (e ) { e.preventDefault(); endStroke(); }; canvas.addEventListener("touchstart" , function (e ) { e.preventDefault(); touch = e.touches[0 ]; beginStroke({ x : touch.pageX, y : touch.pageY }); }); canvas.addEventListener("touchmove" , function (e ) { e.preventDefault(); if (isMouseDown) { touch = e.touches[0 ]; moveStroke({ x : touch.pageX, y : touch.pageY }); } }); canvas.addEventListener("touchend" , function (e ) { e.preventDefault(); endStroke(); }); let maxLineWidth = 30 ;let minLineWidth = 1 ;let maxStrokeV = 10 ;let minStrokeV = 0.1 ;function calcLineWidth (t, s ) { let v = s / t; let resultLineWidth; if (v <= minStrokeV) { resultLineWidth = maxLineWidth; } else if (v >= maxStrokeV) { resultLineWidth = minLineWidth; } else { resultLineWidth = maxLineWidth - ((v - minStrokeV) / (maxStrokeV - minStrokeV)) * (maxLineWidth - minLineWidth); } console .log(resultLineWidth); if (lastLineWidth === -1 ) { return resultLineWidth; } else { return lastLineWidth * (7 / 10 ) + resultLineWidth * (1 - 7 / 10 ); } } function calcDistance (loc1, loc2 ) { return Math .sqrt( (loc1.x - loc2.x) * (loc1.x - loc2.x) + (loc1.y - loc2.y) * (loc1.y - loc2.y) ); } function drawGrid ( ) { context.save(); context.strokeStyle = "rgb(230,11,9)" ; context.beginPath(); context.moveTo(3 , 3 ); context.lineTo(canvasWidth - 3 , 3 ); context.lineTo(canvasWidth - 3 , canvasHeight - 3 ); context.lineTo(3 , canvasHeight - 3 ); context.closePath(); context.lineWidth = 6 ; context.stroke(); context.beginPath(); context.moveTo(0 , 0 ); context.lineTo(canvasWidth, canvasHeight); context.moveTo(canvasWidth, 0 ); context.lineTo(0 , canvasHeight); context.moveTo(canvasWidth / 2 , 0 ); context.lineTo(canvasWidth / 2 , canvasHeight); context.moveTo(0 , canvasHeight / 2 ); context.lineTo(canvasWidth, canvasHeight / 2 ); context.lineWidth = 1 ; context.stroke(); context.restore(); }