├── README.md
├── css
└── style.css
├── image
├── PC端.png
├── clear.png
├── close.png
├── eraser.png
├── favicon.png
├── ic_droplet1.png
├── line.png
├── line_sel.png
├── pen.png
├── pen_sel.png
├── redo.png
├── redo_sel.png
├── save.png
├── save_sel.png
├── undo.png
├── undo_sel.png
└── 移动端.png
├── index.html
└── js
└── main.js
/README.md:
--------------------------------------------------------------------------------
1 | ## 前言
2 |
3 | 本文主要介绍:
4 |
5 | 1. 项目介绍
6 | 2. 项目效果展示
7 | 3. 一步步实现项目效果
8 | 4. 踩坑
9 |
10 | ## 一、项目介绍
11 |
12 | **名称:** 智绘画板
13 |
14 | **技术栈:** HTML5,CSS3,JavaScript,移动端
15 |
16 | **功能描述:**
17 |
18 | - 支持PC端和移动端在线绘画功能
19 | - 实现任意选择画笔颜色、调整画笔粗细以及橡皮檫擦除等绘画功能
20 | - 实现在线画板的本地保存功能
21 | - 支持撤销和返回操作
22 | - ~~自定义背景颜色~~[这个功能尚未完善好]
23 |
24 | ## 二、项目效果展示
25 |
26 | [项目地址](https://github.com/xyyojl/drawingborad) [预览地址](https://xyyojl.github.io/drawingborad/index.html)
27 |
28 | ### 预览图
29 |
30 | PC端的预览图:
31 |
32 |
33 | 
34 |
35 | 移动端的预览图:
36 |
37 |
38 | 
39 |
40 |
41 |
42 | 看完上面的预览图和体验过**智绘画板**觉得还可以的,记得点个赞哦,不管你是否十分激动,反正我是挺激动的,毕竟自己实现出现的项目效果,挺自豪的,说了一堆废话,下面就可以动起手来敲代码,实现自己想要的效果!!!
43 |
44 | 注:下面实现项目效果主要是关于JavaScript方面的,下面仅仅是提供**实现思路的代码**,**并非全部代码**。
45 |
46 | ## 三、一步步实现项目效果
47 |
48 | ### (一)分析页面
49 |
50 | 通过**用例图**,我们知道用户进入我们这个网站有哪些功能?
51 |
52 | 用户可以进行的操作:
53 |
54 | - 画画
55 | - 改变画笔的粗细
56 | - 切换画笔的颜色
57 | - 使用橡皮檫擦除不想要的部分
58 | - 清空画板
59 | - 将自己画的东西保存成图片
60 | - 进行撤销和重做操作
61 | - ~~切换画板背景颜色~~
62 | - 兼容移动端(支持触摸)
63 |
64 | ### (二)进行HTML布局
65 |
66 | 我书写html的同时,引入了css文件和js文件
67 |
68 | ```html
69 |
70 |
71 |
72 |
73 |
74 |
75 | 智绘画板
76 |
77 |
78 |
79 |
80 |
81 |
82 |
101 |
111 |
112 |
113 |
笔大小
114 |
115 |
笔颜色
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
不透明度
125 |
126 |
127 |
128 |
129 |
130 | ```
131 |
132 | ### (三)用CSS美化界面
133 |
134 | css代码可以根据个人习惯进行美化界面,所以这里就不写css的代码了,大家可以直接看**项目代码**或者从开发者工具中审查元素观看。如果有问题可以私聊我,我觉得问题不大。
135 |
136 | ### (四)使用JS实现项目的具体功能
137 |
138 | #### 1.准备工作
139 |
140 | 首先,准备个容器,也就是画板了,前面的html已经书写好这个容器,这里纯属是废话。
141 |
142 | ```html
143 |
144 | ```
145 |
146 | 然后初始化js
147 |
148 | ```js
149 | let canvas = document.getElementById('canvas');
150 | let context = canvas.getContext('2d');
151 | ```
152 |
153 | 我打算把画板做成全屏的,所以接下来设置一下canvas的宽高
154 |
155 | ```js
156 | let pageWidth = document.documentElement.clientWidth;
157 | let pageHeight = document.documentElement.clientHeight;
158 |
159 | canvas.width = pageWidth;
160 | canvas.height = pageHeight;
161 | ```
162 |
163 | 由于部分IE不支持canvas,如果要兼容IE,我们可以创建一个canvas,然后使用`excanvas`初始化,针对IE加上[exCanvas.js](http://code.google.com/p/explorercanvas/),这里我们明确不考虑IE。
164 |
165 | 但是我在电脑上对浏览器的窗口进行改变,画板不会自适应的放缩。解决办法:
166 |
167 | ```js
168 | // 记得要执行autoSetSize这个函数哦
169 | function autoSetSize(){
170 | canvasSetSize();
171 | // 当执行这个函数的时候,会先设置canvas的宽高
172 | function canvasSetSize(){
173 | // 把变化之前的画布内容copy一份,然后重新画到画布上
174 | let imgData = context.getImageData(0,0,canvas.width,canvas.height);
175 | let pageWidth = document.documentElement.clientWidth;
176 | let pageHeight = document.documentElement.clientHeight;
177 |
178 | canvas.width = pageWidth;
179 | canvas.height = pageHeight;
180 | context.putImageData(imgData,0,0);
181 | }
182 | // 在窗口大小改变之后,就会触发resize事件,重新设置canvas的宽高
183 | window.onresize = function(){
184 | canvasSetSize();
185 | }
186 | }
187 | ```
188 |
189 | #### 2.实现画画的功能
190 |
191 | 实现思路:监听鼠标事件, 用`drawLine()`方法把记录的数据画出来。
192 |
193 | 1. 初始化当前画板的画笔状态,`painting = false`。
194 | 2. 当鼠标按下时(`mousedown`),把`painting`设为`true`,表示正在画,鼠标没松开。把鼠标点记录下来。
195 | 3. 当按下鼠标的时候,鼠标移动(`mousemove`)就**把点记录**下来并画出来。
196 | 4. 如果鼠标移动过快,浏览器跟不上绘画速度,点与点之间会出现间隙,所以我们需要将画出的点用线连起来(`lineTo()`)。
197 | 5. 鼠标松开的时候(`mouseup`),把`painting`设为`false`。
198 |
199 | 注:`drawCircle`这个方法其实可以不用书写,这个只是为了让大家能够理解开始点击的位置在哪里?
200 |
201 | ```js
202 | function listenToUser() {
203 | // 定义一个变量初始化画笔状态
204 | let painting = false;
205 | // 记录画笔最后一次的位置
206 | let lastPoint = {x: undefined, y: undefined};
207 |
208 | // 鼠标按下事件
209 | canvas.onmousedown = function(e){
210 | painting = true;
211 | let x = e.clientX;
212 | let y = e.clientY;
213 | lastPoint = {'x':x,'y':y};
214 | drawCircle(x,y,5);
215 | }
216 |
217 | // 鼠标移动事件
218 | canvas.onmousemove = function(e){
219 | if(painting){
220 | let x = e.clientX;
221 | let y = e.clientY;
222 | let newPoint = {'x':x,'y':y};
223 | drawLine(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y);
224 | lastPoint = newPoint;
225 | }
226 | }
227 |
228 | // 鼠标松开事件
229 | canvas.onmouseup = function(){
230 | painting = false;
231 | }
232 | }
233 |
234 | // 画点函数
235 | function drawCircle(x,y,radius){
236 | // 新建一条路径,生成之后,图形绘制命令被指向到路径上生成路径。
237 | context.beginPath();
238 | // 画一个以(x,y)为圆心的以radius为半径的圆弧(圆),
239 | // 从startAngle开始到endAngle结束,按照anticlockwise给定的方向(默认为顺时针)来生成。
240 | context.arc(x,y,radius,0,Math.PI*2);
241 | // 通过填充路径的内容区域生成实心的图形
242 | context.fill();
243 | // 闭合路径之后图形绘制命令又重新指向到上下文中。
244 | context.closePath();
245 | }
246 |
247 | function drawLine(x1,y1,x2,y2){
248 | // 设置线条宽度
249 | context.lineWidth = 10;
250 | // 设置线条末端样式。
251 | context.lineCap = "round";
252 | // 设定线条与线条间接合处的样式
253 | context.lineJoin = "round";
254 | // moveTo(x,y)将笔触移动到指定的坐标x以及y上
255 | context.moveTo(x1,y1);
256 | // lineTo(x, y) 绘制一条从当前位置到指定x以及y位置的直线
257 | context.lineTo(x2,y2);
258 | // 通过线条来绘制图形轮廓
259 | context.stroke();
260 | context.closePath();
261 | }
262 | ```
263 |
264 | #### 3.实现橡皮擦功能
265 |
266 | 实现思路:
267 |
268 | 1. 获取橡皮擦元素
269 | 2. 设置橡皮擦初始状态,`eraserEnabled = false`。
270 | 3. 监听橡皮擦`click`事件,点击橡皮擦,改变橡皮擦状态,`eraserEnabled = true`,并且切换class,实现**被激活**的效果。
271 | 4. `eraserEnabled`为`true`,移动鼠标用`context.clearRect()`实现了橡皮檫。
272 |
273 | 但是我发现canvas的API中,可以清除像素的就是`clearRect`方法,但是`clearRect`方法的清除区域矩形,毕竟大部分人的习惯中的橡皮擦都是圆形的,所以就引入了剪辑区域这个强大的功能,也就是`clip`方法。下面的代码是使用`context.clearRect()`实现了 橡皮檫。请看踩坑部分,了解如何更好的实现橡皮檫。
274 |
275 | ```js
276 | let eraser = document.getElementById("eraser");
277 | let eraserEnabled = false;
278 |
279 | // 记得要执行listenToUser这个函数哦
280 | function listenToUser() {
281 | // ... 代表省略了之前写的代码
282 | // ...
283 |
284 | // 鼠标按下事件
285 | canvas.onmousedown = function(e){
286 | // ...
287 | if(eraserEnabled){//要使用eraser
288 | context.clearRect(x-5,y-5,10,10)
289 | }else{
290 | lastPoint = {'x':x,'y':y}
291 | }
292 | }
293 |
294 | // 鼠标移动事件
295 | canvas.onmousemove = function(e){
296 | let x = e.clientX;
297 | let y = e.clientY;
298 | if(!painting){return}
299 | if(eraserEnabled){
300 | context.clearRect(x-5,y-5,10,10);
301 | }else{
302 | var newPoint = {'x':x,'y':y};
303 | drawLine(lastPoint.x, lastPoint.y,newPoint.x, newPoint.y);
304 | lastPoint = newPoint;
305 | }
306 | }
307 |
308 | // ...
309 | }
310 |
311 |
312 | // 点击橡皮檫
313 | eraser.onclick = function(){
314 | eraserEnabled = true;
315 | eraser.classList.add('active');
316 | brush.classList.remove('active');
317 | }
318 | ```
319 |
320 | ### 4.实现清屏功能
321 |
322 | 实现思路:
323 |
324 | 1. 获取元素节点。
325 | 2. 点击清空按钮清空canvas画布。
326 |
327 | ```js
328 | let reSetCanvas = document.getElementById("clear");
329 |
330 | // 实现清屏
331 | reSetCanvas.onclick = function(){
332 | ctx.clearRect(0,0,canvas.width,canvas.height);
333 | setCanvasBg('white');
334 | }
335 |
336 | // 重新设置canvas背景颜色
337 | function setCanvasBg(color) {
338 | ctx.fillStyle = color;
339 | ctx.fillRect(0, 0, canvas.width, canvas.height);
340 | }
341 | ```
342 |
343 | #### 5.实现保存成图片功能
344 |
345 | 实现思路:
346 |
347 | 1. 获取`canvas.toDateURL`
348 | 2. 在页面里创建并插入一个a标签
349 | 3. a标签href等于`canvas.toDateURL`,并添加download属性
350 | 4. 点击保存按钮,a标签触发`click`事件
351 |
352 | ```js
353 | let save = document.getElementById("save");
354 |
355 | // 下载图片
356 | save.onclick = function(){
357 | let imgUrl = canvas.toDataURL('image/png');
358 | let saveA = document.createElement('a');
359 | document.body.appendChild(saveA);
360 | saveA.href = imgUrl;
361 | saveA.download = 'mypic'+(new Date).getTime();
362 | saveA.target = '_blank';
363 | saveA.click();
364 | }
365 | ```
366 |
367 | #### ~~6.实现改变背景颜色的功能~~
368 |
369 | ~~实现思路:~~
370 |
371 | 1. ~~获取相应的元素节点。~~
372 | 2. ~~给每一个class为bgcolor-item的标签添加点击事件,当点击事件触发时,改变背景颜色。~~
373 | 3. ~~点击设置背景颜色的div之外的地方,实现隐藏那个div。~~
374 |
375 | ```js
376 | let selectBg = document.querySelector('.bg-btn');
377 | let bgGroup = document.querySelector('.color-group');
378 | let bgcolorBtn = document.querySelectorAll('.bgcolor-item');
379 | let penDetail = document.getElementById("penDetail");
380 | let activeBgColor = '#fff';
381 |
382 |
383 | // 实现了切换背景颜色
384 | for (let i = 0; i < bgcolorBtn.length; i++) {
385 | bgcolorBtn[i].onclick = function (e) {
386 | // 阻止冒泡
387 | e.stopPropagation();
388 | for (let i = 0; i < bgcolorBtn.length; i++) {
389 | bgcolorBtn[i].classList.remove("active");
390 | this.classList.add("active");
391 | activeBgColor = this.style.backgroundColor;
392 | setCanvasBg(activeBgColor);
393 | }
394 | }
395 | }
396 |
397 | document.onclick = function(){
398 | bgGroup.classList.remove('active');
399 | }
400 |
401 | selectBg.onclick = function(e){
402 | bgGroup.classList.add('active');
403 | e.stopPropagation();
404 | }
405 | ```
406 |
407 | #### 7.实现改变画笔粗细的功能
408 |
409 | 实现思路:
410 |
411 | 1. 实现让设置画笔的属性的对话框出现。
412 | 2. 获取相应的元素节点。
413 | 3. 当input=range的元素发生改变的时候,获取到的值赋值给lWidth。
414 | 4. 然后设置`context.lineWidth = lWidth`。
415 |
416 | ```js
417 | let range1 = document.getElementById('range1');
418 | let lWidth = 2;
419 | let ifPop = false;
420 |
421 | range1.onchange = function(){
422 | console.log(range1.value);
423 | console.log(typeof range1.value)
424 | thickness.style.transform = 'scale('+ (parseInt(range1.value)) +')';
425 | console.log(thickness.style.transform )
426 | lWidth = parseInt(range1.value*2);
427 | }
428 |
429 |
430 | // 画线函数
431 | function drawLine(x1,y1,x2,y2){
432 | // ...
433 | context.lineWidth = lWidth;
434 | // ...
435 | }
436 |
437 | // 点击画笔
438 | brush.onclick = function(){
439 | eraserEnabled = false;
440 | brush.classList.add('active');
441 | eraser.classList.remove('active');
442 | if(!ifPop){
443 | // 弹出框
444 | console.log('弹一弹')
445 | penDetail.classList.add('active');
446 | }else{
447 | penDetail.classList.remove('active');
448 | }
449 | ifPop = !ifPop;
450 | }
451 | ```
452 |
453 | #### 8.实现改变画笔颜色的功能
454 |
455 | 实现思路跟**改变画板背景颜色**的思路类似。
456 |
457 | ```js
458 | let aColorBtn = document.getElementsByClassName("color-item");
459 |
460 | getColor();
461 |
462 | function getColor(){
463 | for (let i = 0; i < aColorBtn.length; i++) {
464 | aColorBtn[i].onclick = function () {
465 | for (let i = 0; i < aColorBtn.length; i++) {
466 | aColorBtn[i].classList.remove("active");
467 | this.classList.add("active");
468 | activeColor = this.style.backgroundColor;
469 | ctx.fillStyle = activeColor;
470 | ctx.strokeStyle = activeColor;
471 | }
472 | }
473 | }
474 | }
475 | ```
476 |
477 | #### 9.实现改变撤销和重做的功能
478 |
479 | 实现思路:
480 |
481 | 1. 保存快照:每完成一次绘制操作则保存一份 canvas 快照到 `canvasHistory` 数组(生成快照使用 canvas 的 `toDataURL()` 方法,生成的是 base64 的图片);
482 | 2. 撤销和反撤销:把 `canvasHistory` 数组中对应索引的快照使用 canvas 的 `drawImage()` 方法重绘一遍;
483 | 3. 绘制新图像:执行新的绘制操作时,删除当前位置之后的数组记录,然后添加新的快照。
484 |
485 | ```js
486 | let undo = document.getElementById("undo");
487 | let redo = document.getElementById("redo");
488 |
489 | // ...
490 | canvas.ontouchend = function () {
491 | painting = false;
492 | canvasDraw();
493 | }
494 |
495 | // ...
496 | canvas.onmouseup = function(){
497 | painting = false;
498 | canvasDraw();
499 | }
500 |
501 | let canvasHistory = [];
502 | let step = -1;
503 |
504 | // 绘制方法
505 | function canvasDraw(){
506 | step++;
507 | if(step < canvasHistory.length){
508 | canvasHistory.length = step; // 截断数组
509 | }
510 | // 添加新的绘制到历史记录
511 | canvasHistory.push(canvas.toDataURL());
512 | }
513 |
514 | // 撤销方法
515 | function canvasUndo(){
516 | if(step > 0){
517 | step--;
518 | // ctx.clearRect(0,0,canvas.width,canvas.height);
519 | let canvasPic = new Image();
520 | canvasPic.src = canvasHistory[step];
521 | canvasPic.onload = function () { ctx.drawImage(canvasPic, 0, 0); }
522 | undo.classList.add('active');
523 | }else{
524 | undo.classList.remove('active');
525 | alert('不能再继续撤销了');
526 | }
527 | }
528 | // 重做方法
529 | function canvasRedo(){
530 | if(step < canvasHistory.length - 1){
531 | step++;
532 | let canvasPic = new Image();
533 | canvasPic.src = canvasHistory[step];
534 | canvasPic.onload = function () {
535 | // ctx.clearRect(0,0,canvas.width,canvas.height);
536 | ctx.drawImage(canvasPic, 0, 0);
537 | }
538 | redo.classList.add('active');
539 | }else {
540 | redo.classList.remove('active')
541 | alert('已经是最新的记录了');
542 | }
543 | }
544 | undo.onclick = function(){
545 | canvasUndo();
546 | }
547 | redo.onclick = function(){
548 | canvasRedo();
549 | }
550 | ```
551 |
552 | #### 10.兼容移动端
553 |
554 | 实现思路:
555 |
556 | 1. 判断设备是否支持触摸
557 | 2. `true`,则使用`touch`事件;`false`,则使用`mouse`事件
558 |
559 | ```js
560 | // ...
561 | if (document.body.ontouchstart !== undefined) {
562 | // 使用touch事件
563 | anvas.ontouchstart = function (e) {
564 | // 开始触摸
565 | }
566 | canvas.ontouchmove = function (e) {
567 | // 开始滑动
568 | }
569 | canvas.ontouchend = function () {
570 | // 滑动结束
571 | }
572 | }else{
573 | // 使用mouse事件
574 | // ...
575 | }
576 | // ...
577 | ```
578 |
579 | ## 四、踩坑
580 |
581 | ### 问题1:在电脑上对浏览器的窗口进行改变,画板不会自适应
582 |
583 | 解决办法:
584 |
585 | onresize响应事件处理中,获取到的页面尺寸参数是变更后的参数 。
586 |
587 | 当窗口大小发生改变之后,重新设置canvas的宽高,简单来说,就是窗口改变之后,给canvas.width和canvas.height重新赋值。
588 |
589 | ```js
590 | // 记得要执行autoSetSize这个函数哦
591 | function autoSetSize(){
592 | canvasSetSize();
593 | // 当执行这个函数的时候,会先设置canvas的宽高
594 | function canvasSetSize(){
595 | let pageWidth = document.documentElement.clientWidth;
596 | let pageHeight = document.documentElement.clientHeight;
597 |
598 | canvas.width = pageWidth;
599 | canvas.height = pageHeight;
600 | }
601 | // 在窗口大小改变之后,就会触发resize事件,重新设置canvas的宽高
602 | window.onresize = function(){
603 | canvasSetSize();
604 | }
605 | }
606 | ```
607 |
608 | ### 问题2:当绘制线条宽度比较小的时候还好,一旦比较粗就会出现问题
609 |
610 | 解决办法:看一下文档,得出方法,只需要简单修改一下**绘制线条的代码**就行
611 |
612 | ```js
613 | // 画线函数
614 | function drawLine(x1,y1,x2,y2){
615 | context.beginPath();
616 | context.lineWidth = lWidth;
617 | //-----加入-----
618 | // 设置线条末端样式。
619 | context.lineCap = "round";
620 | // 设定线条与线条间接合处的样式
621 | context.lineJoin = "round";
622 | //-----加入-----
623 | context.moveTo(x1,y1);
624 | context.lineTo(x2,y2);
625 | context.stroke();
626 | context.closePath();
627 | }
628 | ```
629 |
630 | ### 问题3:如何实现圆形的橡皮檫?
631 |
632 | 解决办法:
633 |
634 | canvas的API中,可以清除像素的就是`clearRect`方法,但是`clearRect`方法的清除区域矩形,毕竟大部分人的习惯中的橡皮擦都是圆形的,所以就引入了剪辑区域这个强大的功能,也就是`clip`方法。用法很简单:
635 |
636 | ```js
637 | ctx.save()
638 | ctx.beginPath()
639 | ctx.arc(x2,y2,a,0,2*Math.PI);
640 | ctx.clip()
641 | ctx.clearRect(0,0,canvas.width,canvas.height);
642 | ctx.restore();
643 | ```
644 |
645 | 上面那段代码就实现了圆形区域的擦除,也就是先实现一个圆形路径,然后把这个路径作为剪辑区域,再清除像素就行了。有个注意点就是需要先保存绘图环境,清除完像素后要重置绘图环境,如果不重置的话以后的绘图都是会被限制在那个剪辑区域中。
646 |
647 | ### 问题4:如何兼容移动端?
648 |
649 | #### 1.添加meta标签
650 |
651 | 因为浏览器初始会将页面现在手机端显示时进行缩放,因此我们可以在meta标签中设置meta viewport属性,告诉浏览器不将页面进行缩放,页面宽度=用户设备屏幕宽度
652 |
653 | ```js
654 |
657 |
658 | /*
659 | 页面宽度=移动宽度 :width=device-width
660 | 用户不可以缩放:user-scalable=no
661 | 缩放比例:initial-scale=1
662 | 最大缩放比例:maximum-scale=1.0
663 | 最小缩放比例:minimum-scale=1.0
664 | */
665 | ```
666 |
667 | #### 2.在移动端几乎使用的都是touch事件,与PC端不同
668 |
669 | 由于移动端是触摸事件,所以要用到H5的属性touchstart/touchmove/touchend,但是PC端只支持鼠标事件,所以要进行特性检测。
670 |
671 | 在`touch`事件里,是通过`.touches[0].clientX`和`.touches[0].clientY`来获取坐标的,这点要和`mouse`事件区别开。
672 |
673 | ### 问题5:当浏览器大小变化时,画布被清空
674 | 解决办法1:http://js.jirengu.com/dafic/2/edit
675 |
676 | 解决办法2:http://js.jirengu.com/worus/2/edit
677 |
678 | 参考链接:[canvas长宽变化时,画布内容消失](https://blog.csdn.net/vuturn/article/details/47807899)
679 | ### 问题6:当橡皮擦移动很快时会变成圆点
680 | 参考链接:
681 | [HTML5 实现橡皮擦的擦除效果](https://www.cnblogs.com/axes/p/3850309.html)
682 |
683 | ### 问题7:橡皮擦把背景层都给擦掉了,橡皮擦需要优化
684 | 嗯嗯,这个问题尚未解决,所以我就先把自定义背景颜色的功能取消掉
685 |
686 | ### 问题8:出现一个问题就是清空之后,重新画,然后出现原来的画的东西
687 |
688 | 这个嘛,问题不大,只不过是我漏写context.beginPath(); ,也花了一点时间在上面解决bug,让我想起“代码千万行,注释第一行;编程不规范,同事两行泪 ”,还是按照文档操作规范操作好,真香!!!
689 |
690 | >本文作者 **xyyojl**
691 | >
692 | >本文如有错误之处,请留言,我会及时更正
693 | >
694 | >或者提bug、提需求也是可以的
695 | >
696 | >觉得对您有帮助的话就**点个赞**或**收藏**吧!
697 | >
698 | >*欢迎转载或分享,转载时请注明出处*
699 |
--------------------------------------------------------------------------------
/css/style.css:
--------------------------------------------------------------------------------
1 | *{margin: 0;padding: 0;box-sizing: border-box;}
2 | *::before{box-sizing: border-box;}
3 | *::after{box-sizing: border-box;}
4 | ul{padding: 0; }
5 | li{list-style: none;}
6 | .clearfix::after{
7 | content: '';
8 | display: block;
9 | clear: both;
10 | }
11 | .bg-btn{
12 | position: fixed;
13 | top: 25px;
14 | left: 25px;
15 | width: 50px;
16 | height: 50px;
17 | background: url('../image/ic_droplet1.png') center center no-repeat;
18 | background-size: 30px 30px;
19 | border-radius: 50%;
20 | box-shadow: 0 1px 2px 0 rgba(32,33,36,0.28);
21 | cursor: pointer;
22 | background-color: #fff;
23 | }
24 | .bg-btn.active{
25 | box-shadow: 0 1px 8px 0 rgba(32,33,36,0.28);
26 | }
27 | .tools{
28 | position: fixed;
29 | left: 0;
30 | bottom: 30px;
31 | width: 100%;
32 | height: 50px;
33 | display: flex;
34 | justify-content: center;
35 | text-align: center;
36 | }
37 | .tools .container{
38 | /* height: 70px; */
39 | padding: 8px 20px;
40 | border-radius: 40px;
41 | box-shadow: 0 1px 2px 0 rgba(32,33,36,0.28);
42 | background: #fff;
43 | }
44 | .tools button{
45 | width: 30px;
46 | height: 30px;
47 | border: none;
48 | outline: none;
49 | background-size: 20px 20px;
50 | background-position: center center;
51 | background-repeat: no-repeat;
52 | background-color: #fff;
53 | margin: 0 6px;
54 | transition: 0.3s;
55 | cursor: pointer;
56 | border: 2px solid transparent;
57 | }
58 | .tools button.save{
59 | background-image: url('../image/save.png');
60 | }
61 | .tools button.brush{
62 | background-image: url('../image/pen.png');
63 | }
64 | .tools button.eraser{
65 | background-image: url('../image/eraser.png');
66 | }
67 | .tools button.clear{
68 | background-image: url('../image/clear.png');
69 | }
70 | .tools button.undo{
71 | background-image: url('../image/undo_sel.png');
72 | }
73 | .tools button.redo{
74 | background-image: url('../image/redo_sel.png');
75 | }
76 | .tools button.undo.active{
77 | background-image: url('../image/undo.png');
78 | border-color: transparent;
79 | }
80 | .tools button.redo.active{
81 | background-image: url('../image/redo.png');
82 | border-color: transparent;
83 | }
84 | .tools button.active{
85 | border-radius: 5px;
86 | border-color: #1398E6;
87 | }
88 |
89 | .color-group{
90 | display: none;
91 | position: absolute;
92 | top: 50px;
93 | left: 50%;
94 | margin-left: -161px;
95 | width: 322px;
96 | padding: 16px;
97 | border-radius: 5px;
98 | box-shadow: 0 1px 4px 0 rgba(32,33,36,0.28);
99 | background-color: #fff;
100 | }
101 | .color-group .closeBtn,
102 | .pen-detail .closeBtn{
103 | position: absolute;
104 | top: 6px;
105 | right: 8px;
106 | width: 32px;
107 | height: 32px;
108 | background: url('../image/close.png') center center no-repeat;
109 | cursor: pointer;
110 | }
111 | .color-group.active{
112 | display: block;
113 | }
114 | .color-group h3{
115 | font-weight: 500;
116 | margin-bottom: 8px;
117 | font-size: 18px;
118 | }
119 | .color-group li{
120 | position: relative;
121 | float: left;
122 | list-style: none;
123 | width: 40px;
124 | height: 40px;
125 | border-radius: 50%;
126 | margin: 4px;
127 | cursor: pointer;
128 | }
129 | .color-group li.active::before{
130 | position: absolute;
131 | left: 4px;
132 | top: 4px;
133 | content: '';
134 | display: block;
135 | width: 32px;
136 | height: 32px;
137 | background: transparent;
138 | border: 3px solid #fff;
139 | border-radius: 50%;
140 | }
141 | .pen-detail{
142 | display: none;
143 | position: fixed;
144 | left: 50%;
145 | margin-left: -140px;
146 | bottom: 90px;
147 | width: 280px;
148 | height: 210px;
149 | padding: 20px 24px;
150 | border: 1px solid #81A4BD;
151 | border-radius: 5px;
152 | color: #808FA2;
153 | font-style: 18px;
154 | background: #fff;
155 | font-size: 14px;
156 | }
157 | .pen-detail.active{
158 | display: block;
159 | }
160 | .pen-detail p{
161 | margin-top: 2px;
162 | margin-bottom: 4px;
163 | }
164 | .pen-detail .pen-type li{
165 | float: left;
166 | width: 30px;
167 | height: 30px;
168 | background-size: 28px 28px;
169 | background-position: center center;
170 | background-repeat: no-repeat;
171 | cursor: pointer;
172 | }
173 | .pen-detail .pen-type li.pen{
174 | background-image: url('../image/pen_sel.png');
175 | }
176 | .pen-detail .pen-type li.pen.active{
177 | background-image: url('../image/pen.png');
178 | }
179 | .pen-detail .pen-type li.line{
180 | background-image: url('../image/line_sel.png');
181 | }
182 | .pen-detail .pen-type li.line.active{
183 | background-image: url('../image/line.png');
184 | }
185 | .pen-detail .pen-type li.circle{
186 | border-radius: 50%;
187 | border: 2px solid #CDCDCD;
188 | }
189 | .pen-detail .pen-type li.circle.active,
190 | .pen-detail .pen-type li.rect.active{
191 | border-color: #1F1F1F;
192 | }
193 | .pen-detail .pen-type li.rect{
194 | border-radius: 4px;
195 | border: 2px solid #CDCDCD;
196 | }
197 | .pen-detail .pen-type li + li{
198 | margin-left: 28px;
199 | }
200 |
201 | .circle-box{
202 | line-height: 24px;
203 | }
204 |
205 | .circle-box{
206 | position: relative;
207 | width: 24px;
208 | height: 24px;
209 | display: inline-block;
210 | text-align: center;
211 | margin-right: 8px;
212 | }
213 | #thickness{
214 | position: absolute;
215 | top: 50%;
216 | left: 50%;
217 | margin-left: -1px;
218 | margin-top: -1px;
219 | background: #000;
220 | border-radius: 50%;
221 | transform-origin: center;
222 | width: 2px;
223 | height: 2px;
224 | }
225 | input[type=range]{
226 | -webkit-appearance: none;
227 | width: 180px;
228 | height: 24px;
229 | outline: none;
230 | }
231 | input[type='range']::-webkit-slider-runnable-track{
232 | background-color: #DBDBDB;
233 | height: 4px;
234 | border-radius: 5px;
235 | }
236 | input[type='range']::-webkit-slider-thumb {
237 | -webkit-appearance: none;
238 | /* border: 5px solid #fff; */
239 | width: 12px;
240 | height: 12px;
241 | border-radius: 50%;
242 | background: #FF4081;
243 | cursor: pointer;
244 | margin-top: -4px;
245 | }
246 | .pen-color li{
247 | position: relative;
248 | float: left;
249 | list-style: none;
250 | width: 30px;
251 | height: 30px;
252 | border-radius: 50%;
253 | margin: 4px;
254 | cursor: pointer;
255 | }
256 | .pen-color li.active::before{
257 | position: absolute;
258 | left: 3px;
259 | top: 3px;
260 | content: '';
261 | display: block;
262 | width: 24px;
263 | height: 24px;
264 | background: transparent;
265 | border: 2px solid #fff;
266 | border-radius: 50%;
267 | }
268 | .pen-detail i.showOpacity{
269 | display: inline-block;
270 | width: 26px;
271 | height: 26px;
272 | background: #000;
273 | border-radius: 50%;
274 | margin-right: 4px;
275 | margin-left: 4px;
276 | }
277 | canvas{
278 | display: block;
279 | background: #fff;
280 | }
281 |
282 |
283 | @media screen and (max-width: 768px) {
284 | .color-group{
285 | top: 80px;
286 | }
287 | .tools{
288 | bottom: 15px;
289 | }
290 | .pen-detail{
291 | bottom: 80px;
292 | }
293 | }
--------------------------------------------------------------------------------
/image/PC端.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xyyojl/drawingborad/25ec34e88df857072d3fb801dda5b76f208170c0/image/PC端.png
--------------------------------------------------------------------------------
/image/clear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xyyojl/drawingborad/25ec34e88df857072d3fb801dda5b76f208170c0/image/clear.png
--------------------------------------------------------------------------------
/image/close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xyyojl/drawingborad/25ec34e88df857072d3fb801dda5b76f208170c0/image/close.png
--------------------------------------------------------------------------------
/image/eraser.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xyyojl/drawingborad/25ec34e88df857072d3fb801dda5b76f208170c0/image/eraser.png
--------------------------------------------------------------------------------
/image/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xyyojl/drawingborad/25ec34e88df857072d3fb801dda5b76f208170c0/image/favicon.png
--------------------------------------------------------------------------------
/image/ic_droplet1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xyyojl/drawingborad/25ec34e88df857072d3fb801dda5b76f208170c0/image/ic_droplet1.png
--------------------------------------------------------------------------------
/image/line.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xyyojl/drawingborad/25ec34e88df857072d3fb801dda5b76f208170c0/image/line.png
--------------------------------------------------------------------------------
/image/line_sel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xyyojl/drawingborad/25ec34e88df857072d3fb801dda5b76f208170c0/image/line_sel.png
--------------------------------------------------------------------------------
/image/pen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xyyojl/drawingborad/25ec34e88df857072d3fb801dda5b76f208170c0/image/pen.png
--------------------------------------------------------------------------------
/image/pen_sel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xyyojl/drawingborad/25ec34e88df857072d3fb801dda5b76f208170c0/image/pen_sel.png
--------------------------------------------------------------------------------
/image/redo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xyyojl/drawingborad/25ec34e88df857072d3fb801dda5b76f208170c0/image/redo.png
--------------------------------------------------------------------------------
/image/redo_sel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xyyojl/drawingborad/25ec34e88df857072d3fb801dda5b76f208170c0/image/redo_sel.png
--------------------------------------------------------------------------------
/image/save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xyyojl/drawingborad/25ec34e88df857072d3fb801dda5b76f208170c0/image/save.png
--------------------------------------------------------------------------------
/image/save_sel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xyyojl/drawingborad/25ec34e88df857072d3fb801dda5b76f208170c0/image/save_sel.png
--------------------------------------------------------------------------------
/image/undo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xyyojl/drawingborad/25ec34e88df857072d3fb801dda5b76f208170c0/image/undo.png
--------------------------------------------------------------------------------
/image/undo_sel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xyyojl/drawingborad/25ec34e88df857072d3fb801dda5b76f208170c0/image/undo_sel.png
--------------------------------------------------------------------------------
/image/移动端.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xyyojl/drawingborad/25ec34e88df857072d3fb801dda5b76f208170c0/image/移动端.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 智绘画板
8 |
9 |
10 |
11 |
12 |
13 |
32 |
42 |
43 |
44 |
51 |
笔大小
52 |
53 |
笔颜色
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
不透明度
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/js/main.js:
--------------------------------------------------------------------------------
1 | let canvas = document.getElementById('canvas');
2 | let context = canvas.getContext('2d');
3 | let eraser = document.getElementById('eraser');
4 | let brush = document.getElementById('brush');
5 | let reSetCanvas = document.getElementById("clear");
6 | let save = document.getElementById("save");
7 | /* let selectBg = document.querySelector('.bg-btn');
8 | let bgGroup = document.querySelector('.color-group');
9 | let bgcolorBtn = document.querySelectorAll('.bgcolor-item'); */
10 | let penDetail = document.getElementById("penDetail");
11 | let aColorBtn = document.getElementsByClassName("color-item");
12 | let undo = document.getElementById("undo");
13 | let redo = document.getElementById("redo");
14 |
15 |
16 | let range1 = document.getElementById('range1');
17 | let range2 = document.getElementById('range2');
18 | let showOpacity = document.querySelector('.showOpacity');
19 | let closeBtn = document.querySelectorAll('.closeBtn');
20 | let eraserEnabled = false;
21 | let activeBgColor = '#fff';
22 | let ifPop = false;
23 | let lWidth = 2;
24 | let opacity = 1;
25 | let strokeColor = 'rgba(0,0,0,1)';
26 | let radius = 5;
27 |
28 |
29 | autoSetSize();
30 |
31 | setCanvasBg('white');
32 |
33 | listenToUser();
34 |
35 |
36 | /* 下面是实现相关效果的函数,可以不用看 */
37 |
38 | function autoSetSize(){
39 | canvasSetSize();
40 | function canvasSetSize(){
41 | // 把变化之前的画布内容copy一份,然后重新画到画布上
42 | let imgData = context.getImageData(0,0,canvas.width,canvas.height);
43 | let pageWidth = document.documentElement.clientWidth;
44 | let pageHeight = document.documentElement.clientHeight;
45 |
46 | canvas.width = pageWidth;
47 | canvas.height = pageHeight;
48 | context.putImageData(imgData,0,0);
49 | }
50 |
51 | window.onresize = function(){
52 | canvasSetSize();
53 | }
54 | }
55 |
56 | // 监听用户鼠标事件
57 | function listenToUser() {
58 | // 定义一个变量初始化画笔状态
59 | let painting = false;
60 | // 记录画笔最后一次的位置
61 | let lastPoint = {x: undefined, y: undefined};
62 |
63 | if(document.body.ontouchstart !== undefined){
64 | canvas.ontouchstart = function (e) {
65 | painting = true;
66 | let x1 = e.touches[0].clientX;
67 | let y1 = e.touches[0].clientY;
68 | if(eraserEnabled){//要使用eraser
69 | context.save();
70 | context.globalCompositeOperation = "destination-out";
71 | context.beginPath();
72 | radius = (lWidth/2) > 5? (lWidth/2) : 5;
73 | context.arc(x1,y1,radius,0,2*Math.PI);
74 | context.clip();
75 | context.clearRect(0,0,canvas.width,canvas.height);
76 | context.restore();
77 | lastPoint = {'x': x1,'y': y1}
78 | }else{
79 | lastPoint = {'x': x1,'y': y1}
80 | }
81 | };
82 | canvas.ontouchmove = function (e) {
83 | let x1 = lastPoint['x'];
84 | let y1 = lastPoint['y'];
85 | let x2 = e.touches[0].clientX;
86 | let y2 = e.touches[0].clientY;
87 | if(!painting){return}
88 | if(eraserEnabled){
89 | moveHandler(x1,y1,x2,y2);
90 | //记录最后坐标
91 | lastPoint['x'] = x2;
92 | lastPoint['y'] = y2;
93 | }else{
94 | let newPoint = {'x': x2,'y': y2};
95 | drawLine(lastPoint.x, lastPoint.y,newPoint.x, newPoint.y);
96 | lastPoint = newPoint;
97 | }
98 | };
99 |
100 | canvas.ontouchend = function () {
101 | painting = false;
102 | canvasDraw();
103 | }
104 | }else{
105 | // 鼠标按下事件
106 | canvas.onmousedown = function(e){
107 | painting = true;
108 | let x1 = e.clientX;
109 | let y1 = e.clientY;
110 | if(eraserEnabled){//要使用eraser
111 | //鼠标第一次点下的时候擦除一个圆形区域,同时记录第一个坐标点
112 | context.save();
113 | context.globalCompositeOperation = "destination-out";
114 | context.beginPath();
115 | radius = (lWidth/2) > 5? (lWidth/2) : 5;
116 | context.arc(x1,y1,radius,0,2*Math.PI);
117 | context.clip();
118 | context.clearRect(0,0,canvas.width,canvas.height);
119 | context.restore();
120 | lastPoint = {'x': x1,'y': y1}
121 | }else{
122 | lastPoint = {'x': x1,'y': y1}
123 | }
124 | }
125 |
126 | // 鼠标移动事件
127 | canvas.onmousemove = function(e){
128 | let x1 = lastPoint['x'];
129 | let y1 = lastPoint['y'];
130 | let x2 = e.clientX;
131 | let y2 = e.clientY;
132 | if(!painting){return}
133 | if(eraserEnabled){
134 | moveHandler(x1,y1,x2,y2);
135 | //记录最后坐标
136 | lastPoint['x'] = x2;
137 | lastPoint['y'] = y2;
138 | }else{
139 | let newPoint = {'x': x2,'y': y2};
140 | drawLine(lastPoint.x, lastPoint.y,newPoint.x, newPoint.y);
141 | lastPoint = newPoint;
142 | }
143 | }
144 |
145 | // 鼠标松开事件
146 | canvas.onmouseup = function(){
147 | painting = false;
148 | canvasDraw();
149 | }
150 | }
151 |
152 |
153 |
154 | }
155 |
156 | //
157 | function moveHandler(x1,y1,x2,y2){
158 | //获取两个点之间的剪辑区域四个端点
159 | var asin = radius*Math.sin(Math.atan((y2-y1)/(x2-x1)));
160 | var acos = radius*Math.cos(Math.atan((y2-y1)/(x2-x1)))
161 | var x3 = x1+asin;
162 | var y3 = y1-acos;
163 | var x4 = x1-asin;
164 | var y4 = y1+acos;
165 | var x5 = x2+asin;
166 | var y5 = y2-acos;
167 | var x6 = x2-asin;
168 | var y6 = y2+acos;
169 |
170 | //保证线条的连贯,所以在矩形一端画圆
171 | context.save()
172 | context.beginPath()
173 | context.globalCompositeOperation = "destination-out";
174 | radius = (lWidth/2) > 5? (lWidth/2) : 5;
175 | context.arc(x2,y2,radius,0,2*Math.PI);
176 | context.clip()
177 | context.clearRect(0,0,canvas.width,canvas.height);
178 | context.restore();
179 |
180 | //清除矩形剪辑区域里的像素
181 | context.save()
182 | context.beginPath()
183 | context.globalCompositeOperation = "destination-out";
184 | context.moveTo(x3,y3);
185 | context.lineTo(x5,y5);
186 | context.lineTo(x6,y6);
187 | context.lineTo(x4,y4);
188 | context.closePath();
189 | context.clip();
190 | context.clearRect(0,0,canvas.width,canvas.height);
191 | context.restore();
192 | }
193 |
194 |
195 | // 画线函数
196 | function drawLine(x1,y1,x2,y2){
197 | context.beginPath();
198 | context.lineWidth = lWidth;
199 | // context.strokeStyle = strokeColor;
200 | // context.globalAlpha = opacity;
201 | // 设置线条末端样式。
202 | context.lineCap = "round";
203 | // 设定线条与线条间接合处的样式
204 | context.lineJoin = "round";
205 | context.moveTo(x1,y1);
206 | context.lineTo(x2,y2);
207 | context.stroke();
208 | context.closePath();
209 | }
210 |
211 | // 点击橡皮檫
212 | eraser.onclick = function(){
213 | eraserEnabled = true;
214 | eraser.classList.add('active');
215 | brush.classList.remove('active');
216 | }
217 | // 点击画笔
218 | brush.onclick = function(){
219 | eraserEnabled = false;
220 | brush.classList.add('active');
221 | eraser.classList.remove('active');
222 | if(!ifPop){
223 | // 弹出框
224 | penDetail.classList.add('active');
225 | }else{
226 | penDetail.classList.remove('active');
227 | }
228 | ifPop = !ifPop;
229 | }
230 |
231 | // 实现清屏
232 | reSetCanvas.onclick = function(){
233 | context.clearRect(0,0,canvas.width,canvas.height);
234 | setCanvasBg('white');
235 | canvasHistory = [];
236 | undo.classList.remove('active');
237 | redo.classList.remove('active');
238 | }
239 |
240 | // 重新设置canvas背景颜色
241 | function setCanvasBg(color) {
242 | context.fillStyle = color;
243 | context.fillRect(0, 0, canvas.width, canvas.height);
244 | }
245 |
246 | // 下载图片
247 | save.onclick = function(){
248 | let imgUrl = canvas.toDataURL('image/png');
249 | let saveA = document.createElement('a');
250 | document.body.appendChild(saveA);
251 | saveA.href = imgUrl;
252 | saveA.download = 'mypic'+(new Date).getTime();
253 | saveA.target = '_blank';
254 | saveA.click();
255 | }
256 |
257 |
258 | // 实现了切换背景颜色
259 | /* for (let i = 0; i < bgcolorBtn.length; i++) {
260 | bgcolorBtn[i].onclick = function (e) {
261 | e.stopPropagation();
262 | for (let i = 0; i < bgcolorBtn.length; i++) {
263 | bgcolorBtn[i].classList.remove("active");
264 | this.classList.add("active");
265 | activeBgColor = this.style.backgroundColor;
266 | setCanvasBg(activeBgColor);
267 | }
268 |
269 | }
270 | }
271 | document.onclick = function(){
272 | bgGroup.classList.remove('active');
273 | }
274 |
275 | selectBg.onclick = function(e){
276 | bgGroup.classList.add('active');
277 | e.stopPropagation();
278 | } */
279 |
280 | // 实现改变画笔粗细的功能 1-20 放大的倍数 1 10 实际大小呢? 2-20
281 |
282 | range1.onchange = function(){
283 | thickness.style.transform = 'scale('+ (parseInt(range1.value)) +')';
284 | lWidth = parseInt(range1.value*2);
285 | }
286 |
287 | range2.onchange = function(){
288 | opacity = 1 - parseInt(this.value)/10;
289 | if(opacity !== 0){
290 | showOpacity.style.opacity = opacity;
291 | }
292 | }
293 |
294 | // 改变画笔颜色
295 | getColor();
296 |
297 | function getColor(){
298 | for (let i = 0; i < aColorBtn.length; i++) {
299 | aColorBtn[i].onclick = function (e) {
300 | // e.stopPropagation();
301 | for (let i = 0; i < aColorBtn.length; i++) {
302 | aColorBtn[i].classList.remove("active");
303 | this.classList.add("active");
304 | activeColor = this.style.backgroundColor;
305 | context.fillStyle = activeColor;
306 | context.strokeStyle = activeColor;
307 | }
308 | penDetail.classList.remove('active');
309 | ifPop = false;
310 | }
311 | }
312 | }
313 |
314 | // 实现撤销和重做的功能
315 | let canvasHistory = [];
316 | let step = -1;
317 |
318 | // 绘制方法
319 | function canvasDraw(){
320 | step++;
321 | if(step < canvasHistory.length){
322 | canvasHistory.length = step; // 截断数组
323 | }
324 | // 添加新的绘制到历史记录
325 | canvasHistory.push(canvas.toDataURL());
326 | if(step > 0){
327 | undo.classList.add('active');
328 | }
329 | }
330 |
331 | // 撤销方法
332 | function canvasUndo(){
333 | if(step > 0){
334 | step--;
335 | let canvasPic = new Image();
336 | canvasPic.src = canvasHistory[step];
337 | canvasPic.onload = function () { context.drawImage(canvasPic, 0, 0); }
338 | undo.classList.add('active');
339 | redo.classList.add('active');
340 | }else{
341 | undo.classList.remove('active');
342 | alert('不能再继续撤销了');
343 | }
344 | }
345 | // 重做方法
346 | function canvasRedo(){
347 | if(step < canvasHistory.length - 1){
348 | step++;
349 | let canvasPic = new Image();
350 | canvasPic.src = canvasHistory[step];
351 | canvasPic.onload = function () {
352 | context.drawImage(canvasPic, 0, 0);
353 | }
354 | // redo.classList.add('active');
355 | }else {
356 | redo.classList.remove('active')
357 | alert('已经是最新的记录了');
358 | }
359 | }
360 | undo.onclick = function(){
361 | canvasUndo();
362 | }
363 | redo.onclick = function(){
364 | canvasRedo();
365 | }
366 |
367 |
368 |
369 | for (let index = 0; index < closeBtn.length; index++) {
370 | closeBtn[index].onclick = function(e){
371 | let btnParent = e.target.parentElement;
372 | btnParent.classList.remove('active');
373 | }
374 |
375 | }
376 |
377 | window.onbeforeunload = function(){
378 | return "Reload site?";
379 | };
380 |
--------------------------------------------------------------------------------