由于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的颜色选择器,代码较多,贴上来也不能执行,就不贴上来了