提示:canvas画图,画矩形,圆形,直线,曲线可拖拽移动
文章目录
- 前言
- 一、画矩形,圆形,直线,曲线可拖拽移动
- 总结
前言
一、画矩形,圆形,直线,曲线可拖拽移动
test.html
<!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;
//resize是否移动
let isResizeMove = false;
//改变尺寸点
let resizePath = null;
//改变尺寸点List
let resizePointList = [];
//文字输入框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};
//检查是否点击到resize的点
activePoint = isResizePointInPath(x,y);
if(activePoint){
isResize = true;
activePath = activePoint.activePath;//原有path,需要清除
//可resize,清除resize点
topCtx.clearRect(0,0,canvasWidth,canvasHeight);
switch (activePoint.type){
case 'rect':
makePathActive();
break;
case 'ellipse':
makePathActive();
break;
case 'line':
makePathActive();
break;
}
return
}
//检测是否点击到图形
activePath = isPointInPath(x,y);
if(activePath){
createResizePoint(activePath); //只有点击到图形的时候,才添加图形resize的点
isDrag = true;
topCtx.strokeStyle = topCtx.fillStyle = botCtx.strokeStyle = botCtx.fillStyle = activePath.strokeStyle||strokeStyle;
topCtx.lineWidth = botCtx.lineWidth = activePath.lineWidth||lineWidth;
topCtx.clearRect(0,0,canvasWidth,canvasHeight);
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(isResize){
isResizeMove = true;
if(activePoint&&activePoint.resizeFun){
activePoint.resizeFun(x,y);
}
return
}
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(isResize){
isResize = false;
activePath = null;
resizePointList = [];
if(isResizeMove){
isResizeMove = false;
pathList.pop();
}else{
pathObj = pathList.pop();
}
topToBot();
return;
}
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);
//如果有resizePoints
if(pathObj.points&&pathObj.points.length>0){
drawResizePoint(pathObj.points);
}
pathObj.points = [];
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;
}
//判断是否点击到图形
const isResizePointInPath = (x,y)=>{
let point = null;
for(let i=0;i<resizePointList.length;i++){
let resizePoint = resizePointList[i];
if(botCtx.isPointInStroke(resizePoint.path,x,y)){
point = resizePoint;
break;
}
}
return point;
}
//激活rect图形轮廓 botCtx删除activePath topCtx添加activePath
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.strokeStyle = path.strokeStyle;
topCtx[path.shape](path.path);
//创建可拖动的点
}
}
arr.push(activePath);
pathList = arr;
}
//创建resize的点
const createResizePoint = ()=>{
activePath.points = [];
let ponitFillStyle = 'rgba(0,0,0,0.8)';
let pointWidth = 10;
let pointHeight = 10;
switch(activePath.type){
case 'rect':
activePath.points = [
{
x:activePath.x-pointWidth/2,
y:activePath.y-pointHeight/2,
width:10,height:10,ponitFillStyle,type:activePath.type,id:0,activePath:activePath, //用于删除原activePath
resizeFun:(x,y)=>{ drawRect(x,y,activePath.x + activePath.width-x,activePath.y + activePath.height-y); },
},
{
x:activePath.x+activePath.width/2-pointWidth/2,
y:activePath.y-pointHeight/2,
width:10,height:10,ponitFillStyle,type:activePath.type,id:1,activePath:activePath,
resizeFun:(x,y)=>{
drawRect(activePath.x,y,activePath.width,activePath.height+activePath.y-y);
}
},
{
x:activePath.x+activePath.width-pointWidth/2,
y:activePath.y-pointHeight/2,
width:10,height:10,ponitFillStyle,type:activePath.type,id:2,activePath:activePath,
resizeFun:(x,y)=>{drawRect(activePath.x, y ,x-activePath.x, activePath.height+ activePath.y - y);}
},
{
x:activePath.x+activePath.width-pointWidth/2,
y:activePath.y+activePath.height/2-pointHeight/2,
width:10,height:10,ponitFillStyle,type:activePath.type,id:3,activePath:activePath,
resizeFun:(x,y)=>{
drawRect(activePath.x, activePath.y ,x-activePath.x, activePath.height);
}
},
{
x:activePath.x+activePath.width-pointWidth/2,
y:activePath.y+activePath.height-pointHeight/2,
width:10,height:10,ponitFillStyle,type:activePath.type,id:4,activePath:activePath,
resizeFun:(x,y)=>{
drawRect(activePath.x, activePath.y ,x-activePath.x, y-activePath.y);
}
},
{
x:activePath.x+activePath.width/2-pointWidth/2,
y:activePath.y+activePath.height-pointHeight/2,
width:10,height:10,ponitFillStyle,type:activePath.type,id:5,activePath:activePath,
resizeFun:(x,y)=>{
drawRect(activePath.x, activePath.y ,activePath.width, y-activePath.y);
}
},
{
x:activePath.x-pointWidth/2,
y:activePath.y+activePath.height-pointHeight/2,
width:10,height:10,ponitFillStyle,type:activePath.type,id:6,activePath:activePath,
resizeFun:(x,y)=>{
drawRect(x, activePath.y ,activePath.x-x+activePath.width, y-activePath.y);
}
},
{
x:activePath.x-pointWidth/2,
y:activePath.y+activePath.height/2-pointHeight/2,
width:10,height:10,ponitFillStyle, type:activePath.type,id:7,activePath:activePath,
resizeFun:(x,y)=>{
drawRect(x, activePath.y ,activePath.x-x+activePath.width, activePath.height);
}
},
];
// drawResizePoint( activePath.points);
break;
case 'ellipse':
activePath.points = [
{
x:activePath.x - pointWidth/2,
y:activePath.y - activePath.radiusY - pointHeight/2,
width:10,height:10,ponitFillStyle,type:activePath.type,id:0,activePath:activePath,
resizeFun:(x,y)=>{drawEllipse(activePath.x,activePath.y,activePath.radiusX,Math.abs(y-activePath.y));},
},
{
x:activePath.x + activePath.radiusX - pointWidth/2,
y:activePath.y - pointHeight/2,
width:10,height:10,ponitFillStyle,type:activePath.type,id:0,activePath:activePath,
resizeFun:(x,y)=>{drawEllipse(activePath.x,activePath.y,Math.abs(x - activePath.x),activePath.radiusY);},
},
{
x:activePath.x - pointWidth/2,
y:activePath.y + activePath.radiusY - pointHeight/2,
width:10,height:10,ponitFillStyle,type:activePath.type,id:0,activePath:activePath,
resizeFun:(x,y)=>{drawEllipse(activePath.x,activePath.y,activePath.radiusX,Math.abs(y-activePath.y));},
},
{
x:activePath.x - activePath.radiusX - pointWidth/2,
y:activePath.y - pointHeight/2,
width:10,height:10,ponitFillStyle,type:activePath.type,id:0,activePath:activePath,
resizeFun:(x,y)=>{drawEllipse(activePath.x,activePath.y,Math.abs(x - activePath.x),activePath.radiusY);},
},
]
// drawResizePoint( activePath.points);
break;
case 'line':
activePath.points = [
{
x:activePath.startX - pointWidth/2,
y:activePath.startY - pointHeight/2,
width:10,height:10,ponitFillStyle,type:activePath.type,id:0,activePath:activePath,
resizeFun:(x,y)=>{drawLine(x,y,activePath.x,activePath.y);},
},
{
x:activePath.x - pointWidth/2,
y:activePath.y - pointHeight/2,
width:10,height:10,ponitFillStyle,type:activePath.type,id:0,activePath:activePath,
resizeFun:(x,y)=>{drawLine(activePath.startX,activePath.startY,x,y);},
}
]
// drawResizePoint( activePath.points);
break;
}
drawResizePoint( activePath.points);
}
//画resize的点
const drawResizePoint = (points)=>{
resizePointList = [];
for(let i=0;i<points.length;i++){
let resizePoint = points[i];
let path = new Path2D();
topCtx.beginPath();
topCtx.moveTo(resizePoint.x,resizePoint.y);
path.rect(resizePoint.x,resizePoint.y,resizePoint.width,resizePoint.height);
topCtx.strokeStyle = resizePoint.ponitFillStyle;
topCtx.stroke(path);
resizePoint.path = path;
resizePointList.push(resizePoint)
}
}
//画椭圆形
const drawEllipse = (x,y,radiusX,radiusY)=>{
pathObj = null;
//清除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.strokeStyle = strokeStyle;
topCtx.stroke(path);
pathObj = {
type:'ellipse',
shape:'stroke',
path,
x,
y,
radiusX,
radiusY,
lineWidth:topCtx.lineWidth||lineWidth,
strokeStyle:topCtx.strokeStyle||strokeStyle,
points:[],
};
}
//画矩形
const drawRect = (x,y,width,height)=>{
pathObj = null;
//清除topCtx画布
topCtx.clearRect(0,0,canvasWidth,canvasHeight);
topCtx.beginPath();
let path = new Path2D();
// 矩形
//这里需要确认起始点和结束点
//即 不管画出的矩形是向上,向下 左上角点为起始点,做下角点为结束点
let beginX = width<0?x+width:x;
let beginY = height<0?y+height:y
path.rect(beginX,beginY,Math.abs(width),Math.abs(height));
topCtx.strokeStyle = strokeStyle;
topCtx.stroke(path);
pathObj = {
type:'rect',
shape:'stroke',
path,
x:beginX,
y:beginY,
width:Math.abs(width),
height:Math.abs(height),
lineWidth:topCtx.lineWidth||lineWidth,
strokeStyle:topCtx.strokeStyle||strokeStyle,
points:[],
};
}
//橡皮擦
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;
pathObj = null;
//清空当前画布内容
topCtx.clearRect(0,0,canvasWidth,canvasHeight);
//必须每次都beginPath 不然会卡
topCtx.beginPath();
let path = new Path2D();
path.moveTo(startX,startY);
path.lineTo(x,y);
topCtx.strokeStyle = strokeStyle;
topCtx.stroke(path);
pathObj = {
type:'line',
shape:'stroke',
path,
x,
y,
startX,
startY,
lineWidth:topCtx.lineWidth||lineWidth,
strokeStyle:topCtx.strokeStyle||strokeStyle,
points:[],
};
}
//画带透明度涂鸦
const drawCurve = (drawPointsParams)=>{
// drawPoints.push({x,y});
if(!drawPointsParams||drawPointsParams.length<1)return
pathObj = null;
//清空当前画布内容
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.strokeStyle = strokeStyle;
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>
总结
踩坑路漫漫长@~@