用canvas实现window画图-1.0

一、起应以及分析

最近面试的时候被面试官问到了canvas做个window画图,就想着试试能不能撸一个出来。
首先嘛,就先撸个简洁版,能画能擦处能撤回。那么需求定好了,就开始分析吧!
首先能画,就是画笔这个功能,我看了一下,canvas里面有这么几个函数,组合起来可以实现画笔这个功能

image.png

橡皮擦嘛!就用清除

image.png
那么剩下的就是业务需求了,存档和撤回
撤回嘛!我们肯定要有存档才能撤回,存档我们不可能每次把自己做过的步骤都得存下来,撤回一次多麻烦,那么我们就得把整个画布得保存成base64的数据,用来撤回,好巧不巧,canvas就有这个方法

image.png

二、实战

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>
复制代码

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>
复制代码

样式效果图

image.png

接下来就是js代码了

首先肯定是画笔

useBrush(){
        // 这个函数暂且忽略后续会讲
        this.clearAllTool()
        // 首先判断是否使用画笔
        if(this.toolStatus != 1){
            // 开启
            this.toolStatus = 1
            // 2.在移动的路径内做点连线
            var mouseMoveEvent = (e)=>{
                // 节流 保存一秒60帧以内
                if(this.throttleDate==0 || this.throttleDate+16<Date.now()){
                    // 2.获取当前鼠标的坐标,做一个坐标点
                    this.ctx.lineTo(e.layerX,e.layerY);
                    // 与上一个坐标点连线
                    this.ctx.stroke();
                }
            }
            // 1.单击做一个起始点(按顺序看)
            this.brushEvents.down = (e) => {
                this.ctx.beginPath();
                this.ctx.moveTo(e.layerX,e.layerY);
                this.canvas.addEventListener('mousemove',mouseMoveEvent)
            }
            // 3.松开鼠标后,清空move事件,存档
            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{
            // 关闭
            this.toolStatus = 0
        }
    },
复制代码

以上代码用一句话来说就是,在鼠标单击的时候做个坐标点,然后每次触发移动事件的时候,获取到坐标进行连线,松开后则存档

能画之后就可以开始写保存和撤回

save(){
    // 如果没有撤回存档
    if(this.saveRecords.length == this.page){
        console.info('保存')
        // 则直接保存
        this.saveRecords.push(this.canvas.toDataURL())
        this.page++;
    }else{
        console.info('覆盖缓存')
        // 否者,则清空当前页之后的存档
        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);
    }
},
复制代码

存档这边得先说撤回,撤回的逻辑就是,把你当前这一页的坐标page-1,然后拿到上一页覆盖,(不删除存档,到时候可以做前进Ctrl+Y),保存的话,如果撤回就覆盖之后的存档

橡皮擦

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{
        // 关闭
        this.toolStatus = 0
    }
},
复制代码

这个我就不多说了,核心代码就是clearRect,逻辑和画笔一样

别忘了清空事件监听器

clearAllTool(){
    this.toolStatus = 0
    console.info('清除所有工具',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)
}
复制代码

三、完整代码

<script>
import { uuid } from 'uuidv4'
export default {
    name:'loonCanvasEditor',
    props:[
        'width',
        'height'
    ],
    data(){
        return{
            id:uuid(),
            saveRecords:[],// 历史存档
            canvas:null,
            ctx:null,
            toolStatus:0,
            page:0, // pageIndex
            throttleBrush:null, // 画笔节流
            throttleDate: 0,
            brushEvents:{
                down:()=>{},
                up:()=>{}
            },
            eraserEvents:{
                down:()=>{},
                up:()=>{}
            },
        }
    },
    methods:{
        save(){
            // 如果没有撤回存档
            if(this.saveRecords.length == this.page){
                console.info('保存')
                // 则直接保存
                this.saveRecords.push(this.canvas.toDataURL())
                this.page++;
            }else{
                console.info('覆盖缓存')
                // 否者,则清空当前页之后的存档
                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);
            }
        },
        // 使用画笔
        useBrush(){
            this.clearAllTool()
            if(this.toolStatus != 1){
                // 开启
                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{
                // 关闭
                this.toolStatus = 0
            }
        },
        // 使用橡皮
        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{
                // 关闭
                this.toolStatus = 0
            }
        },
        clearAllTool(){
            this.toolStatus = 0
            console.info('清除所有工具',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>
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享