Realize the drawing board with canvas-1.0

Realize the drawing board with canvas-1.0

1. response and analysis

During the recent interview, the interviewer asked the canvas to make a window drawing, and I wanted to try if I could draw one out.
First of all, let s make a concise version, which can be drawn and erased and can be withdrawn. Then the requirements are set, start analyzing!
The first thing I can paint is the function of the brush. I took a look. There are several functions in the canvas, which can be combined to realize the function of the brush.

Eraser! Just use clear

Then the rest is business needs, archive and withdraw withdrawal! We must have an archive to withdraw. For an archive, we can t save every step we have done. It s too much trouble to withdraw once, so we have to save the entire canvas as base64 data to withdraw. It s a coincidence. Unfortunately, canvas has this method

2. actual combat

template

< template > < div id = "loon_canvas_editor" > < canvas :ref = "id" :width = "width" :height = "height" > </canvas > < div id = "toolbar" > < img :class = " toolStatus==1?'select':"" @ click = "useBrush" src = "/components/brush.png" alt = "" > < img :class= "toolStatus==2?'select':"" @ click = "useEraser" src = "/components/eraser.png" alt = "" > < div class = "interval" > </div > < img @ click = "withdraw" src = "/components/withdraw.png" alt = "" > < img @ click = "save" src = "/components/save.png" alt = "" > </div > </div > </template > Copy code

css

< style lang = "less" scoped > #loon_canvas_editor { width : 100% ; height : 100% ; font-size : 0 ; margin : 0 auto; canvas { border : 1px solid #39f ; } #toolbar { display : flex; width : 300px ; padding : 10px 20px ; background : #deeeff ; border : 1px solid #39f ; margin-top : -1px ; img { width : 20px ; height : 20px ; margin-right : 10px ; } .shape { width : 20px ; height : 20px ; display : flex; align-items : center; justify-content : center; margin-right : 10px ; } .select { border : 1px solid #222 ; } .interval { width : 2px ; height : 20px ; background : #aaa ; margin-right : 10px ; } } } </style > Copy code

Style rendering

Next is the js code

First of all it must be the brush

