I believe that everyone should have encountered such a need when learning canvas or using canvas in project development: to implement a sketchpad gadget that can be written.
Well, I believe that for children who are more familiar with canvas, this can be done with just a few dozen lines of code. The following demo is a simple example:
<!DOCTYPE html><html><head> <title>Sketchpad demo</title> <style type=text/css> canvas { border: 1px blue solid; } </style></head><body> <canvas id=canvas width=800 height=500></canvas> <script type=text/javascript> let isDown = false; let beginPoint = null; const canvas = document.querySelector('#canvas'); const ctx = canvas.getContext('2d'); // Set line color ctx.strokeStyle = 'red'; ctx.lineWidth = 1; ctx.lineJoin = 'round'; ctx .lineCap = 'round'; canvas.addEventListener('mousedown', down, false); canvas.addEventListener('mousemove', move, false); canvas.addEventListener('mouseup', up, false); canvas.addEventListener('mouseout', up, false); function down(evt) { isDown = true; beginPoint = getPos(evt); } function move(evt) { if (!isDown) return; const endPoint = getPos(evt); drawLine(beginPoint, endPoint); beginPoint = endPoint; } function up(evt) { if (!isDown) return; const endPoint = getPos(evt); drawLine(beginPoint, endPoint); beginPoint = null; isDown = false; } function getPos(evt) { return { x: evt.clientX, y: evt.clientY } } function drawLine(beginPoint, endPoint) { ctx.beginPath(); ctx.moveTo(beginPoint.x, beginPoint.y); ctx.lineTo(endPoint.x, endPoint.y); ctx.stroke(); ctx.closePath(); } </script></body></html >
Its implementation logic is also very simple:
mousedown
, mouseup
and mousemove
, and we also create an isDown
variable;mousedown
), set isDown
to true
, and when the user puts down the mouse ( mouseup
), set it to false
. The advantage of this is that it can determine whether the user is currently in a drawing state;mousemove
event. If and only if isDown
is true
(that is, in the writing state), the current point will be connected and drawn with the previous point through the lineTo
method of the canvas;Through the above steps, we can realize the basic drawing board function. However, things are not that simple. Careful children's shoes may find a very serious problem - the lines drawn in this way are jagged and not smooth enough, and you The faster you draw, the stronger the sense of broken lines will be. The performance is shown below:
Why is this happening? Problem analysisThe main reasons for this phenomenon are:
We connect the points using the lineTo
method of canvas. What connects two adjacent points is a straight line, not a curve, so what is drawn in this way is a polyline;
Limited by the browser's collection frequency of mousemove
events, everyone knows that during mousemove
, the browser collects the coordinates of the current mouse every short period of time. Therefore, the faster the mouse moves, the distance between the two adjacent points collected is The further away, the more obvious the sense of folding lines;
There are actually ways to draw smooth curves. If lineTo
is unreliable, we can use another drawing API of canvas - quadraticCurveTo
, which is used to draw quadratic Bezier curves.
quadratic bezier curve
quadraticCurveTo(cp1x, cp1y, x, y)
Calling quadraticCurveTo
method requires four parameters. cp1x
and cp1y
describe the control points, while x
and y
are the end points of the curve:
More detailed information can be found on MDN
Since we want to use a Bezier curve, it is obvious that our data is not enough. To completely describe a quadratic Bezier curve, we need: starting point, control point and end point. Where do these data come from?
There is a very clever algorithm that can help us obtain this information
Algorithm for obtaining quadratic Bezier key pointsThis algorithm is not difficult to understand. Let me give an example directly:
Suppose we collect a total of 6 mouse coordinates in a painting, namely A, B, C, D, E, F
; take the previous three points A, B, C
, calculate the midpoint B1
of B
and C
, and use A
is the starting point, B
is the control point, B1
is the end point. Use quadraticCurveTo
to draw a quadratic Bezier curve segment;
Next, calculate the midpoint C1
between points C
and D
, and continue to draw the curve with B1
as the starting point, C
as the control point, and C1
as the end point;
The drawing continues by analogy. When the last point F
is reached, the Bezier curve is ended with D1
, the midpoint of D
and E
, as the starting point, E
as the control point, and F
as the end point.
OK, the algorithm is like this, then we will upgrade the existing code based on this algorithm:
let isDown = false;let points = [];let beginPoint = null;const canvas = document.querySelector('#canvas');const ctx = canvas.getContext('2d');//Set the line color ctx.strokeStyle = 'red';ctx.lineWidth = 1;ctx.lineJoin = 'round';ctx.lineCap = 'round';canvas.addEventListener('mousedown', down, false);canvas.addEventListener('mousemove', move, false);canvas.addEventListener('mouseup', up, false);canvas.addEventListener('mouseout' , up, false); function down(evt) { isDown = true; const { x, y } = getPos(evt); points.push({x, y}); beginPoint = {x, y};}function move(evt) { if (!isDown) return; const { x, y} = getPos(evt); points.push({ x, y}); if (points.length > 3) { const lastTwoPoints = points.slice(-2); const controlPoint = lastTwoPoints[0]; const endPoint = { x: (lastTwoPoints[0].x + lastTwoPoints[1].x) / 2, y: (lastTwoPoints[0].y + lastTwoPoints[1].y) / 2, } drawLine(beginPoint, controlPoint, endPoint); beginPoint = endPoint; }}function up(evt) { if (!isDown) return; const { x, y } = getPos(evt); points.push({x, y}); if (points.length > 3) { const lastTwoPoints = points.slice(-2); const controlPoint = lastTwoPoints[0]; const endPoint = lastTwoPoints[1]; drawLine(beginPoint , controlPoint, endPoint); } beginPoint = null; isDown = false; points = [];}function getPos(evt) { return { x: evt.clientX, y: evt.clientY }}function drawLine(beginPoint, controlPoint, endPoint) { ctx.beginPath(); ctx.moveTo(beginPoint.x, beginPoint.y); ctx.quadraticCurveTo(controlPoint.x, controlPoint. y, endPoint.x, endPoint.y); ctx.stroke(); ctx.closePath();}
On the basis of the original, we created a variable points
to save the points that the mouse passed through in the previous mousemove
event. According to this algorithm, it takes at least 3 points to draw a quadratic Bezier curve, so we only have the points in points
Drawing starts only when the number of points is greater than 3. The subsequent processing is exactly the same as this algorithm, so I won’t go into details here.
After the code update, our curve has become much smoother, as shown in the figure below:
This article ends here. I hope you all have fun drawing in canvas~ See you next time:)
If you are interested in children's shoes, you can click here to follow my blog. Any new and interesting blog posts will be shared here as soon as possible~
The above is the entire content of this article. I hope it will be helpful to everyone’s study. I also hope everyone will support VeVb Wulin Network.