JS - Canvas 画板

做一个画板

  1. 查看 Canvas MDN 文档 https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API
  2. 做一个 Demo 理解 mouse 事件
  3. 再做一个 Demo 理解 event 对象
  4. 再做一个 Demo ……

遇到的 BUG

如果你发现 canvas 右边总是多出一个滚动条,你就加一句 CSS canvas {display: block;}

代码

canvas

html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
<title>JS Bin</title>
</head>

<body>
<canvas id="xxx" width=300 height=300></canvas>
<div id=actions class="actions">
<button id=eraser>橡皮擦</button>
<button id=brush>画笔</button>
</div>

</body>

</html>

css

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
#xxx{
background: green;
display: block;
}

body{
margin: 0px;
}

html{
}

.actions{
position: fixed;
bottom: 0;
left: 0;
}
.actions > #brush{
display: none;
}

.actions.x > #brush{
display: inline-block;
}
.actions.x > #eraser{
display: none
}

JS(JS要放在页面最下方,不要放在 head 里)

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
var yyy = document.getElementById('xxx');
var context = yyy.getContext('2d');

autoSetCanvasSize(yyy)

listenToMouse(yyy)


var eraserEnabled = false
eraser.onclick = function() {
eraserEnabled =true
actions.className = 'actions x'

}
brush.onclick = function(){
eraserEnabled = false
actions.className = 'actions'
}


/******/

function autoSetCanvasSize(canvas) {
setCanvasSize()

window.onresize = function() {
setCanvasSize()
}

function setCanvasSize() {
var pageWidth = document.documentElement.clientWidth
var pageHeight = document.documentElement.clientHeight

canvas.width = pageWidth
canvas.height = pageHeight
}
}

function drawCircle(x, y, radius) {
context.beginPath()
context.fillStyle = 'black'
context.arc(x, y, radius, 0, Math.PI * 2);
context.fill()
}

function drawLine(x1, y1, x2, y2) {
context.beginPath();
context.strokeStyle = 'black'
context.moveTo(x1, y1) // 起点
context.lineWidth = 5
context.lineTo(x2, y2) // 终点
context.stroke()
context.closePath()
}

function listenToMouse(canvas) {


var using = false
var lastPoint = {
x: undefined,
y: undefined
}
canvas.onmousedown = function(aaa) {
var x = aaa.clientX
var y = aaa.clientY
using = true
if (eraserEnabled) {
context.clearRect(x - 5, y - 5, 10, 10)
} else {
lastPoint = {
"x": x,
"y": y
}
}
}
canvas.onmousemove = function(aaa) {
var x = aaa.clientX
var y = aaa.clientY

if (!using) {return}

if (eraserEnabled) {
context.clearRect(x - 5, y - 5, 10, 10)
} else {
var newPoint = {
"x": x,
"y": y
}
drawLine(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y)
lastPoint = newPoint
}

}
canvas.onmouseup = function(aaa) {
using = false
}
}

进阶代码

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
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
<!DOCTYPE html>
<html lang="zh-Hans">
<head>
<!-- <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scalable=1.0, minimum-scalable=1.0"> -->
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0">
<meta charset="UTF-8">
<title>Hello, My Canvas</title>
<link rel="stylesheet" href="./style.css">
<script src="//at.alicdn.com/t/font_1082445_lrj6o5x5vs.js"></script>
</head>
<body>
<canvas id='xxx' width=300 height=300></canvas>
<div id=actions class="actions">
<svg id=pen class="active icon">
<use xlink:href="#icon-pen"></use>
</svg>
<svg id=eraser class="icon">
<use xlink:href="#icon-eraser"></use>
</svg>
<svg id=clear class="icon">
<use xlink:href="#icon-clear"></use>
</svg>
<svg id=download class="icon">
<use xlink:href="#icon-save"></use>
</svg>
</div>
<ol class="colors">
<li id=black class="black active"></li>
<li id=red class="red"></li>
<li id=green class="green"></li>
<li id=blue class="blue"></li>
</ol>
<ol class="sizes">
<li id="thin" class="thin"></li>
<li id="thick" class="thick"></li>
</ol>
<script src="./main.js"></script>
</body>
<!-- 最开始的js
<script>
var yyy = document.getElementById('xxx');
var context = yyy.getContext('2d');
init();
window.onresize = function(){
init();
}
function init(){
var pageWidth = document.documentElement.clientWidth;
var pageHeight = document.documentElement.clientHeight;
yyy.width = pageWidth;
yyy.height = pageHeight;
}
/*********上方为初始化canvas宽高************* */
/*
context.fillStyle = 'red';
context.fillRect(10, 10, 100, 100);
绘制矩形的描边,可以先描边在填充,也可以先填充再描边
stroke表示只描边,不填充
fill才进行填充
context.strokeStyle = 'blue'
context.strokeRect(10, 10, 100, 100);
//清除一部分区域,橡皮擦;
context.clearRect(50, 50, 10, 10);/*50为位置,10为范围 */
/*
context.fillStyle = 'blue';
context.beginPath();//开始绘制三角形
context.moveTo(240, 240);
context.lineTo(300, 240);
context.lineTo(300, 300);
context.fill();*/
var using = false;
var lastPoint = {
x: undefined,
y: undefined
};
yyy.onmousedown = function(random){
let x = random.clientX;
//这里的x,y是相对于视口的位置
let y = random.clientY;
if(eraserEnabled){
using = true;
context.clearRect(x - 5, y - 5, 10, 10);
} else {
using = true;
lastPoint = {'x': x, 'y': y};
// drawCircle(x, y, 1);
}
};
yyy.onmousemove = function(random){
let x = random.clientX;
let y = random.clientY;
if(eraserEnabled){
if(using){//检查是否在使用橡皮擦
context.clearRect(x - 5, y - 5, 10, 10);
}
} else {
if(using){
let newPoint = {'x': x, 'y': y};
// drawCircle(x, y, 1);
drawLine(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y);
lastPoint = newPoint;
}
}
};
yyy.onmouseup = function(random){
using = false;
};
/*
// drawCircle(150, 150, 50);
function drawCircle(x ,y, radius){
context.beginPath();
context.fillStyle = 'black';
context.arc(x, y, radius, 0, Math.PI*2);
context.fill();
}*/
function drawLine(x1, y1, x2, y2){
context.beginPath();
context.strokeStyle = 'black';
context.moveTo(x1, y1);//起点
// context.lineWidth = 5;
context.lineTo(x2, y2);//终点
context.stroke();
context.closePath();
};
/*//画线
context.beginPath();
context.moveTo(30, 30);//起点
context.lineWidth = 5;
context.lineTo(200, 30);//终点
context.stroke();
context.closePath();*/
/************************ */
var eraserEnabled = false;
eraser.onclick = function(){
eraserEnabled = !eraserEnabled;
};
</script> -->
</html>

js

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
//1、初始化canvas宽高
var yyy = document.getElementById('xxx');
var context = yyy.getContext('2d');
lineWidth = 5;

atuoSetCanvasSize(yyy);

/*****/
//2、进行操作
listenToUser(yyy);

//3、使用橡皮擦
var eraserEnabled = false;
pen.onclick = function(){
eraserEnabled = false;
pen.classList.add('active');
eraser.classList.remove('active');
};
eraser.onclick = function(){
eraserEnabled = true;
eraser.classList.add('active');
pen.classList.remove('active');
};
clear.onclick = function(){
context.clearRect(0, 0, yyy.width, yyy.height);
};
download.onclick = function(){
var url = yyy.toDataURL("image/png");
console.log(url);
var a = document.createElement('a');
document.body.appendChild(a);
a.href = url;
a.download = '我的画儿';
a.target = '_blank';
a.click();
};
black.onclick = function(){
context.strokeStyle = 'black';
black.classList.add('active');
red.classList.remove('active');
green.classList.remove('active');
blue.classList.remove('active');
};
red.onclick = function(){
context.strokeStyle = 'red';
red.classList.add('active');
black.classList.remove('active');
green.classList.remove('active');
blue.classList.remove('active');
};
green.onclick = function(){
context.strokeStyle = 'green';
green.classList.add('active');
black.classList.remove('active');
blue.classList.remove('active');
red.classList.remove('active');
};
blue.onclick = function(){
context.strokeStyle = 'blue';
blue.classList.add('active');
black.classList.remove('active');
red.classList.remove('active');
green.classList.remove('active');
};
thin.onclick = function(){
lineWidth = 5;
}
thick.onclick = function(){
lineWidth = 8;
}
/****************** */

