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> <button id="btn2" style="margin-left:25px;margin-right:25px;">Cancel</button> <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;
}
另外附上文章中用到的图片: