用canvas实现画板-2.0

1,130 阅读2分钟

一、画板2.0

用canvas实现window画图-1.0

2.0的效果图

image.png

方形

image.png

useSquare(){
    // 一如既往的清空所有工具的事件监听器
    this.clearAllTool()
    if(this.toolStatus != 3){
        this.toolStatus = 3
        var x = 0,
            y = 0;
        var mouseMoveEvent = (e)=>{
            if(this.throttleDate==0 || this.throttleDate+16<Date.now()){
                // 清空上一次的涂鸦(未确定的存档),后续细讲
                this.withdraw()
                //根据x,y获取宽高
                this.ctx.strokeRect(x, y, e.layerX-x,e.layerY-y);
            }
        }
        this.squareEvents.down = (e) => {
            x = e.layerX
            y = e.layerY
            // 使用图形工具,撤回的时候用到
            this.openShape = true
            this.ctx.strokeRect(x, y, 10,10);
            this.canvas.addEventListener('mousemove',mouseMoveEvent)
        }
        this.squareEvents.up = (e) => {
            this.openShape = false
            this.canvas.removeEventListener('mousemove', mouseMoveEvent)
            this.save()
        }
        this.canvas.addEventListener('mousedown', this.squareEvents.down);
        this.canvas.addEventListener('mouseup', this.squareEvents.up)
    }else{
        // 关闭
        this.toolStatus = 0
    }
},

思路和1.0的很相似,除了撤回函数改了,和频繁使用撤回函数

圆形

image.png image.png

懂了这种圆之后,我们来看看椭圆

image.png image.png 好了,虽然说圆我们用不到,但是有助于了解椭圆这个概念,角度偏移这一块我们暂时用不上,其他的就直接上手用了

useArc(){
    this.clearAllTool()
    if(this.toolStatus != 4){
        this.toolStatus = 4
        var x = 0,
            y = 0;
        var mouseMoveEvent = (e)=>{
            if(this.throttleDate==0 || this.throttleDate+16<Date.now()){
                this.withdraw()
                // 获取XR和YR
                var radiusX = (e.layerX-x)/2,
                    radiusY = (e.layerY-y)/2;
                this.ctx.beginPath();
                // x的圆心 = x+XR , y同上
                // 角度偏移我们用不上,描边的话,我们要全部描黑,所以就0, 2 * Math.PI
                this.ctx.ellipse(x+radiusX, y+radiusY, radiusX, radiusY, 0, 0, 2 * Math.PI);
                this.ctx.stroke()
            }
        }
        this.arcEvents.down = (e) => {
            x = e.layerX
            y = e.layerY
            this.openShape = true
            this.ctx.beginPath();
            this.ctx.arc(x+10, y+10, 10, 0, 2 * Math.PI, false);
            this.ctx.stroke()
            this.canvas.addEventListener('mousemove',mouseMoveEvent)
        }
        this.arcEvents.up = (e) => {
            this.openShape = false
            this.canvas.removeEventListener('mousemove', mouseMoveEvent)
            this.save()
        }
        this.canvas.addEventListener('mousedown', this.arcEvents.down);
        this.canvas.addEventListener('mouseup', this.arcEvents.up)
    }else{
        // 关闭
        this.toolStatus = 0
    }
},

圆的逻辑和方形差不多,唯一不同的就是生成圆的这个公式复杂一点

撤回、保存、恢复(前进)

由于我们加了图形这个工具,所以现阶段的撤回需要频繁的刷新页面,却又不改变数据,所以改良一下

withdraw(){
    if(this.page > 0){
        console.info('withdraw',this.openShape)
        var img = new Image();
        // 如果使用图形工具,就单纯撤回,不调整页数
        if(!this.openShape)
            this.page--
        img.src = this.saveRecords[this.page-1];
        console.info(this.saveRecords[this.page-1])
        this.ctx.clearRect(0, 0, this.width, this.height);
        img.onload = ()=>{
            this.ctx.drawImage(img, 0, 0);
        }
    }
}

撤回完了就是恢复(windows里面叫恢复,我们这就入乡随俗吧)

image.png

recover(){
    console.info('恢复')
    if(this.page<this.saveRecords.length){
        // 这个变量用来告诉保存说,我恢复了,之后的页面你可以直接删除
        this.useRecover = true
        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);
        }
    }
},

恢复的逻辑不难,就是拿后一页的缓存图片,或者说页面

保存,竟然都改了撤回,又加了恢复,那我们的逻辑也得改改

save(){
    // 如果没有撤回存档
    if(this.saveRecords.length == this.page){
        console.info('保存')
        // 则直接保存
        this.saveRecords.push(this.canvas.toDataURL())
        this.page++;
    }else if(this.useRecover){
        console.info('恢复')
        // 删除掉恢复之后的页面
        for (let i = 0; i < this.saveRecords.length-this.page; i++) {
            this.saveRecords.pop()
        }
        // 开始保存数据
        // 以下俩行代码可以缩写成  this.page = this.saveRecords.length+1 ,不过写俩行比较好理解
        this.page = this.saveRecords.length
        this.page ++
        this.saveRecords.push(this.canvas.toDataURL())
        this.useRecover = false
    }else{
        console.info('覆盖缓存')
        // 否者,则清空当前页之后的存档
        for (let i = 0; i < this.saveRecords.length-this.page; i++) {
            this.saveRecords.pop()
        }
    }
}

总的来说改动不大,就是加了恢复要改改

二、完整代码

