canvas画图,画矩形,圆形,直线,曲线可拖拽移动

提示:canvas画图,画矩形,圆形,直线,曲线可拖拽移动

文章目录


前言

一、画矩形,圆形,直线,曲线可拖拽移动

test.html

c 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>canvas跟随鼠标移动画透明线</title>
    <style>
        div,canvas,img{
            user-select: none;
        }
        .my_canvas,.bg_img{
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%,-50%);
        }
        .cf{
            content: '';
            display: block;
            overflow: hidden;
            clear: both;
        }
        .fl{
            float: left;
        }
        .fr{
            float: right;
        }
        .bg_img{
            width: 674px;
            height: 495px;
            background: #ddd;
        }
        .img_tools{
            position: absolute;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            border: 1px solid #eee;
            border-radius: 64px;
            height: 64px;
            line-height: 64px;
            box-sizing: border-box;
            padding: 15px 20px 0;
        }
        .img_tool{
           height: 32px;
           line-height: 32px;
           color: #000;
           font-size: 14px;
           text-align: center;
           width: 80px;
           border: 1px solid #ddd;
           border-radius: 32px;
           margin-right: 10px;
           cursor: pointer;
           position: relative;
        }
        .img_tool_active{
            color: #409EFF;
            border: 1px solid #409EFF;
        }
        .show_history{
            position: absolute;
            bottom:0;
            left: 50%;
            transform: translateX(-50%);
        }
        .show_history>img{
            width: 120px;
            margin-right: 10px;
            border: 1px solid #eee;
            border-radius: 4px;
        }
        .canvas_text{
            width: 120px;
            height: 32px;
            line-height: 32px;
            position: absolute;
            top: 0;
            left: 0;
            border: 1px solid #c0c0c0;
            border-radius: 4px;
            font-size: 16px;
            outline: none;
            background: none;
            display: none;
            font-family: Arial, Helvetica, sans-serif;
            padding-left: 0;
            letter-spacing: 0;
        }
    </style>