useBrush () { //This function Ignoring subsequent speaks the this .clearAllTool () //first determine whether to use the brush IF ( the this .toolStatus! = . 1 ) { //Open the this .toolStatus = . 1 //2. moving within the path Make a connection var mouseMoveEvent = ( e )=> { //Throttle save within 60 frames per second if ( this .throttleDate== 0 || this .throttleDate + 16 < Date .now()){ // 2.Get The coordinates of the current mouse, make a coordinate point this.ctx.lineTo(e.layerX,e.layerY); //Connect with the previous coordinate point this .ctx.stroke(); } } //1. Click to make a starting point (look in order) this .brushEvents.down = ( e ) => { this .ctx.beginPath(); this .ctx.moveTo(e.layerX,e.layerY); this .canvas.addEventListener( 'mousemove' ,mouseMoveEvent) } //3. After releasing the mouse, the move event empty, archive the this .brushEvents.up = ( E ) => { the this .canvas.removeEventListener ( 'mouseMove' ,. MouseMoveEvent) the this .ctx.stroke (); the this .save ( ) } this .canvas.addEventListener( 'mousedown' , this .brushEvents.down); this .canvas.addEventListener( 'mouseup' , this .brushEvents.up) } else { //close this .toolStatus = 0 } }, Copy code

In one sentence, the above code is to make a coordinate point when the mouse is clicked, and then every time a movement event is triggered, the coordinate is obtained for connection, and it is archived after release.

After you can draw, you can start to write, save and withdraw

save () { //If the archive is not withdrawn if ( this .saveRecords.length == this .page){ console .info( 'Save' ) //directly save this .saveRecords.push( this .canvas.toDataURL()) this .page++; } else { console .info( 'Overwrite cache' ) //Otherwise, clear the archive after the current page for ( let i = 0 ; i < this .saveRecords.length- this .page; i++) { this .saveRecords. pop() } } }, withdraw () { var img = new Image(); this .page - img.src = this .saveRecords[ this .page- 1 ]; //Clear this .ctx.clearRect( 0 , 0 , this .width, this .height); //Withdraw img.onload = ()=> { this .ctx.drawImage(img, 0 , 0 ); } }, Copy code

Here you have to talk about withdrawal first. The logic of withdrawal is to take your current page coordinate page-1, and then get the previous page to overwrite (do not delete the archive, you can do forward Ctrl+Y at that time), if you save , If you withdraw, overwrite the archive afterwards

Eraser

useEraser () { this .clearAllTool() if ( this .toolStatus != 2 ){ this .toolStatus = 2 var mouseMoveEvent = ( e )=> { if ( this .throttleDate== 0 || this .throttleDate+ 16 < Date .now( )){ //Core code, clear this .ctx.clearRect(e.layerX- 5 ,e.layerY- 5 , 10 , 10 ); } } this .eraserEvents.down = ( e ) => { this .ctx.clearRect(e.layerX- 5 ,e.layerY- 5 , 10 , 10 ); this .canvas.addEventListener( 'mousemove' ,mouseMoveEvent) } this .eraserEvents.up = ( e ) => { this .canvas.removeEventListener( 'mousemove' , mouseMoveEvent) this .save() } this .canvas.addEventListener( 'mousedown' , this .eraserEvents.down); this .canvas.addEventListener( 'mouseup' , this .eraserEvents.up) } else { //close this .toolStatus = 0 } }, Copy code

I won t say much about this, the core code is clearRect, the logic is the same as the pen

Don't forget to clear the event listener

clearAllTool () { this .toolStatus = 0 console .info( 'Clear all tools' , this .canvas.removeEventListener, this .brushEvents.down) this .canvas.removeEventListener( 'mousedown' , this .brushEvents.down) this .canvas.removeEventListener ( 'mouseup' , this .brushEvents.up) this .canvas.removeEventListener( 'mousedown' , this .eraserEvents.down) this .canvas.removeEventListener( 'mouseup' , this.eraserEvents.up) } Copy code

3. the complete code

< Script > Import {UUID} from 'uuidv4' Export default { name : 'loonCanvasEditor' , The props : [ 'width' , 'height' ], data () { return { id :uuid(), saveRecords :[], //Historical archive canvas : null , ctx : null , toolStatus : 0 , page : 0 , //pageIndex throttleBrush : null , //brush throttle throttleDate : 0 , brushEvents :{ down : ()=> {}, up :()=> {} }, eraserEvents :{ down : ()=> {}, up : ()=> {} }, } }, methods :{ save () { //If the archive is not withdrawn if ( this .saveRecords.length == this .page){ console .info( 'Save' ) //directly save this .saveRecords.push( this .canvas.toDataURL()) this .page++; } else { console .info( 'Overwrite cache' ) //Otherwise, clear the archive after the current page for ( let i = 0 ; i < this .saveRecords.length- this .page; i++) { this .saveRecords. pop() } } }, withdraw () { var img = new Image(); this .page - img.src = this .saveRecords[ this .page- 1 ]; this .ctx.clearRect( 0 , 0 , this .width, this .height); img.onload = ()=> { this .ctx.drawImage(img, 0 , 0 ); } }, //use brush useBrush () { this .clearAllTool() if ( this .toolStatus != 1 ){ //Turn on this .toolStatus = 1 var mouseMoveEvent = ( e )=> { if ( this .throttleDate== 0 || this .throttleDate+ 16 < Date .now()){ this .ctx.lineTo(e.layerX,e.layerY); this .ctx.stroke(); } } this .brushEvents.down = ( e ) => { this .ctx.beginPath(); this .ctx.moveTo(e.layerX,e.layerY); this .canvas.addEventListener( 'mousemove' ,mouseMoveEvent) } this .brushEvents.up = ( e ) => { this .canvas.removeEventListener( 'mousemove' , mouseMoveEvent) this .ctx.stroke(); this .save() } this .canvas.addEventListener( 'mousedown' , this .brushEvents.down); this .canvas.addEventListener( 'mouseup' , this .brushEvents.up) } else { //close this .toolStatus = 0 } }, //use eraser useEraser () { this .clearAllTool() if ( this .toolStatus != 2 ){ this .toolStatus = 2 var mouseMoveEvent = ( e )=> { if ( this .throttleDate== 0 || this .throttleDate+ 16 < Date .now( )){ this .ctx.clearRect(e.layerX- 5 ,e.layerY- 5 , 10 , 10 ); } } this .eraserEvents.down = ( e ) => { this .ctx.clearRect(e.layerX- 5 ,e.layerY- 5 , 10 , 10 ); this .canvas.addEventListener( 'mousemove' ,mouseMoveEvent) } this .eraserEvents.up = ( e ) => { this .canvas.removeEventListener( 'mousemove' , mouseMoveEvent) this .save() } this .canvas.addEventListener( 'mousedown' , this .eraserEvents.down); this .canvas.addEventListener( 'mouseup' , this .eraserEvents.up) } else { //close this .toolStatus = 0 } }, clearAllTool () { this .toolStatus = 0 console .info( 'Clear all tools' , this .canvas.removeEventListener, this .brushEvents.down) this .canvas.removeEventListener( 'mousedown' , this .brushEvents.down) this .canvas.removeEventListener ( 'mouseup' , this .brushEvents.up) this .canvas.removeEventListener( 'mousedown' , this .eraserEvents.down) this .canvas.removeEventListener( 'mouseup' ,this .eraserEvents.up) } }, mounted () { this .canvas = this .$refs[ this .id] this .ctx = this .canvas.getContext( '2d' ) this .canvas.style.width = this .width this .canvas.style.height = this .height this .save() } } </script > copy code

Version 2.0 update to achieve the window with canvas drawing -2.0