实现canvas动态画图的预览效果,圆、直线、矩形

本文介绍如何利用div元素实现canvas上绘制直线、矩形和圆形的实时预览效果,解决了canvas无法部分清除的问题,同时探讨了事件冲突及解决方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

由于canvas只能全部清除,不能清除某一次画的内容,因此用一个div来实现预览效果。

1.直线

将div的宽度设置为1,高度即为两点之间的像素距离,然后以水平为参照,起点和终点连线与水平面的角度差,就是div要旋转的补角,将div按照一定角度进行旋转即可。

2.矩形

矩形拖动的预览效果实现比较简单,因为div就是一个矩形,鼠标在canvas上鼠标按下的位置就是div的一个角,松开鼠标的位置是另一个角。

3.圆

我的实现方式是将div的css属性border-radius设置为50%,以鼠标按下的点,到鼠标当前位置的连线,作为圆的直径,那么就需要用到数学中的几何知识去计算出div的位置,算法不做描述,代码中已经实现。

附上计算坐标的草图:

画矩形和圆形这里面有一个问题,我画了一点时间去解决,就是鼠标的事件是绑定在canvas上的,鼠标移动把图形缩小时,预览的div层会挡住canvas,导致canvas的鼠标事件不触发,我的解决方法是给div层绑定事件,鼠标在上面移动时将div隐藏,在canvas上移动时将div显示,因此缩小图形时,div看上去会不停的闪烁。

同理,当鼠标松开时结束当前绘画操作,此时如果鼠标不是位于canvas上,而是位于div上,会导致无法触发鼠标在canvas上的释放事件,因此我给div层也绑定了鼠标释放事件。因为canvas和div都是绝对定位,我可以根据div和canvas的绝对定位的差值(dx,dy),以及鼠标在div上的相对位置(x1,y1),即可求出(x0,y0)和实际定位差:

实际x差值 = x1 + dx

实际y差值 = y1 + dy

也就可以算出div的位置了,下面是演示的页面代码:

<!DOCTYPE html>
<html style="height: 100%;width:100%">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="Content-Type" content="multipart/form-data; charset=utf-8" />
    <title>首页</title>
    <script type="text/javascript" src="${basePath}/static/game/canvas/canvas.js"></script>
    <link rel="stylesheet" type="text/css" href="${basePath}/static/game/canvas/canvas.css"/>
</head>
<body class="full flex flex-lie" style="overflow-y: hidden">
<div class="canvas-tool flex flex-row">
    <img class="canvas-tool-item active" src="${basePath}/static/game/canvas/pen.png" onclick="changeMode(this,1)" title="画笔"/>
    <img class="canvas-tool-item" src="${basePath}/static/game/canvas/line.png" onclick="changeMode(this,3)" title="直线"/>
    
    <img class="canvas-tool-item" src="${basePath}/static/game/canvas/rectangle.png" onclick="changeMode(this,4)" title="矩形"/>
    <img class="canvas-tool-item" src="${basePath}/static/game/canvas/circle.png" onclick="changeMode(this,2)" title="圆"/>
    <div class="canvas-tool-item" id="colors" style="margin-left: 30px;" title="选颜色"></div>

</div>
<div class="canvas-board" style="text-align:center;">
    <canvas id="myCanvas1" style="border:1px solid gray;margin:0 auto">
        您的浏览器不支持 HTML5 canvas 标签。
    </canvas>
</div>
</body>
<script>
    var currMode = 1;
    var sett = {};
    sett.width = window.innerWidth;
    sett.height = window.innerHeight - 50;
    sett.mode = currMode;
    function changeMode(obj,mode){
        sett.mode=mode;
        canvas.enterCanvasMode("myCanvas1",mode);
        $(".canvas-tool-item").removeClass("active");
        $(obj).addClass('active');
    }
    function changeColor(color){
        sett.penColor = color;
        canvas.enableSetting("myCanvas1",{
            penColor:color,
        });
        canvas.enterCanvasMode("myCanvas1");
    }
    $(function(){
        canvas.beginCanvas("myCanvas1",sett);
        /*LAY_UTIL.loadColor({
            $select:'#colors',
            color:'black',
            done:changeColor
        });*/
    })

