Xu-Blog

养一猫一狗,猫叫啵啵,狗叫没想好~~

0%

canvas绘图写字

内容借鉴慕课网 学写一个字,老师@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) {
// 窗口坐标系转换为canvas坐标系
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) {
// console.log('mouse move')
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);

// TODO: draw
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();
}
End~~请支持原创撒花= ̄ω ̄=花撒
如果您读文章后有收获,可以打赏我喝咖啡哦~