Canvas试验图形-写字板

这是一个使用HTML5Canvas实现的图形绘制应用,用户可以选择绘制矩形、圆形等图形或进行手写。但目前存在一个问题,即图形绘制与手写不能同时进行。代码中定义了不同的形状类并实现了拖动功能,同时提供了回退和清除所有功能。然而,当选择手写时,之前的图形会消失。寻求解决方案。
摘要由CSDN通过智能技术生成

Canvas
在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .pickerColor{
            position: fixed;
            left: 50%;
            top: 10%;
            transform: translate(-50%,0%);
            height: 25px;
        }
        canvas{
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%,-50%);
            width: 800px;
            height: 500px;
            background-color: #ccc;
        }
        .btnGroup{
            position: fixed;
            left: 60%;
            top: 10%;
            transform: translate(-50%,0%);
            height: 25px;
        }
        /* .backAllbtn{
            position: fixed;
            left: 70%;
            top: 10%;
            transform: translate(-50%,0%);
            height: 25px;
        } */
        #shapeOptions{
            position: fixed;
            left: 40%;
            top: 10%;
            transform: translate(-50%,0%);
            height: 25px;
        }
    </style>
</head>
<body>
    <p>因为是使用无限循环来绘制图形及通过endX,endY实时拖动位置,所以无法跟笔画共存</p>
    <ul>
        <li>要么就纯笔画</li>
        <li>使用图形,笔画就会消失</li>
    </ul>
    <div>
        <select id="shapeOptions" name="shape">
            <option value="rect">矩形</option>
            <option value="arc" >圆形</option>
            <option value="square" >正方形</option>
            <option value="write" selected>手写</option>
        </select>

        <input class="pickerColor" type="color">
        <div class="btnGroup">
            <button class="backbtn" disabled  >回退</button>
            <button class="backAllbtn" disabled >清除所有</button>
            <button class="save" >保存图片</button>
        </div>
        
    </div>
    <canvas></canvas>