</script>
</html>

canvas.js代码如下:

var canvas = {};
/***************************几种画图模式********************************/
canvas.modes = {};
canvas.modes.FREEDOM_DRAW=1;//自由画线
canvas.modes.DRAW_CIRCLE=2;//自由画圆
canvas.modes.DRAW_LINE=3;//自由画直线
canvas.modes.DRAW_RECT=4;//自由画矩形
canvas.modes.ERASE=0;//自由擦除
/***********************************************************************/
canvas.settingMap={};
canvas.contextMap={};
canvas.defaultSetting={
	mode:canvas.modes.FREEDOM_DRAW,
	penColor:"black",
	bgColor:"white",
	lineWidth:1,
	width:"400px",
	height:"400px",
};
//获得setting
canvas.composeSetting = function(domId,setting){
	if(StringUtil.isNotEmpty($("#"+domId))){
		if(StringUtil.isEmpty(canvas.settingMap[domId])){
			//不能直接引用canvas.defaultSetting,会覆盖canvas.defaultSetting
			canvas.settingMap[domId]=JSON.parse(JSON.stringify(canvas.defaultSetting));
		}
		if(StringUtil.isNotEmpty(setting)){
			for(var key in setting){
				canvas.settingMap[domId][key]=setting[key];
			}
		}
	}
};

canvas.size = function(domId,wh){
	var cvs=document.getElementById(domId);
	var width = StringUtil.safeToString(wh[0],"400px");
	var height = StringUtil.safeToString(wh[1],"400px");
	var nc = document.createElement("canvas");
	var ctx = canvas.getContext(domId);
	cvs.width = width;
	cvs.height = height;
	nc.width = width;
	nc.height = height;

	nc.getContext("2d").drawImage(cvs,0,0);
	ctx.width = width;
	ctx.height = height;
	ctx.drawImage(nc,0,0);


};
//使外部setting生效
canvas.enableSetting = function(domId,setting){
	var ctx= canvas.getContext(domId);
	if(setting !== undefined){
		for(var key in setting){
			canvas.settingMap[domId][key]=setting[key];
		}
	}
	$("#"+domId).css("background",canvas.settingMap[domId]['bgColor']);
	ctx.strokeStyle=canvas.settingMap[domId]['penColor'];
	ctx.lineWidth=canvas.settingMap[domId]['lineWidth'];
	ctx.beginPath();
};
canvas.getContext = function(domId){
	var ctx = canvas.contextMap[domId];
	if(ctx === undefined){
		var cvs=document.getElementById(domId);
		ctx = cvs.getContext("2d");
		canvas.contextMap[domId] = ctx;
	}
	return ctx;
};
canvas.enterCanvasMode = function(domId,md){
	if(md !== undefined){
		canvas.settingMap[domId]['mode'] = parseInt(md);
	}
	var mode=canvas.settingMap[domId]['mode'];
	var ctx = canvas.getContext(domId);
	ctx.strokeStyle=canvas.settingMap[domId]['penColor'];
	ctx.lineWidth=canvas.settingMap[domId]['lineWidth'];
	if(mode===canvas.modes.FREEDOM_DRAW){
		canvas.freedomCanvas(domId);
	}else if(mode===canvas.modes.DRAW_CIRCLE){
		canvas.drawCircle(domId);
	}else if(mode===canvas.modes.DRAW_LINE){
		canvas.drawLine(domId);
	}else if(mode===canvas.modes.DRAW_RECT){
		canvas.drawRect(domId);
	}else if(mode===canvas.modes.ERASE){
		canvas.erase(domId);
	}
};
canvas.initCanvas = function(domId,setting) {
	canvas.composeSetting(domId,setting);
	canvas.enableSetting(domId);
	var cvs=document.getElementById(domId);
	var width = StringUtil.safeToString(canvas.settingMap[domId]['width'],"400px");
	var height = StringUtil.safeToString(canvas.settingMap[domId]['height'],"400px");
	cvs.width = width;
	cvs.height = height;
	canvas.contextMap[domId] = cvs.getContext("2d");
};
canvas.beginCanvas = function(domId,setting){
	try{
		canvas.initCanvas(domId,setting);

		canvas.enterCanvasMode(domId);
		
	}catch(e){
		console.log("canvas初始化失败!");
		console.error(e);
	}
	
};

