JS画布内生成图标,并实现拖拽,连线,刷新

JS有现成的拖拽命令,但是只能实现简单的拖拽功能,下面演示的可以在画布的任意一个地方拖拽,并停留在画布的任意地方。

整个HTML页面框架代码如下:

<html>
<head>
  <meta charset="UTF-8">
    <title>拖拽放置</title>
      <link rel="stylesheet" href="mycss.css" type="text/css" />
</head>
<body>
<div id="myMenu">
  <div>插入模块</div>
    <div onclick="copycontent('source.png')"><img src="./Image/source.png"></div>
    <div onclick="copycontent('Station.png')"><img src="./Image/Station.png"></div>
    <div onclick="copycontent('Drain.png')"><img src="./Image/Drain.png"></div>
    <div onclick="lineDraw()"><img src="./Image/Line.png"></div>
    <div onclick="connectcontent()"><img src="./Image/Connector.png"></div>
  
</div>
<div id="container">
  <div id="heading" onclick="anima()"><img src="./Image/EventController.png"></div>
  <div id="content-body"></div>
  <div id="footing">底部状态栏</div>
</div>

<div id="myDiv2" style="background-color: lightblue;width: 0;height: 20px;line-height: 20px;">0%</div>
<button id="btn2">animatino demo</button>
<script src="./myjs.js" type="text/javascript"></script>
<script>

  //1.0 生成画笔
  const canvas = document.createElement('canvas')
  canvas.width =document.getElementById("content-body").clientWidth-4
  canvas.height = document.getElementById("content-body").clientHeight-4
  canvas.id = 'canvas'
  document.getElementById("content-body").appendChild(canvas)
  let ctx = canvas.getContext('2d') //画笔
  //1.1 双缓存绘图,避免闪烁
  const tempcanvas = document.createElement('canvas')
  tempcanvas.width = document.getElementById("content-body").clientWidth-4
  tempcanvas.height = document.getElementById("content-body").clientHeight-4
  tempcanvas.id = 'tempcanvas'
  let tempctx = tempcanvas.getContext('2d') //画笔

  //1.2  鼠标按下监听
  canvas.addEventListener('mousedown', e => {
    saveInfo(e)
  })

  //1.3 鼠标移动
  canvas.addEventListener('mousemove', e => {
    DrawPicture(e)
  })

  //1.4 鼠标抬起事件
  canvas.addEventListener('mouseup', e => {
    canvasInfo.status = statusConfig.IDLE
    canvas.style.cursor = ''
  })

  //1.5 鼠标离开事件
  canvas.addEventListener('mouseleave', e => {
    canvasInfo.status = statusConfig.IDLE
    canvas.style.cursor = ''
  })

  var timer,range,rythym
  localStorage.setItem("range",50)
  btn2.onclick = function(){
    //在线图片转base64:https://www.w3xue.com/tools/pt2base64/
    pictures.push({x:200,y:200,src:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAARCAYAAAA7bUf6AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABjSURBVDhPY2BoaMDAd/a4/QdhQmJwDCJgCpBxT4wIUWIohuzrM4JjEB+kgZAYiD/IDAFJYsMgBcSKgQ0BMZAxsWIwPBqwmGKjAYsQwzAEJADD6HxcYiAMNwQdoyjAIwbBDQwAKAZ+DlDe4nYAAAAASUVORK5CYII=",disp:'yes'})
    myanimation(timer,range,rythym)
  }

</script>
</body>
</html>

JS整理后用到的函数:

//0.0 状态标志
const statusConfig = {
    IDLE: 0, //
    DRAGSTART: 1, //鼠标按下
    DRAGGING: 2, //托拽中
    COPYPASTE: 3    //复制模式
} 
// 画布信息
var canvasInfo = {
    status: statusConfig.IDLE, //状态
    dragTarget: null, //拖拽对象
    lastEvtPos: { //前一位置
        x: null, 
        y: null
    },
    offsetEvtPos: { //前一偏移
        x: null,
        y: null
    }
}
var connInfo={
    prePos:{
        x:null,
        y:null,
        src:null
    },
    sucPos:{
        x:null,
        y:null,
        src:null
    }

}

let pictures = [] //存储画的图片
let lines=[]    //存储*********************连线

//0.1 在图上生成图标
function copycontent(picname)
{
    var x1=Number(localStorage.getItem("x1"))
    var y1=Number(localStorage.getItem("y1"))
    var pathpicture="./Image/"+picname
    var data=LoadImage(ctx,pathpicture,x1,y1,'yes')
    pictures.push({x:x1,y:y1,src:pathpicture,disp:'yes'})
}

//0.2 进入连线模式
function connectcontent()
{
    //进入连线模式,如果在图片外部单击,退出连线模式
    localStorage.setItem("connect","yes")
    console.log("进入连线模式")
}

//0.3 右键菜单
window.onload = function(){
    var ul = document.getElementById("myMenu"); //获取ul节点对象
    ul.style.display = "none";
    /*为document绑定鼠标右键菜单事件*/
    document.oncontextmenu = function(e){
     var _event = window.event||e; //事件对象
     var x = _event.clientX; // 鼠标的x坐标
     var y = _event.clientY; //鼠标的y坐标
     var x1=_event.offsetX; // 鼠标的x坐标
     var y1=_event.offsetY; // 鼠标的x坐标
    localStorage.setItem("x1",x1)
    localStorage.setItem("y1",y1)
    console.log("X:",x,"Y:",y,"X1:",x1,"Y1:",y1)
     ul.style.display = "block";
     ul.style.left = x + "px"; //改变ul的坐标
     ul.style.top = y + "px"; 
     //阻止默认行为
     if(_event.preventDefault){
     _event.preventDefault(); //标准格式
     }else{
     _event.returnValue = false; //IE格式
     }
    }
    document.onclick = function(){
     ul.style.display = "none";
    }
}

//0.4 函数:加载图片
function LoadImage(ctx,imagesrc,posx,posy,disp)
{
  var imageD=new Image();
  imageD.src=imagesrc
  imageD.onload = function(){
    if(disp==='yes')
    {
        ctx.drawImage(imageD, posx, posy)
    }
  }
}

//0.5 单击:正常模式,保存获取到图标的信息;连线模式,连线,保存连线信息
// 如果没有获取到图标,退出连线模式
function saveInfo(e)
{
    var flag=localStorage.getItem("connect")
    const canvasPostion = getCanvasPostion(e)
    var pictureRef = ifInPicture(pictures,canvasPostion)
    if (pictureRef)
    {
        if(flag==="yes")
        {
            localStorage.setItem("connect","start")
            console.log("选到第一个点");
            connInfo.prepoint=pictureRef 
        }else if(flag==="start")
        {
            localStorage.setItem("connect","yes")
            console.log("选到第二个点");
            connInfo.sucpoint=pictureRef
            drawLine(ctx,connInfo.prepoint.x,connInfo.prepoint.y,connInfo.sucpoint.x,connInfo.sucpoint.y)
            console.log(pictureRef)
            console.log(connInfo)
            lines.push({prepoint:connInfo.prepoint,sucpoint:connInfo.sucpoint})
        }else
        {
            console.log(pictureRef);
            //将拖到初始值赋给canvasInfo
            canvasInfo.dragTarget = pictureRef //拖拽对象
            canvasInfo.status = statusConfig.DRAGSTART
            canvasInfo.lastEvtPos = canvasPostion
            canvasInfo.offsetEvtPos = canvasPostion
        }
    }else
    {
        if(flag!="no")
        {
            localStorage.setItem("connect","no")
            console.log("退出连线模式")
        }
    }
}

//0.6 画线
function drawLine(ctx,px,py,sx,sy)
{
    ctx.beginPath();
    ctx.moveTo(px+40,py+20);
    ctx.lineTo(sx,sy+20)
    ctx.stroke()
}

//0.7 绘图标和连线
function DrawPicture(e)
{
    //1. 获取当前位置
    const canvasPostion = getCanvasPostion(e)
    //2. 得到绘图对象
    const {dragTarget} = canvasInfo
    //3. 根据鼠标是否在图标上更新鼠标光标图像
    if (ifInPicture(pictures,canvasPostion)) {
        canvas.style.cursor = 'all-scroll'
        //canvas.style.cursor = 'crosshair'
        //canvas.style.cursor = 'cell'
    }else {
        canvas.style.cursor = ''
    }
    //4. 没有获取到绘图对象,返回
    if (!dragTarget) return
    //5. 鼠标已按下,且有移动,开始拖拽
    if (canvasInfo.status === statusConfig.DRAGSTART && getInstance(canvasPostion, canvasInfo.lastEvtPos) > 5) {
        console.log('try to drag');
        canvasInfo.status = statusConfig.DRAGGING
        canvasInfo.offsetEvtPos = canvasPostion
        
    }else if(canvasInfo.status === statusConfig.DRAGGING){
        console.log('draging');
        //更新图片的坐标,否则下次点击时找不到对应的图标
        dragTarget.x += (canvasPostion.x - canvasInfo.offsetEvtPos.x)
        dragTarget.y += (canvasPostion.y - canvasInfo.offsetEvtPos.y)  //基于偏移
        // 新增控制在区域内拖动代码----开始
        if (dragTarget.x < 0) {
            dragTarget.x = 0 
        }else if (dragTarget.x > canvas.width-40) {
            dragTarget.x = canvas.width-40 
        }
        if (dragTarget.y < 0) {
            dragTarget.y = 0 
        }else if (dragTarget.y  > canvas.height-40) {
            dragTarget.y = canvas.height-40
        }

        //绘制在缓存
        pictures.forEach(c=>LoadImage(tempctx,c.src,c.x,c.y,c.disp))
        lines.forEach(c=>drawLine(tempctx,c.prepoint.x,c.prepoint.y,c.sucpoint.x,c.sucpoint.y))
        ctx.clearRect(0,0, canvas.width, canvas.height)  //清空画布
        //绘制好之后直接复制到显示缓存
        ctx.drawImage(tempcanvas,0,0)
        tempctx.clearRect(0,0, tempcanvas.width, tempcanvas.height)  //清空画布
        canvasInfo.offsetEvtPos = canvasPostion
        
    }else if(canvasInfo.status===statusConfig.COPYPASTE)
    {
        canvas.style.cursor = 'copy'
        canvasInfo.status=statusConfig.IDLE
    }
}

//0.8 元素拖拽  鼠标的画布坐标
const getCanvasPostion = e => {
    return {
        x: e.offsetX, //鼠标在页面中的位置的同时减去canvas元素本身的偏移量
        y: e.offsetY,
    }
}
  
//0.9 两点之间的距离
const getInstance = (p1, p2) => {
    if(Math.abs(p1.x-p2.x)<=21 && Math.abs(p1.y-p2.y)<=21)
    {
        return Math.abs(p1.x-p2.x)**2+ Math.abs(p1.y-p2.y)**2
    }else
    {
        return false
    }
}
  
//0.10 判断是否在图片范围内
const ifInPicture = (pictures,pos) => {
    for (let i = 0; i < pictures.length; i++) {
        if (getInstance(pictures[i], pos)) {
        return pictures[i]
        }
    }
    return false
}

//0.11 动画部分
//在拖动图标时,Pictures可能会多加入图片,这样就没有动画效果了,所以Push,pop操作在这里不太可靠,只能作演示
function myanimation(timer,range,rythym)
{
    myDiv2.style.width = '0';
    cancelAnimationFrame(timer);
    timer = requestAnimationFrame(function fn(){
    range=localStorage.getItem("range")
    rythym=parseInt(range/2)
    //满足一定的条件,启动动画
    if(parseInt(myDiv2.style.width) < 500){
      //绘制在缓存
      if(timer % range ===1)
      {
        //*******************通过属性控制图片是否显示,这里取位置pictures.length-1只是用来演示
        pictures[pictures.length-1].disp='yes'
      }else if(timer % range ===3)
      {
        pictures[pictures.length-1].disp='no'
        //1,加入图片,2,显示图片,3,弹出图片,4,显示无图(4-99)
      }else if(timer % range ===2||timer % range ===rythym)
      {
        pictures.forEach(c=>LoadImage(tempctx,c.src,c.x,c.y,c.disp))
        lines.forEach(c=>drawLine(tempctx,c.prepoint.x,c.prepoint.y,c.sucpoint.x,c.sucpoint.y))
        ctx.clearRect(0,0, canvas.width, canvas.height)  //清空画布
        //绘制好之后直接复制到显示缓存
        ctx.drawImage(tempcanvas,0,0)
        tempctx.clearRect(0,0, tempcanvas.width, tempcanvas.height)  //清空画布
        console.log(timer)
      }
      timer = requestAnimationFrame(fn);
    }else{
      //不满足条件,停止动画
      cancelAnimationFrame(timer);
    }
  })
}

function anima()
{
    var url="menu.html"
    var windowfeatures="width=460,height=400,left=400,top=100,menubar=0,toolbar=0,resizable=0,location=0, status=0"
    window.open(url,"_blank",windowfeatures)
}

function arcRect(ctx,x,y,width,height,radius)
{
    // 开始路径并移动到起点位置
    ctx.beginPath();
    ctx.moveTo(x , y+ radius);
    ctx.arcTo(x, y, x+radius, y, radius); 
    // 从右上角开始绘制圆角
    ctx.lineTo(x+width - radius, y);
    ctx.arcTo(x+width, y, x+width, y + radius, radius);
    
    // 从右下角开始绘制圆角
    ctx.lineTo(x+width, y+height - radius);
    ctx.arcTo(x+width, y+height, x+width - radius, y+height, radius);
    
    // 从左下角开始绘制圆角
    ctx.lineTo(x+radius, y+height);
    ctx.arcTo(x, y+height, x, y+height - radius, radius);
    
    // 最后连接起点与第一条直线
    ctx.closePath();
    
    // 填充或者描边矩形(根据需求选择)
    ctx.fillStyle = "gray";
    ctx.fill()
    //ctx.fillText("Hello",x+Math.floor(width/2),y+Math.floor(height/2))
    ctx.strokeStyle = "darkblue";
    ctx.stroke();
}

function lineDraw()
{
    //**************演示设置矩形参数
  var x = 100; // 左边距
  var y = 50; // 顶部边距
  var width = 200; // 矩形宽度
  var height = 50; // 矩形高度
  var radius = 10; // 圆角半径
  arcRect(ctx,x,y,width,height,radius)
}

弹出设置界面代码:

<!DOCTYPE html>
<html>
<head lang="en">
 <meta charset="UTF-8">
 <title>Menu</title>
 <script type="text/javascript">
    //导航栏单击变换内容
    function tabSwitch(_this,num) {
        var tag = document.getElementById("nav9");
        var number = tag.getElementsByTagName("a"); //获取导航栏元素个数(getElementsByTagName是返回元素素组)
        var divNum = document.getElementsByClassName("eachDiv"); //获取导航元素对应的div个数
        for(var i=0;i<number.length;i++){ //number是一个数组,这里应该用number.length显示它的长度5
        number[i].className = " "; //清除所有导航栏元素的特殊样式
        divNum[i].style.display = "none"; //其他所有div都隐藏
        }
        _this.className = "l_nav1_no1"; //给当前导航栏元素添加样式
        var content = document.getElementById("l_no2_"+num); //当前导航栏元素对应的div
        content.style.display = "block"; //显示当前导航栏元素对应的div部分
    }

    
 </script>
 <style type="text/css">
  .l_nav1 {
   height: 28px;
   padding-top: 8px;
  }
  .l_nav1 a{
   color: #3C3C3C;
   text-decoration: none;
   padding: 8px;
  }
  .l_nav1 a:hover,#l_nav1 a:active {
   color: green;
   text-decoration: underline;
  }
  .l_nav1 .l_nav1_no1 { /*“头条”*/
   color: green;
   background-color: #ffffff;
   text-decoration: none;
  }

  .l_no2 {
   background-color: #ffffff;
   height: 282px;
   width: 376px;
   overflow: auto; /*当元素内容太大而超出规定区域时,内容会被修剪,但是浏览器会显示滚动条以便查看其余的内容。。*/
  }
  .l_no2 ul{  /*列表部分*/
   padding-left: 0px;
   line-height: 25px;
   font-size: 14px;;
  }
  .l_no2 ul li{
   list-style: none;
  }
  .l_no2 ul a{
   color: #3C3C3C;
   text-decoration: none;
  }
  .l_no2 ul a:active,.l_no2 ul a:hover {
   color: red;
   text-decoration: underline;
  }

  .lineDiv {
        position: relative;
        height: 5px;
        background: #dcdcdc;
        width: 300px;
        margin: 50px auto;
      }
      .lineDiv .minDiv {
        position: absolute;
        top: -5px;
        left: 0;
        width: 15px;
        height: 15px;
        background: blue;
        cursor: pointer
      }
      .lineDiv .minDiv .vals {
        position: absolute;
        font-size: 20px;
        top: -45px;
        left: -10px;
        width: 35px;
        height: 35px;
        line-height: 35px;
        text-align: center;
        background: white;
      }
      .lineDiv .minDiv .vals:after {
        content: "";
        width: 0px;
        height: 0px;
        border-top: 6px solid blue;
        border-left: 6px solid transparent;
        border-right: 6px solid transparent;
        border-bottom: 6px solid transparent;
        display: block;
        margin-left: 11px;
      }
 </style>
</head>
<body style="background-color: #dcdcdc;padding:30px;">
 <nav id="nav9" class="l_nav1">
  <a href="#" onclick="tabSwitch(this,1)" class="l_nav1_no1">控制界面</a>
  <a href="#" onclick="tabSwitch(this,2)">设置界面</a>
 </nav>
 <div class="l_no2">
  <div id="l_no2_1" class="eachDiv" style="display: block"> <!--默认为该div显示-->
   <ul>
    <li><img src="./Image/Reset.png" onclick="alert('hi')" alt="复位" title="复位" style="margin-left:50px;margin-right:25px;margin-top:25px;"><img src="./Image/Play.png" alt="播放" title="播放" style="margin-left:25px;margin-right:25px;"><img src="./Image/Forward.png" alt="快进" title="快进" style="margin-left:25px;margin-right:50px;"></li>
    <li><center><h3>仿真动画速度倍率<span id="msg">0</span>%</h3></center>
      <div id="lineDiv" class="lineDiv">
        <div id="minDiv" class="minDiv">
          <div id="vals" class="vals">0</div>
        </div>
      </div></li>
    <li></li>
    <li><button id="btn1" style="margin-left:50px;margin-right:25px;">OK</button>&nbsp<button id="btn2" style="margin-left:25px;margin-right:25px;">Cancel</button>&nbsp<button id="btn3" style="margin-left:25px;margin-right:50px;">Apply</button></li>
   </ul>
   
  </div>
  <div id="l_no2_2" class="eachDiv" style="display: none">
   <ul>
    <li><strong style="color: #6C6C6C">·</strong><a href="#">妻子产子收1200枚鸡蛋 丈夫1天卖光</a></li>
    <li><strong style="color: #6C6C6C">·</strong><a href="#">母猪产下八名男婴 原因竟然如此凄凉</a></li>
    <li><strong style="color: #6C6C6C">·</strong><a href="#">小夫妻宾馆开房 隔壁大叔全程看直播</a></li>
    <li><strong style="color: #6C6C6C">·</strong><a href="#">老汉自造房车囚禁两妙龄女 边走边玩</a></li>
   </ul>
  </div>
  
  
  <script>
    function DragStart(event) {
        event.dataTransfer.effectAllowed = "move";
        event.dataTransfer.setData("text", event.target.id); // 保存被拖动元素的ID到传输对象中
    }
    
    window.onload = function() {

      console.log(localStorage.getItem('data'))
        var lineDiv = document.getElementById('lineDiv'); //长线条
        var minDiv = document.getElementById('minDiv'); //小方块
        var msg = document.getElementById("msg");
        var vals = document.getElementById("vals");
        var ifBool = false; //判断鼠标是否按下
        //事件
        var start = function(e) {
          e.stopPropagation();
          ifBool = true;
          console.log("鼠标按下")
        }
        var move = function(e) {
          console.log("鼠标拖动")
          if(ifBool) {
            if(!e.touches) {  //兼容移动端
              var x = e.clientX;
            } else {   //兼容PC端
              var x = e.touches[0].pageX;
            }
            //var x = e.touches[0].pageX || e.clientX; //鼠标横坐标var x
            var lineDiv_left = getPosition(lineDiv).left; //长线条的横坐标
            var minDiv_left = x - lineDiv_left; //小方块相对于父元素(长线条)的left值
            if(minDiv_left >= lineDiv.offsetWidth - 15) {
              minDiv_left = lineDiv.offsetWidth - 15;
            }
            if(minDiv_left < 1) {
              minDiv_left = 1;
            }
            //设置拖动后小方块的left值
            minDiv.style.left = minDiv_left + "px";
            var xlen=parseInt((minDiv_left / (lineDiv.offsetWidth - 15)) * 50)
            msg.innerText = xlen;
            vals.innerText = xlen;
            var ylen=xlen<=8?50:(58-xlen)
            localStorage.setItem("range",ylen)
          }
        }
        var end = function(e) {
            console.log("鼠标弹起")
            ifBool = false;
          }
          //鼠标按下方块
        minDiv.addEventListener("touchstart", start);
        minDiv.addEventListener("mousedown", start);
        //拖动
        window.addEventListener("touchmove", move);
        window.addEventListener("mousemove", move);
        //鼠标松开
        window.addEventListener("touchend", end);
        window.addEventListener("mouseup", end);
        //获取元素的绝对位置
        function getPosition(node) {
          var left = node.offsetLeft; //获取元素相对于其父元素的left值var left
          var top = node.offsetTop;
          current = node.offsetParent; // 取得元素的offsetParent
           // 一直循环直到根元素
           
          while(current != null) {
            left += current.offsetLeft;
            top += current.offsetTop;
            current = current.offsetParent;
          }
          return {
            "left": left,
            "top": top
          };
        }
      }
  </script>
 </div>
</body>
</html>

样式代码:

div#container{
	width: 100%;
	height: 600px;
	background: antiquewhite;
}
div#heading{
	width: 100%;
	height: 6%;
	text-align: center;
	background: rgb(120, 120, 180);
}

div#content-body{
	width: 100%;
	height: 88%;
	background: rgb(120, 137, 184);
}
div#footing{
	width: 100%;
	height: 6%;
	text-align: center;
	background: rgb(120, 120, 180);
}

#myMenu{
	position: absolute;
	border: 1px solid #000;
	}
	#myMenu > div:nth-child(2){
	border-top: 1px solid #000;
	}
	#myMenu > div:nth-child(3){
	border-top: 1px solid #000;
	}
	#myMenu > div:nth-child(4){
	border-top: 1px solid #000;
	}
	#myMenu > div:hover{
	cursor: pointer;
	background-color: #0078E7;
	}

另外附上文章中用到的图片:

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水滴与鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值