<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="https://wd-2019-1256239864.cos.ap-guangzhou.myqcloud.com/front/components/brush.png" alt="">
            <img :class="toolStatus==2?'select':''" @click="useEraser" src="https://wd-2019-1256239864.cos.ap-guangzhou.myqcloud.com/front/components/eraser.png" alt="">
            <div :class="toolStatus==3?'select shape':'shape'" @click="useSquare">
                <div style="width:15px;height:15px;border: 2px solid #39f;"></div>
            </div>
            <div :class="toolStatus==4?'select arc':'arc'" @click="useArc">
                <div style="width:15px;height:15px;border: 2px solid #39f;border-radius:50%"></div>
            </div>
            
            <div class="interval"></div>
            <img @click="withdraw" src="https://wd-2019-1256239864.cos.ap-guangzhou.myqcloud.com/front/components/withdraw.png" alt="">
            <img @click="recover" style="transform: rotateY(180deg);" src="https://wd-2019-1256239864.cos.ap-guangzhou.myqcloud.com/front/components/withdraw.png" alt="">
            <img @click="save" src="https://wd-2019-1256239864.cos.ap-guangzhou.myqcloud.com/front/components/save.png" alt="">
        </div>
    </div>
</template>
<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;
            }
            .arc{
                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>
<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:null,
                up:null
            },
            eraserEvents:{
                down:null,
                up:null
            },
            squareEvents:{
                down:null,
                up:null
            },
            arcEvents:{
                down:null,
                up:null
            },
            openShape:false,
            useRecover:false
        }
    },
    methods:{
        save(){
            // 如果没有撤回存档
            if(this.saveRecords.length == this.page){
                console.info('保存')
                // 则直接保存
                this.saveRecords.push(this.canvas.toDataURL())
                this.page++;
            }else if(this.useRecover){
                console.info('恢复')
                for (let i = 0; i < this.saveRecords.length-this.page; i++) {
                    this.saveRecords.pop()
                }
                this.page = this.saveRecords.length+1
                this.saveRecords.push(this.canvas.toDataURL())
                this.useRecover = false
            }else{
                console.info('覆盖缓存')
                // 否者,则清空当前页之后的存档
                for (let i = 0; i < this.saveRecords.length-this.page; i++) {
                    this.saveRecords.pop()
                }
            }
        },
        withdraw(){
            if(this.page > 0){
                console.info('withdraw',this.openShape)
                var img = new Image();
                if(!this.openShape)
                    this.page--
                img.src = this.saveRecords[this.page-1];
                console.info(this.saveRecords[this.page-1])
                this.ctx.clearRect(0, 0, this.width, this.height);
                img.onload = ()=>{
                    this.ctx.drawImage(img, 0, 0);
                }
            }
        },
        recover(){
            console.info('恢复')
            if(this.page<this.saveRecords.length){
                this.useRecover = true
                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
            }
        },
        // 使用框
        useSquare(){
            this.clearAllTool()
            if(this.toolStatus != 3){
                this.toolStatus = 3
                var x = 0,
                    y = 0;
                var mouseMoveEvent = (e)=>{
                    if(this.throttleDate==0 || this.throttleDate+16<Date.now()){
                        this.withdraw()
                        this.ctx.strokeRect(x, y, e.layerX-x,e.layerY-y);
                    }
                }
                this.squareEvents.down = (e) => {
                    x = e.layerX
                    y = e.layerY
                    this.openShape = true
                    this.ctx.strokeRect(x, y, 10,10);
                    this.canvas.addEventListener('mousemove',mouseMoveEvent)
                }
                this.squareEvents.up = (e) => {
                    this.openShape = false
                    this.canvas.removeEventListener('mousemove', mouseMoveEvent)
                    this.save()
                }
                this.canvas.addEventListener('mousedown', this.squareEvents.down);
                this.canvas.addEventListener('mouseup', this.squareEvents.up)
            }else{
                // 关闭
                this.toolStatus = 0
            }
        },
        // 圈
        useArc(){
            this.clearAllTool()
            if(this.toolStatus != 4){
                this.toolStatus = 4
                var x = 0,
                    y = 0;
                var mouseMoveEvent = (e)=>{
                    if(this.throttleDate==0 || this.throttleDate+16<Date.now()){
                        this.withdraw()
                        var radiusX = (e.layerX-x)/2,
                            radiusY = (e.layerY-y)/2;
                        this.ctx.beginPath();
                        this.ctx.ellipse(x+radiusX, y+radiusY, radiusX, radiusY, 0, 0, 2 * Math.PI);
                        this.ctx.stroke()
                    }
                }
                this.arcEvents.down = (e) => {
                    x = e.layerX
                    y = e.layerY
                    this.openShape = true
                    this.ctx.beginPath();
                    this.ctx.arc(x+10, y+10, 10, 0, 2 * Math.PI, false);
                    this.ctx.stroke()
                    this.canvas.addEventListener('mousemove',mouseMoveEvent)
                }
                this.arcEvents.up = (e) => {
                    this.openShape = false
                    this.canvas.removeEventListener('mousemove', mouseMoveEvent)
                    this.save()
                }
                this.canvas.addEventListener('mousedown', this.arcEvents.down);
                this.canvas.addEventListener('mouseup', this.arcEvents.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)
            this.canvas.removeEventListener('mousedown',this.squareEvents.down)
            this.canvas.removeEventListener('mouseup',this.squareEvents.up)
            this.canvas.removeEventListener('mousedown',this.arcEvents.down)
            this.canvas.removeEventListener('mouseup',this.arcEvents.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>

以上就是我早上过来给你们爆肝的内容,就是不知道要审核多久,写代码去了