/*************************************************************************************************************************/



//自由画线初始化
canvas.freedomCanvas = function(domId){
	if(StringUtil.isNotEmpty($("#"+domId))){
		$("#"+domId).unbind();
		var cvs=document.getElementById(domId);
		var ctx=canvas.getContext(domId);
		var status = false;
		$(cvs).mousedown(function(event){
			status = true;
			ctx.moveTo(event.offsetX,event.offsetY);
			ctx.stroke();
		});	
		$(cvs).mouseup(function(event){
			status=false;
			ctx.stroke();
		});
		$(cvs).mousemove(function(event){
			if(status){
				ctx.lineTo(event.offsetX,event.offsetY);
				ctx.stroke();
			}
		});
		$(cvs).mouseleave(function(event){
			status=false;
		})
	}
};
//自由擦除
canvas.erase = function(domId){
	if($("#"+domId).length>0){
		$("#"+domId).unbind();
		var cvs=document.getElementById(domId);
		var ctx=canvas.getContext(domId);
		ctx.strokeStyle=canvas.settingMap[domId]['bgColor'];
		var status =false;
		$(cvs).mousedown(function(event){
			status =true;
			ctx.moveTo(event.offsetX,event.offsetY);
			ctx.stroke();
		});	
		$(cvs).mouseup(function(event){
			ctx.stroke();
			status =false;
		});
		$(cvs).mousemove(function(event){
			if(status){
				ctx.lineTo(event.offsetX,event.offsetY);
				ctx.stroke();	
			}
		});
		$(cvs).mouseleave(function(event){
			status =false;
		})
	}
};
//自由画矩形初始化
canvas.drawRect= function(domId){
	if(StringUtil.isNotEmpty($("#"+domId))){
		$("#"+domId).unbind();
		var cvs=document.getElementById(domId);
		var ctx=canvas.getContext(domId);
		var status =false;
		var x0=0;
		var y0=0;
		var zIndex=$(cvs).css("zIndex");
		$("#"+domId+"_tmp").remove();
		var tmpDiv=$("<div style='position:absolute;width:1px;height:1px;display:none;background:"+canvas.settingMap[domId]['penColor']+";opacity:0.2;border:"+
				StringUtil.safeToString(canvas.settingMap[domId]['lineWidth'],1)+"px solid "+canvas.settingMap[domId]['penColor']+";z-index:"+zIndex+";'>");
		var left=0;
		var top=0;
		$(tmpDiv).appendTo("body");
		$(tmpDiv).mousemove(function(event){
			$(tmpDiv).hide();
		});
		var onMouseUp = function(event,m){
			status =false;
			if(m !== undefined){
				ctx.rect(x0,y0,$(tmpDiv).offset().left + event.offsetX-x0,
					$(tmpDiv).offset().top-50 + event.offsetY-y0);
			}else{
				ctx.rect(x0,y0,event.offsetX-x0,event.offsetY-y0);
			}

			ctx.stroke();
			$(tmpDiv).hide();
		};
		$(tmpDiv).mouseup(function(e){
			onMouseUp(e,'asd');
		});
		$(cvs).mousedown(function(event){
			status =true;
			x0=event.offsetX;
			y0=event.offsetY;
			left=event.pageX;
			top=event.pageY;
			ctx.moveTo(event.offsetX,event.offsetY);
			$(tmpDiv).css({
				"left":left+"px",
				"top":top+"px",
				"width":(event.offsetX-x0)+"px",
				"height":(event.offsetY-y0)+"px"
			});
			$(tmpDiv).show();
		});	
		$(cvs).mouseup(onMouseUp);
		$(cvs).mousemove(function(event){
			if(status){
				$(tmpDiv).show();
				if(event.offsetX>=x0 && event.offsetY>=y0){
					$(tmpDiv).css({
						"left":left+"px",
						"top":top+"px",
						"width":(event.offsetX-x0)+"px",
						"height":(event.offsetY-y0)+"px"
					});
				}else if(event.offsetX>=x0 && event.offsetY<y0){
					$(tmpDiv).css({
						"left":left+"px",
						"top":(top-(y0-event.offsetY))+"px",
						"width":(event.offsetX-x0)+"px",
						"height":(y0-event.offsetY)+"px"
					});
				}else if(event.offsetX<x0 && event.offsetY>=y0){
					$(tmpDiv).css({
						"left":(left-(x0-event.offsetX))+"px",
						"top":top+"px",
						"width":(x0-event.offsetX)+"px",
						"height":(event.offsetY-y0)+"px"
					});
				}else if(event.offsetX<x0 && event.offsetY<y0){
					$(tmpDiv).css({
						"left":(left-(x0-event.offsetX))+"px",
						"top":(top-(y0-event.offsetY))+"px",
						"width":(x0-event.offsetX)+"px",
						"height":(y0-event.offsetY)+"px"
					});
				}
			}
		});
		$(cvs).mouseleave(function(event){
			//mouseDownMap[domId]=false;
		})
	}
};
//自由画直线初始化
canvas.drawLine = function(domId){
	if($("#"+domId).length>0){
		$("#"+domId).unbind();
		var cvs=document.getElementById(domId);
		var ctx=canvas.getContext(domId);
		ctx.moveTo(0,0);
		var x0=0;
		var y0=0;
		var status=false;
		var zIndex=$(cvs).css("zIndex");
		var tmpDiv=$("<div style='position:absolute;width:1px;height:1px;display:none;background:"+canvas.settingMap[domId]['penColor']+";z-index:"+zIndex+";'>");
		var left=0;
		var top=0;
		$(tmpDiv).appendTo("body");
		$(cvs).mousedown(function(event){
			status=true;
			x0=event.offsetX;
			y0=event.offsetY;
			left=event.pageX;
			top=event.pageY;
			$(tmpDiv).css({
				"left":left+"px",
				"top":top+"px",
				"width":"1px",
				"height":"1px"
			});
			$(tmpDiv).show();
		});	
		$(cvs).mouseup(function(event){
			if(status){
				$(tmpDiv).hide();
				ctx.moveTo(event.offsetX,event.offsetY);
				ctx.moveTo(x0,y0);
				ctx.lineTo(event.offsetX,event.offsetY);
				ctx.stroke();
			}
			status=false;
		});
		$(cvs).mousemove(function(event){
			if(status){
				var dx=	Math.abs(x0-event.offsetX);
				var dy=	Math.abs(y0-event.offsetY);
				var d=Math.sqrt(dx*dx+dy*dy);
				if(event.offsetX>=x0 && event.offsetY>=y0){
					$(tmpDiv).width(canvas.settingMap[domId]['lineWidth']);
					$(tmpDiv).height(dy-2);
					var arc=Math.acos((d*d+dy*dy-dx*dx)/2/d/dy)*360/2/Math.PI;
					$(tmpDiv).css({
						"left":(left+dx/2)+"px",
						"top":top+"px",
						"webkitTransform":"skewX("+arc+"deg)",
						"mozTransform":"skewX("+arc+"deg)",
						"oTransform":"skewX("+arc+"deg)",
					})
				}else if(event.offsetX>=x0 && event.offsetY<y0){
					$(tmpDiv).width(dx-2);
					$(tmpDiv).height(canvas.settingMap[domId]['lineWidth']);
					var arcy=-Math.acos((d*d+dx*dx-dy*dy)/2/d/dx)*360/2/Math.PI;
					$(tmpDiv).css({
						"left":left+"px",
						"top":(top-dy/2)+"px",
						"webkitTransform":"skewY("+arcy+"deg)",
						"mozTransform":"skewY("+arcy+"deg)",
						"oTransform":"skewY("+arcy+"deg)",
					})
				}else if(event.offsetX<x0 && event.offsetY>=y0){
					$(tmpDiv).width(canvas.settingMap[domId]['lineWidth']);
					$(tmpDiv).height(dy-2);
					var arcx=-(90-Math.acos((d*d+dx*dx-dy*dy)/2/d/dx)*360/2/Math.PI);
					$(tmpDiv).css({
						"left":(left-dx/2)+"px",
						"top":top+"px",
						"webkitTransform":"skewX("+arcx+"deg)",
						"mozTransform":"skewX("+arcx+"deg)",
						"oTransform":"skewX("+arcx+"deg)",
					})
				}else if(event.offsetX<x0 && event.offsetY<y0){
					$(tmpDiv).width(canvas.settingMap[domId]['lineWidth']);
					$(tmpDiv).height(dy-2);
					var arcx=-90-Math.acos((d*d+dx*dx-dy*dy)/2/d/dx)*360/2/Math.PI;
					$(tmpDiv).css({
						"left":(left-dx/2)+"px",
						"top":(top-dy)+"px",
						"webkitTransform":"skewX("+arcx+"deg)",
						"mozTransform":"skewX("+arcx+"deg)",
						"oTransform":"skewX("+arcx+"deg)",
					})
				}
			}
		});
		$(cvs).mouseleave(function(event){
			//mouseDownMap[domId]=false;
		})
	}
};
//自由画圆初始化
canvas.drawCircle = function(domId){
	if($("#"+domId).length>0){
		$("#"+domId).unbind();
		var cvs=document.getElementById(domId);
		var ctx=canvas.getContext(domId);
		var x0=0;
		var y0=0;
		var status =false;
		//border-radius: 50%;
		var zIndex=$(cvs).css("zIndex");
		$("#"+domId+"_tmp").remove();
		var tmpDiv=$("<div id='"+domId+"_tmp' "+
			"style='position:fixed;border-radius: 50%;border:"+canvas.settingMap[domId]['lineWidth']+"px solid "+canvas.settingMap[domId]['penColor']+";display:none;opacity:0.2;width:0px;height:0px;background:"+canvas.settingMap[domId]['penColor']+";z-index:"+zIndex+";' >");
		$(tmpDiv).appendTo("body");
		var onMouseUp = function(event,m){
			if(status){
				var x;
				var y;
				var dx;
				var dy;
				var r;
				if(m !== undefined){
					x = (x0+$(tmpDiv).offset().left + event.offsetX)/2;
					y = (y0+$(tmpDiv).offset().top-50 + event.offsetY)/2;
					dx=	Math.abs(x0-$(tmpDiv).offset().left - event.offsetX);
					dy=	Math.abs(y0-$(tmpDiv).offset().top+50 - event.offsetY);
					//半径
					r =Math.sqrt(dx*dx+dy*dy)/2;
				}else{
					//圆心坐标
					x=(x0+event.offsetX)/2;
					y=(y0+event.offsetY)/2;
					//鼠标点击和释放坐标差
					dx=	Math.abs(x0-event.offsetX);
					dy=	Math.abs(y0-event.offsetY);
					//半径
					r =Math.sqrt(dx*dx+dy*dy)/2;
				}


				//console.log(x,y,dx,dy,r);
				ctx.beginPath();
				ctx.arc(x,y,r,0,2*Math.PI);
				ctx.stroke();
				$(tmpDiv).hide();
			}
			status=false;
		};

		$(tmpDiv).mouseup(function(e){
			onMouseUp(e,'asd');
		});
		$(tmpDiv).mousemove(function(e){
			$(tmpDiv).hide();
		});

		$(cvs).mousedown(function(event){
			status=true;
			$(tmpDiv).width(0);
			$(tmpDiv).height(0);
			//$(tmpDiv).show();
			ctx.moveTo(event.offsetX,event.offsetY);
			x0=event.offsetX;
			y0=event.offsetY;
			//console.log(x0,y0);
			$(tmpDiv).css("left",x0+"px");
			$(tmpDiv).css("top",y0+"px");
		});	
		$(cvs).mouseup(onMouseUp);
		$(cvs).mousemove(function(event){
			if(status){
				$(tmpDiv).show();
				//console.log($(tmpDiv).offset().left-x0);
				var x=(x0+event.offsetX)/2;
				var y=(y0+event.offsetY)/2;
				var dx=	Math.abs(x0-event.offsetX);
				var dy=	Math.abs(y0-event.offsetY);
				var r=Math.sqrt(dx*dx+dy*dy)/2;
				$(tmpDiv).width(2*r-canvas.settingMap[domId]['lineWidth']-canvas.settingMap[domId]['lineWidth']);
				$(tmpDiv).height(2*r-canvas.settingMap[domId]['lineWidth']-canvas.settingMap[domId]['lineWidth']);
				//计算并设置div的位置
				if(event.offsetX>=x0&&event.offsetY>=y0){
					$(tmpDiv).css("left",(event.pageX-(2*r-r+dx/2))+"px");
					$(tmpDiv).css("top",(event.pageY-(2*r-r+dy/2))+"px");
				}else if(event.offsetX<x0&&event.offsetY<y0){
					$(tmpDiv).css("left",(event.pageX-(r-dx/2))+"px");
					$(tmpDiv).css("top",(event.pageY-(r-dy/2))+"px");
				}else if(event.offsetX>x0&&event.offsetY<y0){
					$(tmpDiv).css("left",(event.pageX-(2*r-r+dx/2))+"px");
					$(tmpDiv).css("top",(event.pageY-(r-dy/2))+"px");
				}else if(event.offsetX<x0&&event.offsetY>y0){
					$(tmpDiv).css("left",(event.pageX-(r-dx/2))+"px");
					$(tmpDiv).css("top",(event.pageY-(2*r-r+dy/2))+"px");
				}

				
				
			}
		});
		$(cvs).mouseleave(function(event){
			//mouseDownMap[domId]=false;
		})
	}
};

 StringUtil.js

https://blog.youkuaiyun.com/qq_36635569/article/details/106398341

canvas.css

.canvas-board{
    position: fixed;top:50px;left:0;
}
.canvas-tool{
    position: fixed;height:40px;top:0;left:0;padding-left: 5px;
}
.canvas-tool .canvas-tool-item{
    width:50px;height:50px;padding: 5px;cursor: pointer;box-sizing: border-box;line-height: 50px;
}
.canvas-tool .canvas-tool-item.active{
    border: 1px solid orange;
}
.full{width:100%;height:100%;}
.flex{display:flex;}

.flex-1{order:1;}
.flex-2{order:2}
.flex-3{order:3}
.flex-auto{flex: 1;overflow: auto}
.height-auto{height: auto}
.flex-row{flex-direction:row}
.flex-row-reverse{flex-direction: row-reverse}
.flex-row .flex-child{height: 100%;}
.flex-lie {flex-direction:column}
.flex-lie .flex-child{width: 100%;}

 

 效果图:

 

 使用了layui的颜色选择器,代码较多,贴上来也不能执行,就不贴上来了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值