</head>
<body>
    <div class="bg_img"></div>
    <canvas id="myCanvasBot" class="my_canvas" width="674" height="495"></canvas>
    <canvas id="myCanvasTop" class="my_canvas" width="674" height="495"></canvas>
    <div class="img_tools cf">
        <div class="img_tool fl" onclick="changeType('curve',this)">涂鸦</div>
        <div class="img_tool fl" onclick="changeType('line',this)">直线</div>
        <div class="img_tool fl img_tool_active" onclick="changeType('rect',this)">矩形</div>
        <div class="img_tool fl" onclick="changeType('ellipse',this)">圆形</div>
        <!-- <div class="img_tool fl" onclick="changeType('eraser',this)">橡皮擦</div> -->
        <!-- <div class="img_tool fl" onclick="changeType('text',this)">文字</div> -->
        <!-- <div class="img_tool fl" onclick="changeType('revoke',this)">撤销</div> -->
        <!-- <div class="img_tool fl" onclick="changeType('restore',this)">恢复</div> -->
    </div>
    <input id="canvasText" autofocus class="canvas_text" type="text">
    <div id="showHistory" class="show_history"></div>
    <script>
        const canvasWidth = 674;
        const canvasHeight = 495;
        //底层canvas
        const botCan = document.getElementById('myCanvasBot');
        //顶层canvas
        const topCan = document.getElementById('myCanvasTop');
        //底层画布
        const botCtx = botCan.getContext('2d');
        //顶层画布
        const topCtx = topCan.getContext('2d');
        //鼠标是否按下  是否移动
        let isDown = false,isMove = false;
        //鼠标是否在canvas上抬起
        let isCanUp = false;
        //需要画图的轨迹
        let drawPoints = [];
        //起始点x,y
        let startPoint = {
            x:0,
            y:0
        };
        //图片历史
        let historyList = [];
        //空历史
        historyList.push(new Image())
        //当前绘画历史index
        let historyIndex = -1;
        //icon历史
        // let partHistory = [];
        //操作类型
        let drawType = 'rect';
        //画线宽度
        const lineWidth = 10;
        //文字大小
        const fontSize = 16;
        //画线颜色
        let strokeStyle = 'rgba(255,0,0,0.6)';
        //path2D图形列表
        let pathList = [];
        //path2D单个图形
        let pathObj = null;
        //path2D的唯一标识
        let pathId = 0;
        //当前被激活的path2D
        let activePath = null;
        //是否为拖拽行为
        let isDrag = false;
        //拖拽是否移动
        let isDragMove = false;
        //是否为改变尺寸行为
        isResize = false;
        //改变尺寸点list 
        let pointsList = [];
        //拖拽修改尺寸的点
        let activePoint = null;
        //文字输入框init
        const canvasText = document.getElementById('canvasText');
        canvasText.style.display = 'none';
        canvasText.style.lineHeight = '32px';
        canvasText.style.height = '32px';
        canvasText.style.display = 'none';
        canvasText.style.color = 'none';
        canvasText.addEventListener('blur',()=>{
            topCtx.font = fontSize + 'px Arial, Helvetica, sans-serif';
            let h = parseFloat(canvasText.style.height);
            topCtx.fillText(canvasText.value, startPoint.x+1, startPoint.y+h/2+fontSize/2-1);
            canvasText.style.display = 'none';
            canvasText.value = '';
            topToBot();
        })
        //起始点x,y
        let textPoint = {
            x:0,
            y:0
        };
        //鼠标按下
        const mousedown = (e)=>{
            isDown = true;
            let x = (e||window.event).offsetX;
            let y = (e||window.event).offsetY;
            if(canvasText.style.display == 'none')startPoint = {x,y};
            //检测是否点击到图形
            activePath = isPointInPath(x,y);
            if(activePath){
                isDrag = true;
                topCtx.strokeStyle = topCtx.fillStyle = botCtx.strokeStyle = botCtx.fillStyle = activePath.strokeStyle||strokeStyle;
                topCtx.lineWidth = botCtx.lineWidth = activePath.lineWidth||lineWidth;
                switch (activePath.type){
                    case 'rect':
                        makePathActive();
                        break;
                    case 'ellipse':
                        makePathActive();
                        break;
                    case 'line':
                        makePathActive();
                        break;
                    case 'curve':
                        makePathActive();
                        break;
                }
                
                return;
            }
            if(drawType == 'text'){
                textPoint = {
                    x:x+topCan.offsetLeft-canvasWidth/2,
                    y:y+topCan.offsetTop-canvasHeight/2
                };
                // canvasText.style.height = 32 + 'px';
                canvasText.style.top = textPoint.y+'px';
                canvasText.style.left = textPoint.x+'px';
                canvasText.style.display = 'block';
                canvasText.style.fontSize = fontSize + 'px';
                canvasText.style.color = strokeStyle;
                setTimeout(()=>{
                    canvasText.focus();
                },100)
            }
            if(drawType == 'curve'){
                drawPoints = [];
                drawPoints.push({x,y});
            }
            topCtx.strokeStyle = topCtx.fillStyle = botCtx.strokeStyle = botCtx.fillStyle = strokeStyle;
            topCtx.lineWidth = botCtx.lineWidth = lineWidth;
            topCtx.lineCap = topCtx.lineJoin = botCtx.lineCap = botCtx.lineJoin = 'round';
        }
        //鼠标移动
        const mousemove = (e)=>{
            let x = (e||window.event).offsetX;
            let y = (e||window.event).offsetY;
            let distanceX = 0;
            let distanceY = 0;
            if(isDown){
                isMove = true;
                if(isDrag){
                    isDragMove = true;
                    switch(activePath.type){
                        case 'curve':
                            distanceX = x - startPoint.x;
                            distanceY = y - startPoint.y;
                            let newPoints = [];
                            for(let i=0;i<activePath.drawPoints.length;i++){
                                let drawPoint = activePath.drawPoints[i];
                                newPoints.push({x:drawPoint.x + distanceX,y:drawPoint.y + distanceY});
                            }
                            drawCurve(newPoints);
                            break;
                        case 'line':
                            distanceX = x - startPoint.x;
                            distanceY = y - startPoint.y;
                            drawLine(activePath.startX + distanceX,activePath.startY + distanceY,activePath.x + distanceX,activePath.y + distanceY,);
                            break;
                        case 'eraser':
                            // drawEraser(x,y);
                            break;
                        case 'rect':
                            // xy 为当前point的坐标
                            // startPoint为点击的矩形上点   查看当前point.x点距离startPoint.x移动了多少  point.y点距离startPoint.y移动了多少
                            drawRect(activePath.x + (x - startPoint.x),activePath.y + (y - startPoint.y),activePath.width,activePath.height);
                            break;
                        case 'ellipse':
                            // drawEllipse(x,y);
                            drawEllipse(activePath.x + (x - startPoint.x),activePath.y + (y - startPoint.y),activePath.radiusX,activePath.radiusY);
                            break;
                    }
                    return;
                }
                switch(drawType){
                    case 'curve':
                        drawPoints.push({x,y});
                        drawCurve(drawPoints);
                        break;
                    case 'line':
                        drawLine(startPoint.x,startPoint.y,x,y);
                        break;
                    case 'eraser':
                        drawEraser(x,y);
                        break;
                    case 'rect':
                        // drawRect(x,y);
                        drawRect(startPoint.x, startPoint.y, x-startPoint.x, y - startPoint.y);
                        break;
                    case 'ellipse':
                        drawEllipse((x+startPoint.x)/2, (y+startPoint.y)/2, Math.abs((x-startPoint.x)/2), Math.abs((y-startPoint.y)/2),0,0, Math.PI*2,true);
                        break;
                }
            }
        }
        //鼠标抬起
        const mouseup = (e)=>{
            isCanUp = true;
            if(isDown){
                isDown = false
                // topCan内容画到botCan上
                if(isDrag){
                    isDrag = false;
                    activePath = null;
                    if(isDragMove){
                        isDragMove = false;
                        pathList.pop();
                    }else{
                        pathObj = pathList.pop();
                    }
                    topToBot();
                    return
                }
                if(drawType!='text')topToBot();
            }
        }
        //topCan内容画到botCan上
        const topToBot = ()=>{
            if(pathObj){
                pathObj.id = pathId++;
                pathList.push(pathObj);
                topCtx.clearRect(0,0,canvasWidth,canvasHeight);
                if(isCanUp)isCanUp=false;
                botCtx[pathObj.shape](pathObj.path);
                pathObj = null;
            }
            drawPoints = [];
            isDown = false;
            isMove = false;
        }
        //判断是否点击到图形
        const isPointInPath = (x,y)=>{
            let PointInPath = null;
            for(let i=0;i<pathList.length;i++){
                let path = pathList[i];
                if(botCtx.isPointInStroke(path.path,x,y)){
                    PointInPath = path;
                    break;
                }
            }
            return PointInPath;
        }
        //激活rect图形轮廓
        const makePathActive = ()=>{
            botCtx.clearRect(0,0,canvasWidth,canvasHeight);
            let arr = [];
            for(let i=0;i<pathList.length;i++){
                let path = pathList[i] 
                if(activePath.id != path.id){
                    botCtx[path.shape](path.path);
                    arr.push(path);
                }else{
                    topCtx[path.shape](path.path);
                }   
            }
            arr.push(activePath);
            pathList = arr;
        }
        //画椭圆形
        const drawEllipse = (x,y,radiusX,radiusY)=>{
            //清除topCtx画布
            topCtx.clearRect(0,0,canvasWidth,canvasHeight);
            topCtx.beginPath();
            let path = new Path2D();
            // 椭圆
            path.ellipse(x,y,radiusX,radiusY,0,0, Math.PI*2,true);
            topCtx.stroke(path);
            pathObj = {
                type:'ellipse',
                shape:'stroke',
                path,
                x, 
                y, 
                radiusX, 
                radiusY,
                lineWidth:topCtx.lineWidth||lineWidth,
                strokeStyle:topCtx.strokeStyle||strokeStyle
            };
        }
        //画矩形
        const drawRect = (x,y,width,height)=>{
            //清除topCtx画布
            topCtx.clearRect(0,0,canvasWidth,canvasHeight);
            topCtx.beginPath();
            let path = new Path2D();
            // 矩形
            path.rect(x,y,width,height);
            topCtx.stroke(path);
            pathObj = {
                type:'rect',
                shape:'stroke',
                path,
                x, 
                y, 
                width, 
                height,
                lineWidth:topCtx.lineWidth||lineWidth,
                strokeStyle:topCtx.strokeStyle||strokeStyle
            };
        }
        //橡皮擦
        const drawEraser = (x,y)=>{
            //橡皮擦圆形半径
            const radius = lineWidth/2;
            botCtx.beginPath(); 
            for(let i=0;i<radius*2;i++){
                //勾股定理高h
                let h = Math.abs( radius - i); //i>radius h = i-radius; i<radius  h = radius - i
                //勾股定理l
                let l = Math.sqrt(radius*radius -h*h); 
                //矩形高度
                let rectHeight = 1;
                 //矩形宽度
                let rectWidth = 2*l;
                //矩形X
                let rectX = x-l;
                //矩形Y
                let rectY = y-radius + i;

                botCtx.clearRect(rectX, rectY, rectWidth, rectHeight);
            }
        }
        //画透明度直线
        const drawLine = (startX,startY,x,y)=>{
            if(!isDown)return;
            //清空当前画布内容
            topCtx.clearRect(0,0,canvasWidth,canvasHeight);
            //必须每次都beginPath  不然会卡
            topCtx.beginPath();
            let path = new Path2D();
            path.moveTo(startX,startY);
            path.lineTo(x,y);
            topCtx.stroke(path);
            pathObj = {
                type:'line',
                shape:'stroke',
                path,
                x, 
                y, 
                startX, 
                startY,
                lineWidth:topCtx.lineWidth||lineWidth,
                strokeStyle:topCtx.strokeStyle||strokeStyle
            };
        }
        //画带透明度涂鸦
        const drawCurve = (drawPointsParams)=>{
            // drawPoints.push({x,y});
            if(!drawPointsParams||drawPointsParams.length<1)return
            //清空当前画布内容
            topCtx.clearRect(0,0,canvasWidth,canvasHeight);
            //必须每次都beginPath  不然会卡
            topCtx.beginPath();
            let path = new Path2D();
            path.moveTo(drawPointsParams[0].x,drawPointsParams[0].y);
            for(let i=1;i<drawPointsParams.length;i++){
                path.lineTo(drawPointsParams[i].x,drawPointsParams[i].y);
            }
            topCtx.stroke(path);
            pathObj = {
                type:'curve',
                shape:'stroke',
                path,
                drawPoints:drawPointsParams,
                lineWidth:topCtx.lineWidth||lineWidth,
                strokeStyle:topCtx.strokeStyle||strokeStyle
            };
            
        }
        //切换操作
        const changeType = (type,that)=>{
            // if(drawType == type) return;
            let tools = document.getElementsByClassName('img_tool');
            for(let i=0;i<tools.length;i++){
                let ele = tools[i];
                if(ele.classList.contains('img_tool_active'))ele.classList.remove('img_tool_active');
            }
            that.classList.add('img_tool_active');
            drawType = type;
            //撤销
            if(drawType == 'revoke'){
                if(historyIndex>0){
                    historyIndex--;
                    drawImage(historyList[historyIndex]);
                }
            //恢复
            }else if(drawType == 'restore'){
                if(historyIndex<historyList.length - 1){
                    historyIndex++;
                    drawImage(historyList[historyIndex]);
                }
            }
        }
        const drawImage = (img)=>{
            botCtx.clearRect(0,0,canvasWidth,canvasHeight);
            botCtx.drawImage(img,0,0);
        }

        //canvas添加鼠标事件
        topCan.addEventListener('mousedown',mousedown);
        topCan.addEventListener('mousemove',mousemove);
        topCan.addEventListener('mouseup',mouseup);
        //全局添加鼠标抬起事件
        document.addEventListener('mouseup',(e)=>{
            let x = (e||window.event).offsetX;
            let y = (e||window.event).offsetY;
            let classList = (e.target || {}).classList || [];
            if(classList.contains('img_tool'))return;
            if(!isCanUp){
                isDown = false;
                // topCan内容画到botCan上
                if(isDrag){
                    isDrag = false;
                    activePath = null;
                    if(isDragMove){
                        isDragMove = false;
                        pathList.pop();
                    }else{
                        pathObj = pathList.pop();
                    }
                    topToBot();
                    return
                }
                if(drawType == 'line'&&!isDrag){
                    let clientX = topCan.getBoundingClientRect().x;
                    let clientY = topCan.getBoundingClientRect().y;
                    drawLine(startPoint.x,startPoint.y,x-clientX,y-clientY);
                }
                // topCan内容画到botCan上
                topToBot();
            }
        });
        //全局添加鼠标移动事件
        document.addEventListener('mousemove',(e)=>{
            if(isMove)return isMove = false;
            let x = (e||window.event).offsetX;
            let y = (e||window.event).offsetY;
            if(drawType == 'line'&&!isDrag){
                let clientX = topCan.getBoundingClientRect().x;
                let clientY = topCan.getBoundingClientRect().y;
                drawLine(startPoint.x,startPoint.y,x-clientX,y-clientY);
            }
        });
    </script>
</body>
</html>

总结

踩坑路漫漫长@~@

相关推荐
吃杠碰小鸡4 分钟前
高中数学-数列-导数证明
前端·数学·算法
kingwebo'sZone9 分钟前
C#使用Aspose.Words把 word转成图片
前端·c#·word
xjt_090129 分钟前
基于 Vue 3 构建企业级 Web Components 组件库
前端·javascript·vue.js
我是伪码农40 分钟前
Vue 2.3
前端·javascript·vue.js
夜郎king1 小时前
HTML5 SVG 实现日出日落动画与实时天气可视化
前端·html5·svg 日出日落
辰风沐阳1 小时前
JavaScript 的宏任务和微任务
javascript
夏幻灵2 小时前
HTML5里最常用的十大标签
前端·html·html5
冰暮流星2 小时前
javascript之二重循环练习
开发语言·javascript·数据库
Mr Xu_2 小时前
Vue 3 中 watch 的使用详解:监听响应式数据变化的利器
前端·javascript·vue.js
未来龙皇小蓝2 小时前
RBAC前端架构-01:项目初始化
前端·架构