</body>
</html>
<script>
    let cvs = document.querySelector('canvas');
    let ctx = cvs.getContext('2d');
    let selectColor = document.querySelector('input')
    let backbtn = document.querySelector('.backbtn')
    let backAllbtn = document.querySelector('.backAllbtn')
    let savebtn = document.querySelector('.save')
    let shapeStyle =  document.querySelector('#shapeOptions');
    let isRender = false;
    let isWrite  = false;

    const shapes = [];
    const undoStack = [];


    function init(){
        cvs.width = 800 * devicePixelRatio;
        cvs.height = 500 * devicePixelRatio;
    }
    init()

  

    // 监听数组变化
    function ShapeHandle(arr,even){
        Object.defineProperty(arr,even, {
            configurable: true,
            enumerable: false,
            writable: true,
            value: function () {
                switch (even) {
                    case 'push':
                    Array.prototype.push.apply(this, arguments);
                    break;
                    case 'pop':
                    Array.prototype.pop.apply(this, arguments);
                    break;
                    default:
                        break;
                }
                if(shapes.length > 0 || undoStack.length > 0 ){
                    backbtn.disabled = false;
                    backAllbtn.disabled = false;
                }
                else{
                    backbtn.disabled = true;
                    backAllbtn.disabled = true;

                }
            }
        });
    }
    new ShapeHandle(shapes,'push')
    new ShapeHandle(shapes,'pop')
    new ShapeHandle(undoStack,'push')
    new ShapeHandle(undoStack,'pop')


    // 保存

    savebtn.addEventListener('click',function(){
        var dataURL = cvs.toDataURL('image/png');
        var link = document.createElement('a');
        link.download = 'myCanvas.png';
        link.href = dataURL;
        link.click();
    })

    // 回退
    backbtn.addEventListener('click',function(){
        let lastShape = shapes[shapes.length - 1];
        if(lastShape.type && lastShape.type == 'write'){
            if (undoStack.length > 1) {
                if(undoStack.length == 1){
                    undoStack.length = 0;
                }
                else{
                    undoStack.pop()
                }
                ctx.putImageData(undoStack[undoStack.length - 1], 0, 0);
                shapes.pop()
            }
        }
        else{
            shapes.pop()
            isRender = true
            requestAnimationFrame(()=>{
                isRender = false
            })
        }
        
    })

    // 清除所有
    backAllbtn.addEventListener('click',function(){
        isRender = true
        shapes.length = 0
        requestAnimationFrame(()=>{
            isRender = false
            backbtn.disabled = true;
            backAllbtn.disabled = true;
        })
    })


    cvs.onmousedown = (e) =>{
        // console.log(e)
        // 获取当前点击的坐标 即 鼠标点击相对视口的距离 - canvas相对视口的距离
        const rect = cvs.getBoundingClientRect()
        const clickX = e.clientX - rect.left;
        const clickY = e.clientY - rect.top;
        const shape = getShape(clickX,clickY)
        isRender = true;
        if(shape){
            const { stratX,stratY,endX,endY } = shape;
            window.onmousemove = (e)=>{
                const disX = e.clientX - rect.left - clickX;
                const disY = e.clientY - rect.top - clickY;
                shape.stratX = stratX + disX;
                shape.endX = endX + disX;
                shape.stratY = stratY + disY;
                shape.endY = endY + disY
            }
        }
        else{
            let shape = null;
            if(shapeStyle.value == 'write'){
                isWrite = true;
            }
            // 创建
            switch (shapeStyle.value) {
                case 'rect':
                    shape = new Rectangle(selectColor.value,clickX,clickY)
                    break;
                case 'square':
                    shape = new Squareangle(selectColor.value,clickX,clickY)
                    break;
                case 'arc':
                    shape = new Arcangle(selectColor.value,clickX,clickY)
                    break;
                case 'write':
                    shape = new Writeangle(selectColor.value,clickX,clickY)
                    undoStack.push(ctx.getImageData(0, 0, cvs.width, cvs.height));
                    break;
                default:
                    alert('还在开发中')
                    return;
                break;
            }
            // if(shapeStyle.value != 'write') ;
            shapes.push(shape)


            // 监听鼠标移动事件和抬起
            window.onmousemove = (e)=>{
                shape.endX = e.clientX - rect.left;
                shape.endY = e.clientY - rect.top;
                if(shape.type && shape.type === 'write'){
                    shape.draw();
                }
            }
        }
        
        window.onmouseup = ()=>{
            window.onmousemove = null;
            window.onmouseup = null;
            isRender = false;
            isWrite = false;
        }

    }   


    // 矩形构造函数
    class Rectangle{
        constructor(color,stratX,stratY){
            this.color = color;
            this.stratX = stratX;
            this.stratY = stratY;
            // 一开始结束坐标和开始坐标是一样的,拖动的时候更改结束坐标
            this.endX = stratX;
            this.endY = stratY;
            this.color = color;
        }

        get minX(){
            return Math.min(this.stratX,this.endX) 
        }
        get maxX(){
            return Math.max(this.stratX,this.endX)
        }
        get minY(){
            return Math.min(this.stratY,this.endY)
        }
        get maxY(){
            return Math.max(this.stratY,this.endY)
        }
        draw(){
            ctx.beginPath();
            // 右下左上
            ctx.moveTo(this.minX * devicePixelRatio,this.minY * devicePixelRatio);
            ctx.lineTo(this.maxX * devicePixelRatio,this.minY * devicePixelRatio);
            ctx.lineTo(this.maxX * devicePixelRatio,this.maxY * devicePixelRatio);
            ctx.lineTo(this.minX * devicePixelRatio,this.maxY * devicePixelRatio);
            ctx.lineTo(this.minX * devicePixelRatio,this.minY * devicePixelRatio);
            ctx.fillStyle = this.color;
            ctx.fill();
            ctx.strokeStyle = '#fff';
            ctx.lineCap = 'round';
            ctx.lineWidth = 3 * devicePixelRatio;
            ctx.stroke()

        }
        // 点击是否是已创建的图形区域 
        inInside(x,y){
            return x >= this.minX && x <= this.maxX && y >= this.minY && y <= this.maxY
        }
    }   



    // 正方形构造函数
    class Squareangle{
        constructor(color,stratX,stratY){
            this.color = color;
            this.stratX = stratX;
            this.stratY = stratY;
            // 一开始结束坐标和开始坐标是一样的,拖动的时候更改结束坐标
            this.endX = stratX;
            this.endY = stratY;
            this.color = color;
        }

        get minX(){
            return Math.min(this.stratX,this.endX) 
        }
        get maxX(){
            return Math.max(this.stratX,this.endX)
        }
        get minY(){
            return Math.min(this.stratY,this.endY)
        }
        get maxY(){
            return Math.max(this.stratY,this.endY)
        }
        get sameAsSide(){
            return this.maxX - this.minX;
        }
        draw(){
            ctx.beginPath();
            // 右下左上
            ctx.moveTo(this.minX * devicePixelRatio,this.minY * devicePixelRatio);
            ctx.lineTo((this.minX + this.sameAsSide) * devicePixelRatio,this.minY * devicePixelRatio)
            ctx.lineTo((this.minX + this.sameAsSide) * devicePixelRatio,(this.minY + this.sameAsSide) * devicePixelRatio)
            ctx.lineTo((this.minX) * devicePixelRatio,(this.minY + this.sameAsSide) * devicePixelRatio)
            ctx.lineTo((this.minX) * devicePixelRatio,(this.minY) * devicePixelRatio)


            ctx.fillStyle = this.color;
            ctx.fill();
            ctx.strokeStyle = '#fff';
            ctx.lineCap = 'round';
            ctx.lineWidth = 3 * devicePixelRatio;
            ctx.stroke()

        }
        // 点击是否是已创建的图形区域 
        inInside(x,y){
            return x >= this.minX && x <= this.maxX && y >= this.minY && y <= this.maxY
        }
    }   


    // 圆形构造函数
    class Arcangle{
        constructor(color,stratX,stratY){
            this.color = color;
            this.stratX = stratX;
            this.stratY = stratY;
            // 一开始结束坐标和开始坐标是一样的,拖动的时候更改结束坐标
            this.endX = stratX;
            this.endY = stratY;
            this.color = color;
        }

        get minX(){
            return Math.min(this.stratX,this.endX) 
        }
        get maxX(){
            return Math.max(this.stratX,this.endX)
        }
        get minY(){
            return Math.min(this.stratY,this.endY)
        }
        get maxY(){
            return Math.max(this.stratY,this.endY)
        }
        get sameAsSide(){
            return this.maxX - this.minX;
        }
        draw(){
            ctx.beginPath();
            ctx.arc(this.minX *  devicePixelRatio,this.minY * devicePixelRatio,this.sameAsSide * devicePixelRatio,0,2 * Math.PI,false)
            ctx.fillStyle = this.color;
            ctx.fill();
            ctx.strokeStyle = '#fff';
            ctx.lineCap = 'round';
            ctx.lineWidth = 3 * devicePixelRatio;
            ctx.stroke()

        }
        // 点击是否是已创建的图形区域 
        inInside(x,y){
            if (Math.sqrt((x - this.minX) ** 2 + (y - this.minY) ** 2) <= this.sameAsSide) {
                return true;
            } else {
                return false;
            }


        }
    }  



    // 手写
    class Writeangle{
        constructor(color,stratX,stratY){
            this.color = color;
            this.stratX = stratX;
            this.stratY = stratY;
            // 一开始结束坐标和开始坐标是一样的,拖动的时候更改结束坐标
            this.endX = stratX;
            this.endY = stratY;
            this.color = color;
            this.type = 'write'
        }

        get minX(){
            return Math.min(this.stratX,this.endX) 
        }
        get maxX(){
            return Math.max(this.stratX,this.endX)
        }
        get minY(){
            return Math.min(this.stratY,this.endY)
        }
        get maxY(){
            return Math.max(this.stratY,this.endY)
        }
        draw(){
            ctx.beginPath();
            ctx.moveTo(this.stratX * devicePixelRatio,this.stratY * devicePixelRatio)
            ctx.lineTo(this.endX * devicePixelRatio,this.endY * devicePixelRatio)

            ctx.lineCap = 'round';
            ctx.strokeStyle = this.color;
            ctx.lineWidth = 3 * devicePixelRatio;

            ctx.stroke();

            this.stratX = this.endX;
            this.stratY = this.endY;

        }
        // 点击是否是已创建的图形区域 
        inInside(x,y){
            return false;
        }
    }  

    function getShape(x,y){
        for(let i = shapes.length - 1; i >= 0 ;i--){
            const s = shapes[i];
            if(s.inInside(x,y)){
                return s;
            }
        }
        return null
    }

    // 不断的清空、创建
    function draw(){
        const drowAuto = requestAnimationFrame(draw);  // 会无限调用该函数
        if(isRender){
            if(!isWrite){
                ctx.clearRect(0,0,cvs.width,cvs.height);
            }
            try{
                // 不能用这个重新绘制write
                for(const s of shapes){
                    if(s.type == undefined && s.type != 'write'){
                        s.draw()
                    }
                }
            }
            catch(e){   
                console.log(e)
            }
        }
        // cancelAnimationFrame(drowAuto);
    }
    draw()

    window.addEventListener('beforeunload',function(){
        backbtn.removeEventListener('click')
    })

</script>

简单的一个可以拖动和创建不同图形和手写板的效果,
但是手写板和图形不能共存,还请懂得小伙伴指教~!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值