Study canvas On and off for a few days , Except look API, I don't know how to learn . therefore , Started trying to simulate some gadgets , Of course, the function is simple , After all, it's still in the beginning stage , It's too complicated to do , And undermine confidence , reasoning , Want to simulate a drawing tool , But I didn't expect to draw a difficult horse , I was badly shut down .
Let's take a look at the rendering :
My current strength is too good , It feels like a lot of energy has been spent to do it here , It's really hard to draw .
Except for the ones I just learned and used API
outside , When drawing , I have come into contact with strokeRect
,arc
,toDataURL
etc. API
Usage of , Of course, it's also relatively simple :
Here are two ways to draw graphics API
Well :
strokeRect
: Is to draw a rectangle in the form of lines ;
x
: Start abscissa position ;y
: Start ordinate position ;width
: The width of the rectangle drawn ;height
: The height of the rectangle drawn ;arc
: Is to draw a rectangle in the form of lines ;
x
: Abscissa position of the center of the circle ;y
: The ordinate position of the center of the circle ;r
: The radius of the circle ; sAngle
: Starting angle , In radians ;eAngle
: End angle , In radians ;counterclockwise
: Optional . Specify whether the drawing should be counter clockwise or clockwise .False = Clockwise ,true = Anti-clockwise ;Let's look at a simple example :
ctx.beginPath()
ctx.strokeStyle = 'red'
ctx.strokeRect(10, 10, 100, 100)
ctx.beginPath()
ctx.strokeStyle = 'green'
ctx.arc(60, 60, 50, 0, 2 * Math.PI)
ctx.stroke()
Copy code
You can see arc
and strokeRect
There are still some differences in the use of ,arc
It just describes the path , And drawing still needs stroke
To achieve , You can see from the two names .
It is not difficult to implement a simple rendering function , Of course, if it's more complicated , Now I think : That's too hard, too hard , There are so many things to consider and do .
Implement a simple drawing function , There are three elements to consider :
These three elements , They can be separated by mousedown
,mousemove
,mouseup
To achieve :
API | Concept |
---|---|
mousedown | Trigger when the mouse is pressed |
mousemove | Trigger when the mouse moves |
mouseup | Trigger when the mouse pops up |
here , because addEventListener
You can listen repeatedly to bind events , When switching drawing graphics, it will cause Bug
, There is no good way to solve ; So I didn't use this API
To bind events , It's the old onmousedown
This form .
good , Now that you have the idea , Then start to realize , Let's write the event monitoring of the mouse first, and then talk about the next step :
cvs.onmousedown = function (e) {
isStart = true
console.log('mousedown')
cvs.onmousemove = function (e) {
isStart && console.log('onmousemove')
}
}
cvs.onmouseup = function (e) {
isStart = false
console.log('onmouseup')
}
Copy code
Here we put mousemove
Events should be bound to mousedown
Inside , Because when we expect : When the pen falls and moves , Drawing .
I looked at the execution effect of the browser , A problem has been identified , Namely mousemove
The frequency of execution is simply too fast :
You can see , I just slide a few times , Hundreds of events on execution , If you add the code that needs to operate the canvas later , Efficiency must be slow , So I added throttling here :
const move = throttle(function (e) {
console.log('mousemove')
})
cvs.onmousedown = function (e) {
isStart = true
console.log('mousedown')
cvs.onmousemove = function (e) {
isStart && move.call(this, e)
}
}
function throttle (callback, delay = 10) {
let timer = null
return function () {
const ctx = this
if (timer) {
return
}
timer = setTimeout(() => {
callback.apply(ctx, arguments)
timer = null
}, delay)
}
}
Copy code
The optimization effect :
After using throttling , It is obvious that the frequency of execution is not as happy as before , So it's a little acceptable .
After listening to the event , We have to think about how to draw graphics on the drawing board .
A painting drawn directly , It's very simple , All we need to do is mousemove
When , Use context
Object to draw, each pixel can achieve the goal :
const move = throttle(function (e) {
ctx.lineTo(e.offsetX, e.offsetY)
ctx.stroke()
})
cvs.onmousedown = function (e) {
isStart = true
ctx.beginPath()
ctx.strokeStyle = '#000'
cvs.onmousemove = function (e) {
isStart && move.call(this, e)
}
}
Copy code
The point is to mousedown
When beginPath
, Reopen the drawing path , To avoid connecting with the last drawing ; And then through mousemove
Events continue to draw on OK 了 .
It's easy to draw directly , But if you draw a straight line , Just a little bit of trouble : Because a straight line is made up of two points , And we mousemove
But there will be many points , After I think , That's how it works :
Record mouseup
The first position , As starting point ; Then record each mousemove
As the end point , And then connect the city in a straight line . that mousemove
How to get the last point of ?
I pass here every time mousemove
when ,celarRect
Canvas information , And then again lineTo
The first point , In this way, the effect can be achieved !
const move = throttle(function (e) {
ctx.clearRect(0, 0, 600, 600)
ctx.moveTo(start.x, start.y)
ctx.lineTo(e.offsetX, e.offsetY)
ctx.stroke()
})
cvs.onmousedown = function (e) {
isStart = true
ctx.beginPath()
ctx.strokeStyle = '#000'
start.x = e.offsetX
start.y = e.offsetY
cvs.onmousemove = function (e) {
isStart && move.call(this, e)
}
}
Copy code
This seems good . however , But something strange happened :
You can see , The line drawn each time is not clearRect
Get rid of , Thought for a long time and didn't find the reason , Later, it was found that when cleared , Re open the drawing path and there will be no problem :
const move = throttle(function (e) {
ctx.clearRect(0, 0, 600, 600)
ctx.beginPath()
ctx.moveTo(start.x, start.y)
ctx.lineTo(e.offsetX, e.offsetY)
ctx.stroke()
})
cvs.onmousedown = function (e) {
isStart = true
ctx.beginPath()
ctx.strokeStyle = '#000'
start.x = e.offsetX
start.y = e.offsetY
cvs.onmousemove = function (e) {
isStart && move.call(this, e)
}
}
Copy code
In this case , The effect is normal . Drawing a rectangle is the same idea as drawing a circle , It's called API
When , There are some differences in processing . such , It seems to have achieved the effect ?
The answer is NO, We found another problem , Through constant clearRect
After clearing the canvas , The previous painting will also be removed , That such , How to draw a picture !
After thinking for a while , My idea is to introduce a method to save drawing records record
An array of objects , Use this array to save each drawing operation , And then again clearRect
After emptying the canvas , Then draw the path in the record again ( If it's more complicated later , Through this record, you can even realize the function of revocation ).
const move = throttle(function (e) {
ctx.clearRect(0, 0, 600, 600)
record.forEach(item => {
ctx.beginPath()
ctx.moveTo(item.x1, item.y1)
ctx.lineTo(item.x2, item.y2)
ctx.stroke()
})
ctx.beginPath()
ctx.moveTo(start.x, start.y)
ctx.lineTo(e.offsetX, e.offsetY)
ctx.stroke()
})
cvs.onmousedown = function (e) {
isStart = true
ctx.beginPath()
ctx.strokeStyle = '#000'
start.x = e.offsetX
start.y = e.offsetY
cvs.onmousemove = function (e) {
isStart && move.call(this, e)
}
}
cvs.onmouseup = function (e) {
isStart = false
record.push({ x1: start.x, y1: start.y, x2: e.offsetX, y2: e.offsetY})
}
Copy code
such , You can keep the effect you painted before . In this way , Draw the circle 、 rectangular 、 And direct drawing should be recorded , It's just a little different .
Drawing graphics, we're done , After that, you should save the picture . Save the picture , There's a saying before API
yes toDataURL
, This API
No context
Contextual , It is canvas
Methods :
const canvas = document.querySelector('canvas')
const url = canvas.toDataURL()
Copy code
This API
There are two parameters : The first is type , The default is :image/png
; The second is quality : The default is :0.92;
Let's see toDataURL MDN file .
Know this API
After how to use , Next, we can finish the operation of saving the picture :
function saveImage () {
const link = document.createElement('a')
link.download = ' file name '
link.href = cvs.toDataURL()
link.click()
}
Copy code
Of course , adopt toDataURL
After getting the data , It can also be assigned to pictures , Achieve preview effect , It should be noted that :
createElemennt
Created images , direct src
Assignment is invalid , But through new Image
Object to implement ;base64
The way to show . Then paste the final effect picture :
I thought drawing was simple , But I didn't expect to encounter many difficulties in realizing the most basic rendering .
and , If you want to implement more complex functions in the future , The difficulty is getting bigger and bigger , But also good , Knock out a Demo Come on , It's barely qualified .
Back up , Let's evaluate the implementation difficulty first , Let's learn from the simple .