JavaScript事件处理(三)
上机三 JavaScript事件处理
目的:
-
熟练掌握JavaScript事件处理机制
-
重点理解面向对象编程思想,并构建程序。
要求:
-
定义一个按钮,动态生成DIV,可以生成多个DIV;
-
实现DIV的自由拖曳,当和以生成的DIV碰撞时,弹出警告框;
-
封装自由拖曳函数,DIV作为参数传入
源代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Third_work</title>
</head>
<style>
/* 将按钮固定位置于浏览器窗口上方 */
.top{
position: fixed;
top: 0px;
z-index: 1;
box-shadow: 0px 0px 10px gainsboro;
width: 1519px;
height: 55px;
}
/* 简单对按钮进行装饰 */
input{
width: 150px;
height: 50px;
border-radius: 25px;
background-color: ghostwhite;
outline: none;
}
</style>
<body>
<div class="top">
<input type="button" value="make a new div" onclick="newDiv()">
<input type="button" value="clear all div" onclick="clearDiv()">
</div>
<div id="content" style="margin-top: 50px;">
<!-- div容器:div在此处生成 -->
</div>
</body>
</html>
<script>
// 定义全局变量,记录生成div的总个数,生成下一个div的位置
var idCount = 0;
var positionXCount = 0;
var positionYCount = 100;
// 顺序生成的div的id如:1,2,3,4,5...
function getDivId(){
var DivId = ++idCount;
return DivId;
}
// 随机生成div的背景颜色
function getBackgroundColor(){
var backgroundColor = "#";
var colorNum =
["0","1","2","3","4","5","6","7","8","9",
"A","B","c","D","E","F"];
for(let i = 0;i < 6;i++ ){
backgroundColor += colorNum[Math.round(Math.random()*15)];
}
return backgroundColor;
}
// 设置生成div的宽高和位置
function getDivSize(obj){
var divSize = parseFloat(100+Math.random()*100);
obj.style.width = obj.style.height = divSize + "px";
obj.style.left = positionXCount + "px";
obj.style.top = positionYCount + "px";
positionXCount += divSize + 100;
if(positionXCount > 1300){
positionXCount = 0;
positionYCount += divSize + 100;
}
}
// 移动div并处理判断碰撞
function moveDiv(obj){
var positionX = 0;
var positionY = 0;
//设置鼠标按下事件
obj.onmousedown = function (event){
// 阻止鼠标打开链接
event.preventDefault();
var hit=0;
// 获取div容器下生成的所有div
var Div = document.getElementById("content").querySelectorAll("div");
// 记录移动前,div位置
var startPositionX = obj.offsetLeft;
var startPositionY = obj.offsetTop
positionX = event.clientX - startPositionX;
positionY = event.clientY - startPositionY;
// 设置鼠标移动事件
document.onmousemove = function(eve){
// 获取移动时,div的动态位置
var L = eve.clientX-positionX;
var R = parseFloat(L + obj.offsetWidth);
var T = eve.clientY-positionY;
var B = parseFloat(T + obj.offsetHeight);
// 比较所有div的位置,位置重合则碰撞
for(var i = 0;i < idCount;i++)
{
// 避免与自身比较
if((i + 1) == obj.id)i++;
// 获取与之比较的div的位置
var l = parseFloat(Div[i].offsetLeft);
var t = parseFloat(Div[i].offsetTop);
var r = parseFloat(Div[i].offsetLeft + Div[i].offsetWidth);
var b = parseFloat(Div[i].offsetTop + Div[i].offsetHeight);
// 两两比较,只有以下4种情况是不发生碰撞的,此外都发生碰撞
if(b<=T || B<=t || r<=L || R<=l){
// 不碰撞则移动
obj.style.left = L + "px";
obj.style.top = T + "px";
}
else{
setTimeout(function (){
// 将div移动回碰撞前位置
obj.style.left = startPositionX + "px";
obj.style.top = startPositionY + "px";
// 清除移动事件
document.onmousemove = null;
if(hit == 0){
alert("hit");hit=1;}
},1);
}
}
}
// 设置鼠标抬起事件
document.onmouseup = function () {
// 清除事件操作
document.onmousemove = null;
document.onmouseup = null;
}
}
}
// 按钮触发,在div容器内生成div
var newDiv = function(){
var ele = document.createElement("div");
ele.id = getDivId();
ele.style.position = "absolute";
ele.style.backgroundColor = getBackgroundColor();
getDivSize(ele);
document.getElementById("content").appendChild(ele);
ele.innerHTML = ele.id;
moveDiv(ele);
}
// 重置清空div容器,删除所有div
var clearDiv = function(){
document.getElementById("content").innerHTML = "";
idCount = 0;
positionXCount = 0;
positionYCount = 100;
}
</script>
截图:

总结(遇到问题、解决方式、心得体会):
div的生成和移动相对简单,但在我完成自由拖拽div、开始写判断div碰撞时,我发现了问题。
为了移动div,我将div的position属性都设置成了absolute,但也因此生成的div都聚集在浏览器窗口左上方互相重叠,虽然可以通过拖拽将其移动到其他位置,但如果碰撞功能部分代码完成,点击div开始移动时,就会触发碰撞,弹出警告框从而阻止了div的移动这无疑是个bug。所以若要实现碰撞功能,必须在生成div时就将每个div分隔开。
由于position:absolute绝对位置,通过设置div的外边距,即margin属性,将div分隔的操作失效。我想到可以在生成div时使用静态位置position:static,这样生成的div是会自动排成一竖列,并且可用margin属性。只要在触发拖拽移动时,将position属性修改回绝对位置ele.style.position = "absolute";这样既实现了生成分隔开的div,又可拖拽移动div!
但是!经过测试,这种通过修改position属性的方法并不可行!在理论上这种方法确实完成了生成分隔开的div和自由拖拽div两种功能。但在实践中,由于受到拖拽的div的position会由静态static转变为绝对absolute,实现了空间自由拖拽移动,但其他div仍然是静态位置position:static,他们会自动排列并置顶。也就是说在拖拽div离开其原本位置时,其他div会自动移动、占据被拖拽的div的位置。在此过程中,div之间的碰撞不可避免,之前分隔div的操作毫无意义,他们依然还是会不受鼠标控制自行发生碰撞。所以不能通过修改绝对位置position:absolute来消除bug。
为了实现拖拽移动div,position:absolute是必须的。在绝对位置下,想要分隔开div就要使用left和top等属性来对div位置进行精准定位,并设置全局变量positionCountX和positionCountY来累计上一个div的位置,和预计生成下一个div的位置。这种靠精准定位div位置生成div的方法成功地解决了自动发生碰撞且强行阻止div移动的bug。
碰撞则是在移动的基础上进行加工,让鼠标事件触发的div与其他div用循环进行两两比较,不碰撞则移动,碰撞则弹出警告框。其中也有需要注意的事项,比如要给弹框上锁(关于上锁详情看代码,解释看实验2总结),避免div重合时不停弹框;还有得在弹出警告框前清除鼠标移动事件,因为alert弹框会发生中断,导致后面的鼠标抬起事件无效,这样div会在鼠标没有按下时也一直跟随着鼠标箭头;此外最好设置在div碰撞后自动移回div碰撞前位置,避免在div碰撞后重叠,这会导致点击事件触发弹框而不是拖拽div移动碰撞触发,并因为alert弹框中断div移动导致两个div在碰撞后出现无法分开的情况。
此外关于div是否发生碰撞判定的算法,我是觉得这很有趣的。div怎么判断是否与其他div发生碰撞呢?这实际上是两个问题,一个是div与其他div比较,数据是没有自己与他人这种概念的;另一个是怎么判断碰撞。关于第一个问题,是靠鼠标拖拽事件区分目标div与所有div,用循环将目标div与所有div的比较转换为两两比较,在比较前还要用if语句避免与自身比较,发生自己与自己碰撞的乌龙;关于第二个问题怎么判断碰撞,一般直接的算法是先判断两个div是否在同一高度范围,这需要这两个目标div的top和bottom进行多次比较,然后才是判断两个目标是否重叠,即这两个div的left和right进行比较。显然直接比较算法需要比较很多次,比较同一高度至少需要比较4次,在同一高度比较是否重叠也需要4次,而且这两种比较是分先后的,也就是所有情况是4*4,共16种情况。这种算法就显得直接比较麻烦,就需要重新构思比较策略。与其分析判断两个div碰撞的情况,不如分析判断两个div不碰撞的情况,这就将所有情况减少了许多,不产生碰撞只有4种情况,其他就算发生碰撞。
解决了以上问题,我个人认为在逻辑上,我的实验源代码已经尽善尽美,但意外还是发生了。在测试中,发生了移动两个div尚未碰撞,就弹出碰撞警告框的情况。经过反复测试并在浏览器控制台输出数据进行查看,我得出结论这并不是我算法设计的问题,而是浏览器显示与计算机计算处理速度不一致,计算机计算速度太快,数据发生了变化,但浏览器还来不及更新显示内容,就被alert弹框强行中断事件。所以要在alert语句外围设置setTimeout(),让计算等待1毫秒,等窗口更新变化。加上setTimeout()语句后,成功解决移动两个div尚未碰撞,就弹出碰撞警告框的问题。
这次实验让我深刻地意识到了重复不停思考的意义。就像research(研究)由re-前缀(表重复)和search(搜索)组成,实验研究就是要不停地探索、不停地测试、不停地思考。

浙公网安备 33010602011771号