First, response and analysis

Recently, when I was interviewed, I was asked by the interviewer to draw a window drawing on canvas. I thought I could try to draw a window drawing on canvas. Well, first of all, it’s just a simple version that can draw, erase, and withdraw. So the requirements set, start to analyze it! The first thing that can be painted is the function of brush. I have a look, there are several functions in the canvas, which can be combined to realize the function of brush

Eraser! With the clear

All that remains is the business requirement, archiving and retracting. We must have an archive before we can withdraw. It is impossible for us to save every step we have done in the archive. It would be troublesome to withdraw once, so we have to save the whole canvas as base64 data for retracting

Second, the 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 the 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 the code

Style renderings

Now comes the JS code

The first is definitely the paintbrush

useBrush(){
        // This function will be ignored later
        this.clearAllTool()
        // Determine whether to use a brush first
        if(this.toolStatus ! =1) {/ / open
            this.toolStatus = 1
            // 2. Make a line in the moving path
            var mouseMoveEvent = (e) = >{
                // Throttle saves up to 60 frames per second
                if(this.throttleDate==0 || this.throttleDate+16<Date.now()){
                    // 2. Get the coordinates of the current mouse and make a coordinate point
                    this.ctx.lineTo(e.layerX,e.layerY);
                    // Connect to the last coordinate point
                    this.ctx.stroke(); }}// 1. Click to make a starting point (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, clear the move event and save it
            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}},Copy the code

In a word, the above code is to make a coordinate point when the mouse clicks, and then each time the trigger of the movement event, to get the coordinate for wiring, after releasing the archive

As soon as you can draw, you can write save and recall

save(){
    // If the archive is not withdrawn
    if(this.saveRecords.length == this.page){
        console.info('save')
        // Save directly
        this.saveRecords.push(this.canvas.toDataURL())
        this.page++;
    }else{
        console.info('Override cache')
        // If no, 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];
    / / to empty
    this.ctx.clearRect(0.0.this.width, this.height);
    / / withdraw
    img.onload = () = >{
        this.ctx.drawImage(img, 0.0); }},Copy the code

The logic of the withdrawal is, put your current page coordinates page-1, and then take the previous page overwrite, (do not delete the archive, you can do forward Ctrl+Y), save, if the withdrawal will overwrite the subsequent archive

The 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 the code

I won’t go into this, but the core code is clearRect, the logic is the same as the brush

Don’t forget to empty 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 the code

Complete code

<script>
import { uuid } from 'uuidv4'
export default {
    name:'loonCanvasEditor'.props: ['width'.'height'].data(){
        return{
            id:uuid(),
            saveRecords: [].// Archive the history
            canvas:null.ctx:null.toolStatus:0.page:0.// pageIndex
            throttleBrush:null.// Brush throttling
            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')
                // Save directly
                this.saveRecords.push(this.canvas.toDataURL())
                this.page++;
            }else{
                console.info('Override cache')
                // If no, 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 a brush
        useBrush(){
            this.clearAllTool()
            if(this.toolStatus ! =1) {/ / open
                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 an 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 the code

Version 2.0 to update canvas window drawing -2.0