function atuoSetCanvasSize(canvas){
setCanvasSize();

window.onresize = function(){
setCanvasSize();
}

function setCanvasSize(){
var pageWidth = document.documentElement.clientWidth;
var pageHeight = document.documentElement.clientHeight;
canvas.width = pageWidth;
canvas.height = pageHeight;
}
}

function drawLine(x1, y1, x2, y2){
context.beginPath();
context.moveTo(x1, y1);//起点
context.lineWidth = lineWidth;
context.lineTo(x2, y2);//终点
context.stroke();
context.closePath();
};

function listenToUser(canvas){
var using = false;
var lastPoint = {
x: undefined,
y: undefined
};
//特性检测
if(document.body.ontouchstart !== undefined){
//触屏设备
canvas.ontouchstart = function(random){
let x = random.touches[0].clientX;
let y = random.touches[0].clientY;
using = true;
if(eraserEnabled){
context.clearRect(x - 5, y - 5, 10, 10);
} else {
lastPoint = {'x': x, 'y': y};
}
};
canvas.ontouchmove = function(random){
let x = random.touches[0].clientX;
let y = random.touches[0].clientY;
if(!using){return;}
if(eraserEnabled){
context.clearRect(x - 5, y - 5, 10, 10);
} else {
let newPoint = {'x': x, 'y': y};
drawLine(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y);
lastPoint = newPoint;
}
};
canvas.ontouchend = function(){
using = false;
};
} else {
//非触屏设备
canvas.onmousedown = function(random){
let x = random.clientX;
//这里的x,y是相对于视口的位置
let y = random.clientY;
using = true;
if(eraserEnabled){
context.clearRect(x - 5, y - 5, 10, 10);
} else {
lastPoint = {'x': x, 'y': y};
}
};
canvas.onmousemove = function(random){
let x = random.clientX;
let y = random.clientY;
//检查是否在使用橡皮擦
if(!using){return;}
if(eraserEnabled){
context.clearRect(x - 5, y - 5, 10, 10);
} else {
let newPoint = {'x': x, 'y': y};
drawLine(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y);
lastPoint = newPoint;
}
};
canvas.onmouseup = function(random){
using = false;
};
}
}

css

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
ul, ol{
list-style: none;
}
*{
margin: 0;
padding: 0;
}
.icon {
width: 1em; height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
#xxx{
/*最初的颜色设置 background-color: bisque; */
background-color: transparent;
display: block;
position: fixed;
top: 0;
left: 0;
}
body{
overflow: hidden;
}
.actions{
position: fixed;
top: 0;
left: 0;
padding: 20px;
}
.actions svg{
width: 1.5em;
height: 1.5em;
transition: all 0.3s;
margin: 0 10px;
}
.actions svg.active{
fill: red;
transform: scale(1.2);
}
.colors{
position: fixed;
top: 60px;
left: 28px;
}
.colors li{
width: 20px;
height: 20px;
border-radius: 50%;
box-shadow: 0 0 3px rgba(0, 0, 0, 0.25);
margin: 10px 0;
transition: all 0.3s;
}
.colors li.black{
background-color: black;
}
.colors li.red{
background-color: red;
}
.colors li.green{
background-color: green;
}
.colors li.blue{
background-color: blue;
}
.colors li.active{
box-shadow: 0 0 3px rgba(0, 0, 0, 0.95);
transform: scale(1.2);
transition: all 0.5s;
}
.sizes{
position: fixed;
right: 20px;
top: 10px;
}
.sizes li{
margin: 20px 0;
}
.sizes .thin{
height: 0;
width: 20px;
border-top: 3px solid black;
}
.sizes .thick{
height: 0;
width: 20px;
border-top: 4px solid black;
}
编辑