preface
-
This paper makes some improvements on the basis of canvas’s function.
-
You can learn about Canvas through the Canvas_API.
-
The code uses ES6 syntax directly and works as expected on The Google browser (version 76.0.3809.100).
The implementation process
Add elements
Add action buttons and canvas elements to the HTML.
<div id="operations">
<input type="button" id="pencil" value="Pencil"/>
<input type="button" id="straightLine" value="Linear"/>
<input type="button" id="rectangle" value="Rectangle"/>
<input type="button" id="solidRectangle" value="Solid rectangle"/>
<input type="button" id="circle" value="Circular"/>
<input type="button" id="solidCircle" value="Solid circle"/>
<input type="button" id="eraser" value="Eraser"/>
<input type="button" id="image" value="Import picture"/>
<input type="button" id="save" value="Save"/>
<input type="button" id="redo" value="Repeat"/>
<input type="button" id="undo" value="Cancel"/>
<input type="button" id="clear" value="Clear"/>
<label>Color:<input type="color" id="color" /></label>
<label>Line thickness: 1<input type="range" id="lineWidth" min="1" max="100" value="1"/>100</label>
<input type="file" id="imageFile" name="image"/>
<a id="downloadLink"></a>
</div>
<div class="canvas-container">
<canvas id="canvas" width="800" height="800"></canvas>
</div>
Copy the code
Adding processing Events
A class called Draw is created where the handleOperations property is used to place the button’s handler. The handleMousemove property is used to handle what needs to be done in mousemove if different types (such as lines and pencils) are selected.
class Draw { constructor (elements) { const { canvas, color, lineWidth, operations, imageFile, downloadLink } = elements; This. type = 'pencil'; This. canvas = canvas; // Canvas element this.context = Canvas. GetContext ('2d'); // Get canvas's 2D context object... },... handleOperations () { ... } handleMousemove () { ... }Copy the code
The click event of the binding element
The click events of the first few buttons set the Type property of the Draw instance.
clear
The “clear” event fills the canvas with the background color.image
Event simulationtype
forfile
theinput
Box click throughFileReader
Object toinput
Box for the selected filebase64
Address and usedrawImage
I’m going to draw it tocanvas
On.save
Events throughtoDataURL
Method to get the current canvasbase64
Address, and seta
Of the labeldownload
Property of the calling elementclick()
Method to simulate a click to download the file.redo
Events andundo
The idea of the event is similar, both are saved by getting inhistoryUrls
In thebase64
Address, callcanvas
thedrawImage
Method to draw an image to the Canvas. Only one is to take forward (undo
), one is to take back (redo
).
handleOperations () {
return {
pencil: () = > { this.type = 'pencil'; }, // Pencil button binding event
straightLine: () = > { this.type = 'straightLine'; }, // The line button is bound to the event
rectangle: () = > { this.type = 'rectangle'; }, // The rectangle button is bound to the event
solidRectangle: () = > { this.type = 'solidRectangle'; }, // Solid rectangle button binding event
eraser: () = > { this.type = 'eraser'; }, // Eraser bound events
circle: () = > { this.type = 'circle'; }, // Round button binding event
solidCircle: () = > { this.type = 'solidCircle'; }, // Solid circle button binding event
clear: () = > { this.clear(); }, // Clear the events bound to the button
image: () = > { // Import the image button binding event
this.imageFile.click();
this.imageFile.onchange = (event) = > {
let reader = new FileReader();
reader.readAsDataURL(event.target.files[0]);
reader.onload = (evt) = > {
let img = new Image();
img.src = evt.target.result;
img.onload = () = > {
this.context.drawImage(img, 0.0); // Paint the picture on the canvas
this.addHistory(); }; }}},save: () = > { // Save the event bound to the button
this.downloadLink.href = this.canvas.toDataURL('image/png');
this.downloadLink.download = 'drawing.png';
this.downloadLink.click();
},
redo: () = > { // Redo the event bound to the button
let length = this.historyUrls.length;
let currentIndex = this.currentHistoryIndex + 1;
if (currentIndex > length - 1 ) {
this.currentHistoryIndex = length - 1;
return;
};
this.currentHistoryIndex = currentIndex;
this.historyImage.src = this.historyUrls[currentIndex];
this.historyImage.onload = () = > {
this.context.drawImage(this.historyImage, 0.0); }},undo: () = > { // Undo the event bound to the button
let currentIndex = this.currentHistoryIndex - 1;
if (currentIndex < 0) {
currentIndex === -1 && this.clear();
this.currentHistoryIndex = -1;
return;
}
this.currentHistoryIndex = currentIndex;
this.historyImage.src = this.historyUrls[currentIndex];
this.historyImage.onload = () = > {
this.context.drawImage(this.historyImage, 0.0); }}}}Copy the code
The event that was triggered when the mouse was moving
Bind the Mousemove event handler to the canvas.
this.canvas.addEventListener('mousemove'.(event) = > {
if (this.isDrawing) {
const { clientX, clientY } = event;
const x = clientX - offsetLeft;
const y = clientY - offsetTop;
let newOriginX = originX, newOriginY = originY;
let distanceX = Math.abs(x-originX);
let distanceY = Math.abs(y-originY);
// Make the coordinates of the upper left corner of the shape always greater than the coordinates of the lower right corner
if (x < originX) newOriginX = x;
if (y < originY) newOriginY = y;
(x, y) is the coordinate on the canvas when the mouse moves, (originX, originY) is the coordinate on the canvas when the mouse clicks, (originX, originY) is the coordinate on the canvas when the mouse clicks,
// (newOriginX, newOriginY) is the coordinate of the top left corner of the shape when drawing it (such as a rectangle)
const mousePosition = { x, y, originX, originY, newOriginX, newOriginY, distanceX, distanceY };
let handleMousemove = this.handleMousemove();
let currentHandleMousemove = handleMousemove[this.type]; // Take different actions depending on the current typecurrentHandleMousemove && currentHandleMousemove(mousePosition); }},false);
Copy the code
The process in Mousemove will be processed according to the value of type.
pencil
:x
.y
Is the coordinate in the process of mouse movement, used directlylineTo
Connect the line to the current(x, y)
Coordinates, you can do the pencil thing.eraser
The same way as the pencil, but the eraser sets the line color to the background color of the canvas so that it looks like it has been erased. After erasing, you need to reset the line color to currentcolor
The selected color of the element (this part of the processing is put inmouseup
In, not inmousemove
Chinese is better.straightLine
: Moves the starting point of the drawing to the point where the mouse clicks(originX, originY)
, and then the starting point and the mouse move(x, y)
Connect, and you’ll have a straight line. Here,this.reDraw();
This is to prevent the mousemove process from drawing the “track” as well.- Rectangles and circles are drawn similarly to lines, except that the methods are called differently, and the drawing method must ensure that the upper left corner of the drawn shape is higher than the upper right corner, otherwise it will not draw properly.
handleMousemove () {
return {
pencil: (mousePosition) = > {
const { x, y } = mousePosition;
this.context.lineTo(x, y);
this.context.stroke();
},
eraser: (mousePosition) = > {
const { x, y } = mousePosition;
this.context.strokeStyle = this.canvasBackground;;
this.context.lineTo(x, y);
this.context.stroke();
this.context.strokeStyle = this.color.value;
this.context.fillStyle = this.color.value;
},
straightLine: (mousePosition) = > {
let { x, y, originX, originY } = mousePosition;
this.reDraw();
this.context.moveTo(originX, originY);
this.context.lineTo(x, y);
this.context.stroke();
this.context.closePath();
},
rectangle: (mousePosition) = > {
let {newOriginX, newOriginY, distanceX, distanceY } = mousePosition;
this.reDraw();
this.context.rect(newOriginX, newOriginY, distanceX, distanceY);
this.context.stroke();
this.context.closePath();
},
solidRectangle: (mousePosition) = > {
let { newOriginX, newOriginY, distanceX, distanceY } = mousePosition;
this.reDraw();
this.context.fillRect(newOriginX, newOriginY, distanceX, distanceY);
this.context.closePath();
},
circle: (mousePosition) = > {
let { newOriginX, newOriginY, distanceX, distanceY } = mousePosition;
this.reDraw();
let r = Math.sqrt(distanceX * distanceX + distanceY * distanceY);
this.context.arc(distanceX + newOriginX, distanceY + newOriginY , r, 0.2 * Math.PI);
this.context.stroke();
this.context.closePath();
},
solidCircle: (mousePosition) = > {
let { newOriginX, newOriginY, distanceX, distanceY } = mousePosition;
this.reDraw();
let r = Math.sqrt(distanceX * distanceX + distanceY * distanceY);
this.context.arc(distanceX + newOriginX, distanceX + newOriginY , r, 0.2 * Math.PI);
this.context.fillStyle = this.color.value;
this.context.fill();
this.context.closePath();
},
clear: () = > {
this.clear(); }}}Copy the code
Problem solved
- The pixel value on the canvas is inconsistent with the pixel value on the page.
I drew a 50px by 50px square on the page and used this.context.strokeRect(0, 0, 50, 50) on the canvas. Draw a square and find that the size of the pixels on the canvas is not the same as the size of the pixels on the page.
The reason is that I didn’t define the width and height in the properties of canvas element at the beginning, but only defined the width and height of canvas element in the CSS style.
.canvas {
height: 800px;
width: 800px;
background-color: #ccc;
}
Copy the code
When width and height are not set, the Canvas initializes a width of 300 pixels and a height of 150 pixels.
Once the width and height are defined in the canvas element’s properties, it is no problem.
<canvas id="canvas" class="canvas" width="800" height="800"></canvas>
Copy the code
- The base64URL of the image was successfully obtained, but nothing was drawn on the canvas.
This is because the image has not been loaded before the start of drawing, wait for the image loading can be drawn.
img.onload = () = > {
this.context.drawImage(img, 0.0); // Paint the picture on the canvas
};
Copy the code
- The downloaded image works fine on the computer, but on the phone it goes dark.
I guess it was because there was no background color added. By default, the phone used black as the background color, “hiding” the lines. After using different colors of brushes to draw a painting and save it, you could see the painted part of the colored brushes as expected. So just add a background color to the image.
this.context.fillStyle = '#ffffff';
this.context.fillRect(0.0.800.800);
Copy the code
Initially the eraser used the clearRect method to erase the content of the canvas, but after adding the background color, you won’t be able to use this method because it will also erase the background color. Set the eraser to a white brush to simulate erasing.
The previous implementation of the eraser:
let eraserWidth = parseInt(this.lineWidth.value);
if (eraserWidth < 10) {
eraserWidth = 10; // Set the minimum width of the eraser to 5px because the eraser doesn't clear very well when the pixel is too small
}
let halfEraserWidth = eraserWidth / 2;
this.context.clearRect(x - halfEraserWidth, y - halfEraserWidth, eraserWidth, eraserWidth);
Copy the code
After dealing with the problem after the implementation of eraser:
this.context.strokeStyle = '#ffffff';
this.context.lineTo(x, y);
this.context.stroke();
this.context.strokeStyle = this.color.value;
Copy the code
other
- Source address.
- Realized effect: