js事件本身有较多的知识点,如事件类型、事件处理函数的注册等等,不多赘述。
强调一点,原教程中说到阻止默认行为时只提及返回 false 这种方法。除此方法之外,《JavaScript权威指南》中说到:在支持addEventListener()的浏览器中,也能通过调用事件对象的preventDefault() 方法取消事件的默认操作。不过,在IE9之前的IE中,可以通过设置对象的returnValue属性为false来达到同样的效果。
下面对原教程中js事件的一些应用进行总结。
1. 拖拽
1.1 基础
拖拽动作可分解为鼠标按下、鼠标移动、鼠标抬起三个动作,整个动作可理解为事件目标的位置与鼠标位置之间的差一定。这个位置差的计算比较简单,可以在二维空间上定义该位置差。用鼠标的Y坐标减去事件目标的top属性可得纵向差,用鼠标的X坐标减去事件目标的left属性可得横向差,即
diffx = oEvent.clientX - oDiv.offsetLeft;
diffy = oEvent.clientY - oDiv.offsetTop;
上述三个动作有一定的逻辑顺序,如先鼠标按下之后鼠标移动才能拖拽事件目标、鼠标抬起应当终止鼠标移动操作等,因此鼠标移动和鼠标抬起应该放在鼠标按下的处理程序中。
则
// not final
window.onload = function(){
var oDiv = document.getElementById("div1");
var diffx = 0;
var diffy = 0;
oDiv.onmousedown = function(evt){
var oEvent = evt|| window.event;
diffx = oEvent.clientX - oDiv.offsetLeft;
diffy = oEvent.clientY - oDiv.offsetTop;
oDiv.onmousemove = function(evt){
var oEvent = evt|| window.event;
oDiv.style.top = oEvent.clientY - diffy + 'px';
oDiv.style.left = oEvent.clientX - diffx + 'px';
};
oDiv.onmouseup = function(){
oDiv.onmousemove = null;
oDiv.onmouseup = null;
};
};
};
1.2 进阶
1.2.1 一些小问题的解决
基础的拖拽小程序中蕴藏着一些小问题,总结如下。
- 当鼠标拖动事件目标过快时,因为事件目标很小,鼠标容易移出事件目标。可使用的解决方法为:对文档本身进行鼠标移动事件监听而非事件目标。
- 当鼠标移出可视区时,因为鼠标已经不在事件目标上,所以鼠标抬起事件目标处理程序将不再起作用。可使用的解决方法为:对文档本身进行鼠标抬起事件监听而非事件目标。
- 鼠标移动有可能将事件目标拖出可视区。可使用的解决方法为:限制事件目标的可活动范围。本例中,事件目标的父级为文档,所以用最大的范围为可视区宽高减去事件目标宽高。在现实开发中,如果事件目标的父级非文档,则用父级宽高减去事件目标宽高即可。
- 在某些浏览器上出现默认行为。可使用的解决方法为:取消默认行为。
- 在IE早期版本中,拖动事件目标会选中事件目标周围的文字等元素。可使用的解决方法为:在返回false之前使用 setCapture() 进行事件捕获,这样,所有的事件都会被集成到鼠标按下事件中,其他元素就不会被选中。注意,在鼠标抬起事件处理程序中需调用releaseCapture()方法释放事件捕获。用if…else…将IE处理与其它浏览器处理分开(程序略。)
则,程序修改如下。
window.onload = function(){
var oDiv = document.getElementById("div1");
var diffx = 0;
var diffy = 0;
oDiv.onmousedown = function(evt){
var oEvent = evt|| window.event;
diffx = oEvent.clientX - oDiv.offsetLeft;
diffy = oEvent.clientY - oDiv.offsetTop;
oDiv.onmousemove = function(evt){
var oEvent = evt|| window.event;
var legalTop = oEvent.clientY - diffy;
var legalLeft = oEvent.clientX - diffx;
var clientHeight = document.documentElement.clientHeight || document.body.clientHeight;
var clientWidth = document.documentElement.clientWidth || document.body.clientWidth;
if(legalLeft < 0)
legalLeft = 0;
else if(legalLeft > clientWidth - oDiv.offsetWidth)
legalLeft = clientWidth - oDiv.offsetWidth;
if(legalTop < 0)
legalTop = 0;
else if(legalTop > clientHeight - oDiv.offsetHeight)
legalTop = clientHeight - oDiv.offsetHeight;
oDiv.style.top = legalTop + 'px';
oDiv.style.left = legalLeft + 'px';
};
oDiv.onmouseup = function(){
oDiv.onmousemove = null;
oDiv.onmouseup = null;
};
return false;
};
};
注意,原教程中并未对 clientHeight 和 clientWidth 进行兼容性处理。
1.2.2 磁性吸附
磁性吸附,即事件目标自动被吸附到父级边儿上。实现方法很简单,当距离在一定范围内时,直接将距离赋值到终点即可,如
if(legalLeft < 100)
legalLeft = 0;
1.2.3 带框拖拽
带框拖拽的做法:创建一个与事件目标一样大小的框,鼠标移动时只移动框,鼠标抬起时删除框并移动事件目标到框所在的位置。
<style>
.box {border:1px dashed black; position:absolute;}
</style>
<script>
window.onload = function ()
{
var oDiv = document.getElementById('div1');
var diffx = 0;
var diffy = 0;
oDiv.onmousedown = function (evt)
{
var oEvent = evt || window.event;
diffx = oEvent.clientX - oDiv.offsetLeft;
diffy = oEvent.clientY - oDiv.offsetTop;
var oBox = document.createElement('div');
oBox.className = 'box';
oBox.style.width = oDiv.offsetWidth - 2 + 'px';
oBox.style.height = oDiv.offsetHeight - 2 + 'px';
oBox.style.left = oDiv.offsetLeft + 'px';
oBox.style.top = oDiv.offsetTop + 'px';
document.body.appendChild(oBox);
document.onmousemove = function (evt)
{
var oEvent = evt || window.event;
var legalLeft = oEvent.clientX - diffx;
var legalTop = oEvent.clientY - diffy;
var clientHeight = document.documentElement.clientHeight || document.body.clientHeight;
var clientWidth = document.documentElement.clientWidth || document.body.clientWidth;
if(legalLeft < 0)
legalLeft = 0;
else if(legalLeft > clientWidth - oDiv.offsetWidth)
legalLeft = clientWidth - oDiv.offsetWidth;
if(legalTop < 0)
legalTop = 0;
else if(legalTop > clientHeight - oDiv.offsetHeight)
legalTop = clientHeight - oDiv.offsetHeight;
oBox.style.left = legalLeft + 'px';
oBox.style.top = legalTop + 'px';
};
document.onmouseup=function ()
{
document.onmousemove = null;
document.onmouseup = null;
oDiv.style.left = oBox.offsetLeft + 'px';
oDiv.style.top = oBox.offsetTop + 'px';
document.body.removeChild(oBox);
};
return false;
};
};
</script>
2. 自定义滚动条
自定义滚动条可以理解为具有父子级别关系的两个元素,子元素在父元素范围内进行拖拽。
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>ScrollBar</title>
<style>
#parent {width:600px; height:20px; background:#CCC; position:relative; margin:10px auto;}
#div1 {width:20px; height:20px; background:red; position:absolute; left:0; top:0;}
#div2 {width:400px; height:300px; border:1px solid black; overflow:hidden; position:relative;}
#div3 {position:absolute; left:0; top:0; padding:4px;}
</style>
<script>
window.onload = function (){
var oDiv = document.getElementById('div1');
var oDiv2 = document.getElementById('div2');
var oDiv3 = document.getElementById('div3');
var oParent = document.getElementById('parent');
var diffx = 0;
oDiv.onmousedown=function (evt){
var oEvent = evt || window.event;
disX = oEvent.clientX - oDiv.offsetLeft;
document.onmousemove = function (evt){
var oEvent = evt || window.event;
var legalLeft = oEvent.clientX - diffx;
if(legalLeft < 0){
legalLeft = 0;
}else if(legalLeft > oParent.offsetWidth - oDiv.offsetWidth){
l = oParent.offsetWidth - oDiv.offsetWidth;
}
oDiv.style.left = legalLeft + 'px';
var scale = legalLeft / (oParent.offsetWidth - oDiv.offsetWidth);
oDiv3.style.top = - scale * (oDiv3.offsetHeight - oDiv2.offsetHeight) + 'px';
};
document.onmouseup = function (){
document.onmousemove = null;
document.onmouseup = null;
};
return false;
};
};
</script>
</head>
<body>
<div id="parent">
<div id="div1"></div>
</div>
<div id="div2">
<div id="div3">
这里有很多文字,文字内容略。
</div>
</div>
</body>
</html>
需要注意:
- 本例中假设滚动条在水平方向上滚动,所以限制范围时只需要限制且设置事件目标的left属性。
- div3实际可滚动的范围为其本身的宽高减去div2的宽高。
参考文献
【1】《JavaScript权威指南》
【2】智能社:JavaScript教程——从入门到精通 ( https://ke.qq.com/course/152997?taid=766917950461349 )