|
| 1 | +> 在Github上看到了[wesbos](https://twitter.com/wesbos)的一个Javascript30天挑战的[repo](https://github.com/wesbos/JavaScript30),旨在使用纯Js来进行练习,不允许使用任何其他的库和框架,该挑战共30天,我会在这里记录下自己练习的过程和遇到的问题。 |
| 2 | +
|
| 3 | +## Day08 - Fun with HTML5 Canvas |
| 4 | + |
| 5 | +第八天的练习是制作一个Canvas画板,可以用鼠标随意书写作画,颜色呈彩虹色渐变,画笔的大小呈大小渐变循环。这部分内容不涉及HTML和CSS相关内容,由纯JS实现。 |
| 6 | + |
| 7 | +[效果如下](http://htmlpreview.github.io/?https://github.com/winar-jin/JavaScript30-Challenge/blob/master/08%20-%20Fun%20with%20HTML5%20Canvas/index.html) |
| 8 | + |
| 9 | +## 完整代码 |
| 10 | +```Javascript |
| 11 | +const canvas = document.querySelector('#draw'); |
| 12 | +const ctx = canvas.getContext('2d'); |
| 13 | +canvas.width = window.innerWidth; |
| 14 | +canvas.height = window.innerHeight; |
| 15 | +ctx.strokeStyle = '#BADA55'; |
| 16 | +ctx.lineJoin = 'round'; |
| 17 | +ctx.lineCap = 'round'; |
| 18 | +ctx.lineWidth = 50; |
| 19 | + |
| 20 | +let isDrawing = true; |
| 21 | +let lastX = 0; |
| 22 | +let lastY = 0; |
| 23 | +let hue = 0; |
| 24 | +let direction = true; |
| 25 | + |
| 26 | +function draw(e){ |
| 27 | + if(!isDrawing) return; |
| 28 | + let lineX = e.offsetX; |
| 29 | + let lineY = e.offsetY; |
| 30 | + ctx.strokeStyle = `hsl(${hue},100%,50%)`; |
| 31 | + ctx.beginPath(); |
| 32 | + ctx.moveTo(lastX,lastY); |
| 33 | + ctx.lineTo(lineX,lineY); |
| 34 | + ctx.stroke(); |
| 35 | + [lastX,lastY] = [e.offsetX,e.offsetY]; |
| 36 | + if(direction){ |
| 37 | + ctx.lineWidth--; |
| 38 | + }else{ |
| 39 | + ctx.lineWidth++; |
| 40 | + } |
| 41 | + // if(ctx.lineWidth<=1){ |
| 42 | + // direction = false; |
| 43 | + // }else if(ctx.lineWidth > 50){ |
| 44 | + // direction = true; |
| 45 | + // } |
| 46 | + if (ctx.lineWidth >= 50 || ctx.lineWidth <= 1) { |
| 47 | + direction = !direction; |
| 48 | + } |
| 49 | + |
| 50 | + hue++; |
| 51 | + hue %= 361; |
| 52 | + // if(hue > 360){ |
| 53 | + // hue = 0; |
| 54 | + // } |
| 55 | +} |
| 56 | + |
| 57 | +canvas.addEventListener('mousemove',draw,false); |
| 58 | +canvas.addEventListener('mousedown',(e) => { |
| 59 | + [lastX,lastY] = [e.offsetX,e.offsetY]; |
| 60 | + isDrawing = true; |
| 61 | +},false); |
| 62 | +canvas.addEventListener('mouseup',() => { isDrawing = false;},false); |
| 63 | +canvas.addEventListener('mouseover',() => { isDrawing = false;},false); |
| 64 | +``` |
| 65 | + |
| 66 | +## Canvas |
| 67 | +`<canvas>`是 HTML5 新增的元素,可使用JavaScript脚本来绘制图形。例如:画图,合成照片,创建动画甚至实时视频处理与渲染。在本例中,应先获取页面Canvas元素,并进行初始化设置如下: |
| 68 | +```Javascript |
| 69 | +const canvas = document.querySelector('#draw'); |
| 70 | +const ctx = canvas.getContext('2d'); |
| 71 | +canvas.width = window.innerWidth; |
| 72 | +canvas.height = window.innerHeight; |
| 73 | +ctx.strokeStyle = '#BADA55'; |
| 74 | +ctx.lineJoin = 'round'; |
| 75 | +ctx.lineCap = 'round'; |
| 76 | +ctx.lineWidth = 50; |
| 77 | +``` |
| 78 | + |
| 79 | +* `canvas.getContext(contextType, contextAttributes);`返回canvas的上下文,若没有定义上下文的话返回`null`,该方法有两个参数: |
| 80 | + * `contextType`代表__上下文类型__,可能取值为: |
| 81 | + * `"2d"`,代表一个二维渲染上下文; |
| 82 | + * `"webgl"`代表三维渲染上下文对象,只能在浏览器实现WebGL 版本1; |
| 83 | + * `"webg2"`代表三维渲染上下文对象,只能在浏览器实现WebGL 版本2; |
| 84 | + * `"bitmaprenderer"`会创建一个` ImageBitmapRenderingContext`,只提供功能去替换指定canvas的`ImageBitmap`内容。 |
| 85 | + * `contextAttributes`代表__上下文属性__。 |
| 86 | +[MDN参考文档](https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLCanvasElement/getContext) |
| 87 | + |
| 88 | +* `ctx.strokeStyle`是Canvas 2D API 描述画笔(绘制图形)颜色或者样式的属性。默认值是 #000 (black)。 |
| 89 | +* `ctx.lineJoin`是 Canvas 2D API 用来设置2个长度不为0的相连部分(线段,圆弧,曲线)如何连接在一起的属性(长度为0的变形部分,其指定的末端和控制点在同一位置,会被忽略)。__如果两个线段的相连部分在同一方向,那么linejoin不会产生任何效果。__可以取以下三个值: |
| 90 | + * `'round'`:通过填充一个额外的,圆心在相连部分末端的扇形,绘制拐角的形状。 圆角的半径是线段的宽度; |
| 91 | + * `'bevel'`:在相连部分的末端填充一个额外的以三角形为底的区域, 每个部分都有各自独立的矩形拐角; |
| 92 | + * `'miter'`(默认值):通过延伸相连部分的外边缘,使其相交于一点,形成一个额外的菱形区域。这个设置可以通过 miterLimit 属性看到效果。 |
| 93 | +* `ctx.lineCap` 是 Canvas 2D API 指定如何绘制每一条线段末端的属性。可以取以下三个值: |
| 94 | + * `'round'`:线段末端以圆形结束; |
| 95 | + * `'butt'`(默认值):线段末端以方形结束; |
| 96 | + * `'square'`:线段末端以方形结束,但是增加了一个宽度和线段相同,高度是线段厚度一半的矩形区域。 |
| 97 | +* `ctx.lineWidth`是 Canvas 2D API 设置线段厚度的属性(即线段的宽度)。当获取属性值时,它可以返回当前的值(默认值是1.0 )。 当给属性赋值时, 0、 负数、 Infinity 和 NaN 都会被忽略;除此之外,都会被赋予一个新值。 |
| 98 | + |
| 99 | +## 画线 |
| 100 | +```Javascript |
| 101 | +let isDrawing = true; |
| 102 | +let lastX = 0; |
| 103 | +let lastY = 0; |
| 104 | +let hue = 0; |
| 105 | +let direction = true; |
| 106 | + |
| 107 | +function draw(e){ |
| 108 | + if(!isDrawing) return; |
| 109 | + let lineX = e.offsetX; |
| 110 | + let lineY = e.offsetY; |
| 111 | + ctx.strokeStyle = `hsl(${hue},100%,50%)`; |
| 112 | + ctx.beginPath(); |
| 113 | + ctx.moveTo(lastX,lastY); |
| 114 | + ctx.lineTo(lineX,lineY); |
| 115 | + ctx.stroke(); |
| 116 | + [lastX,lastY] = [e.offsetX,e.offsetY]; |
| 117 | + hue++; |
| 118 | + if(direction){ |
| 119 | + ctx.lineWidth--; |
| 120 | + }else{ |
| 121 | + ctx.lineWidth++; |
| 122 | + } |
| 123 | + // if(ctx.lineWidth<=1){ |
| 124 | + // direction = false; |
| 125 | + // }else if(ctx.lineWidth > 50){ |
| 126 | + // direction = true; |
| 127 | + // } |
| 128 | + if (ctx.lineWidth >= 50 || ctx.lineWidth <= 1) { |
| 129 | + direction = !direction; |
| 130 | + } |
| 131 | + |
| 132 | + hue %= 361; |
| 133 | + // if(hue > 360){ |
| 134 | + // hue = 0; |
| 135 | + // } |
| 136 | +} |
| 137 | +``` |
| 138 | + |
| 139 | +* 想要划线的话就要清楚以下几件事:线段的起点是哪里、线段的终点是哪里、怎样是画线的状态(按下鼠标移动画线,松开鼠标画线结束),因此需要初始化一下变量: |
| 140 | +```Javascript |
| 141 | +let isDrawing = true; //代表是否正在画线的flag |
| 142 | +let lastX = 0; //线段的起点X坐标 |
| 143 | +let lastY = 0; //线段的起点Y坐标 |
| 144 | +let hue = 0; //标识线段颜色(hsl颜色表示法) |
| 145 | +let direction = true; //线段的粗细变化flag |
| 146 | +``` |
| 147 | +* 使用canvas进行画线,使用 beginPath()开始绘制新的路径,使用 moveTo()移动画笔至路径的起始点,使用lineTo标识路径的终点,使用 stroke() 方法真正地画线。 |
| 148 | + * `ctx.beginPath();`是 Canvas 2D API 通过清空子路径列表开始一个新路径的方法。 当你想创建一个新的路径时,调用此方法。 |
| 149 | + * `ctx.moveTo(x, y);`是 Canvas 2D API 将一个新的子路径的起始点移动到(x,y)坐标的方法。 |
| 150 | + * `ctx.lineTo(x, y);`是 Canvas 2D API 使用直线连接子路径的终点到x,y坐标的方法(并不会真正地绘制)。 |
| 151 | + * `ctx.stroke();`是 Canvas 2D API 使用非零环绕规则,根据当前的画线样式,绘制当前或已经存在的路径的方法。 |
| 152 | + * `ctx.closePath();`是 Canvas 2D API 将笔点返回到当前子路径起始点的方法。它尝试从当前点到起始点绘制一条直线。 如果图形已经是封闭的或者只有一个点,那么此方法不会做任何操作。(此例中未用到) |
| 153 | + |
| 154 | +## 颜色变化 |
| 155 | +```Javascript |
| 156 | +ctx.strokeStyle = `hsl(${hue},100%,50%)`; |
| 157 | +hue++; |
| 158 | +hue %= 361; |
| 159 | +// if(hue > 360){ |
| 160 | +// hue = 0; |
| 161 | +// } |
| 162 | +``` |
| 163 | +在画线的过程中动态的改变线条的颜色,采用hsl颜色表示,hue的值可以从0-360;将hue的值每次每次加一,`hue %= 361;`可以0-360一直循环,当然也可使用`if(hue > 360) hue = 0;`来做到。 |
| 164 | + |
| 165 | +## 线条粗细的变化 |
| 166 | +```Javascript |
| 167 | +if(direction){ |
| 168 | + ctx.lineWidth--; |
| 169 | +}else{ |
| 170 | + ctx.lineWidth++; |
| 171 | +} |
| 172 | +// if(ctx.lineWidth<=1){ |
| 173 | +// direction = false; |
| 174 | +// }else if(ctx.lineWidth > 50){ |
| 175 | +// direction = true; |
| 176 | +// } |
| 177 | +if (ctx.lineWidth >= 50 || ctx.lineWidth <= 1) { |
| 178 | + direction = !direction; |
| 179 | +} |
| 180 | +``` |
| 181 | +设置一个flag,当flag为true时,线段宽度就减小;当flag为false时,线段宽度就增加。使用`if (ctx.lineWidth >= 50 || ctx.lineWidth <= 1) { |
| 182 | + direction = !direction; |
| 183 | +}`可以当线段的宽度在50和1处,使flag的值变化,已达到线段粗细循环渐变的效果,等同于以下代码:`if(ctx.lineWidth<=1){ direction = false;}else if(ctx.lineWidth > 50){ direction = true; }`。 |
| 184 | + |
| 185 | +在draw函数的开始`if(!isDrawing) return;`代表当状态flag为假时就不画线,为真时才画线。 |
| 186 | + |
| 187 | +## 事件监听 |
| 188 | +```Javascript |
| 189 | +canvas.addEventListener('mousemove',draw,false); |
| 190 | +canvas.addEventListener('mousedown',(e) => { |
| 191 | + [lastX,lastY] = [e.offsetX,e.offsetY]; |
| 192 | + isDrawing = true; |
| 193 | +},false); |
| 194 | +canvas.addEventListener('mouseup',() => { isDrawing = false;},false); |
| 195 | +canvas.addEventListener('mouseover',() => { isDrawing = false;},false); |
| 196 | +``` |
| 197 | +分别监听其`mousemove`,`mousedown`,`mouseup`,`mouseover`事件,当鼠标点下时设置线段起点为鼠标的当前坐标,`[lastX,lastY] = [e.offsetX,e.offsetY]`;并且只设置`mousedown`事件的`isDrawing`状态flag为true,代表当鼠标按下时画线,鼠标弹起状态和鼠标移开canvas对象下状态不画线。 |
| 198 | + |
| 199 | +> `[lastX,lastY] = [e.offsetX,e.offsetY];`等同于`lastX = e.offsetX;lastY = e.offsetY;` |
| 200 | +
|
| 201 | +至此就实现了一个canvas下彩虹画笔的画板效果,以上一些属性的介绍参考了[MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D),同样我也认为在学习前端的过程中,应该多多浏览MDN等文档,以熟练自己的知识。 |
0 commit comments