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.