网页元件的拖动编程(上篇)

---------Navigator浏览器中的元件拖动编程
·概述
本文以循序渐近方法简明的介绍了如何进行元件拖动的编程方法,只要你有一些javascript脚本语言的编程经历,加上一些耐心,就会从中学会许多DHTML的编程技术,并且可将所学的知识扩展到其它类似的网页编程中去。本文内容分为上、下二篇,分别讲解了在Navigator和IE浏览器环境下的编程方法,最后给出同时适合这二种浏览器下使用的跨平台版本。当然,现在我们大多数人都在使用IE浏览器,但如果你想做一个更专业化的网页编程者,掌握Navigator浏览器的编程方法是十分必要的。所以我们先谈谈在Navigator浏览器中如何进行元件的拖动编程。

本文将讨论以下内容:
·拖动事件的构成
·拖动一个单独的页面元件
·把多个元件设置为可拖动元件
·移动被拖动的元件到页面的最上层

一·元件拖动的编程初步
每一次元件拖动的动作都与三个独立但又相互关联的事件有关,它们是:
onMousedown鼠标事件,当用户用按下鼠标按键时触发这个事件。
onMousemove鼠标事件,当用户的鼠标指针在页面上移动时触发这个事件。
onMouseup 鼠标事件,当用户的鼠标按键释放时触发这个事件。
在 Navigator 的脚本程序中,事件的使用有二种方式:
1 . 事件捕获方式。
document.captureEvents(Event.MOUSEDOWN);
document.onmousemove = grabEl;
2 . 直接设置一个事件句柄去调用相应的句柄函数。
document.elementName.document.onmousedown =grabEl;

在Navigator中,onmousedown是一个文档对象的事件,由于每一个定位元件都有一个文档属性 - document,元件本身也是一个对象,并且这个元件对象也是属于页面文档的,所以语句书写顺序为:document.elementName.document.onmousedown,其中的elementName是元件对象的名称,grabEl是事件调用的句柄函数。包含这个语句的脚本程序应该出现在文档对象被创建以后,所以要把程序放置在页面程序的最后部分。
假如页面上放置了一个名为“elOne”的元件,如果用户希望鼠标指针移动到这个元件上时,按下鼠标按钮就调用一个函数grabEl,释放鼠标按钮时调用函数dropEl,则完成所述要求的基本程序框架如下:
<HTML>
<HEAD>
<STYLE TYPE="text/css">
<!--
  #elOne {//定义一个层的样式。
    position: absolute;
left: 100; top: 100;
layer-background-color: red;//注意这个属性,见后文说明。
    color: white;
    font-weight: bold;
    text-align: center;
width: 100;
height: 100;
clip: rect(0 100 100 0)
  }
-->
</STYLE>
</HEAD>
<BODY>
  <DIV ID="elOne">Drag Me!</DIV>
<SCRIPT LANGUAGE="JavaScript1.2">
<!--
  
function grabEl() {
//写入相应的指令语句
  }

function dropEl() {
//写入相应的指令语句
  }

  document.elOne.document.onmousedown = grabEl;
  document.elOne.document.onmouseup = dropEl;

//-->
</SCRIPT>
</BODY>
</HTML>
以上程序定义了一个层的ID样式,并在BODY区域的DIV标记属性中使用了这个样式。它在页面上显示为一个红色的方框。以后,我们将按照程序执行的动作要求在以上的句柄函数中填写相应的命令语句。注意:在以上程序的样式定义中使用了一个Navigator特定的CSS属性—layer-background-color,它与标准的CSS属性—background-color不同,这是因为在Navigator中存在一个BUG。当需要填充背景色时只有使用这种特定的形式才能够被Navigator浏览器识别。但它不会对IE浏览器产生任何影响,因为在IE中会忽略掉这个属性设置,不会影响程序的执行。
在以上的页面程序中,当事件句柄调用相应的函数时,会自动将一个“事件对象(event)”作为参数传递给它,如果你需要在句柄函数中使用一个“事件对象”,就必须以参数传递的方式使用它。可以给参数以任何名称,但这里我们沿用通常的习惯,使用Navigator中指定的名称“e"来作为参数名。在句柄函数中需要使用“事件对象”时,就以“e”作为对象名引用事件对象。
grabEl函数用于抓取一个对象,但它要与鼠标的移动建立必要的联系,必须要捕捉到鼠标移动的动作。最简单的方式是用window对象来捕获鼠标移动,具体的语句为:window.captureEvents(Event.MOUSEMOVE),并要调用一个句柄函数“moveEl”来处理移动过程中所要实现的程序动作,语句为:
window.onmousemove = moveEl
只要侦测到鼠标移动,就会立即调用“moveEl”函数,对操作的对象进行必要的处理。要想停止侦测鼠标移动的事件,需要在“dropEl”函数中释放对鼠标事件的捕获:window.releaseEvents(Event.MOUSEMOVE)
至此,我们对以前的三个鼠标事件的句柄函数填加了一些必要的语句,见下面的程序。为简单起见,可以省略语句前面的“window”对象名,这并不影响程序的正常使用。

  function grabEl() {
    captureEvents(Event.MOUSEMOVE);
    onmousemove = moveEl;
  }

  function moveEl() {
  }

  function dropEl() {
    releaseEvents(Event.MOUSEMOVE);
  }

二·如何实际去拖动一个元件对象

在处理鼠标的移动时,必须要确切地知道鼠标指针的位置和不断发生的鼠标移动事件之间的移动距离。首先要做的事情是,在onmousedown事件发生时调用grabEl函数,在其中记录鼠标指针的位置。在以后的程序中,把这个记录的位置值与鼠标现行的移动位置进行比较,计算出元件对象的移动距离,这是非常重要的步骤。
为了记录onmousedown事件发生时的鼠标位置,使用Navigator 的二个对象属性:pageX 和 pageY。它们以屏幕像素的点数表示出相对于文档页面的偏移距离。由于我们选择用“e”来命名一个事件对象,所以,可以用e.pageX和e.pageY方式来引用这二个属性,在我们现在讨论的这个程序里,它表示了鼠标onmousedown事件发生时的位置。在grabEl函数内,将这二个属性的值保存到二个变量currentX和currentY中,记录下现行的鼠标指针位置。由于这二个变量需要被其它的函数使用,所以一定要把它们定义为全局变量。具体语句如下:

currentX = currentY = 0;

function grabEl(e) { // 注意要包含表示事件对象的参数“e”
  currentX = e.pageX;
  currentY = e.pageY;
  captureEvents(Event.MOUSEMOVE);
  onmousemove = moveEl;
}

现在我们已经记录下了鼠标指针的初始位置和设置了鼠标移动事件的捕获。当使用者移动鼠标时,就会触发鼠标移动事件,从而调用moveEl函数。在moveEl函数中,需要做的第一件事情就是计算鼠标移动的距离。创建二个新的变量:distanceX 和 distanceY,它们用来保存相邻的两次鼠标移动事件发生时所产生的移动距离,即初始的鼠标位置currentX、currentY与新的鼠标位置e.pageX、e.pageY之间的偏差值,具体语句如下。

  function moveEl(e) { //注意要包含事件对象的标识符“e”
    distanceX = (e.pageX - currentX);
    distanceY = (e.pageY - currentY);
  }
现在我们知道了元件的移动距离,接下来,用现行的鼠标位置值修改记录鼠标初始位置的变量,准备为下次计算鼠标移动距离做好准备,具体语句如下。

  function moveEl(e) {
    distanceX = (e.pageX - currentX);
    distanceY = (e.pageY - currentY);
    currentX = e.pageX;
    currentY = e.pageY;
  }
最后,必须在页面内实际移动一个元件。这需要使用JavaScript 1.2 脚本程序中一个对象的moveBy()方法,只需将二个鼠标移动的距离值传递给这个方法就行了,具体语句如下。

  function moveEl(e) {
    distanceX = (e.pageX - currentX);
    distanceY = (e.pageY - currentY);
    currentX = e.pageX;
    currentY = e.pageY;
    document.elOne.moveBy(distanceX,distanceY);
  }

当每次移动鼠标时,元件对象也跟着一起移动。只有当onmouseup鼠标事件发生时,由于它释放了鼠标移动事件的捕获,元件将停止移动。以上程序已经完全实现了一个页面元件的拖动过程。

三·在页面上拖动多个元件的技巧
有时需要在页面上需要拖动的元件不只一个,但我们不需要给每一个元件都指定一个事件句柄,而是在整个文档内捕获所需的事件,并将各个元件和事件相互区分开。
首先,创建一个全局的元件跟踪变量whichEl ,并将其初始化为一个空值:whichEl = null 。当没有被拖动的元件时,它保持为空,当有元件被拖动时,它将保存这个拖动的元件对象。
在脚本程序的最后,设置在文档内捕获mousedown 和 mouseup事件并指定要调用的句柄函数(见下面的语句)。
document.captureEvents(Event.MOUSEDOWN | Event.MOUSEUP);
document.onmousedown = grabEl;
document.onmouseup = dropEl;
首先,我们必须在页面内选择所要拖动的元件。我们以前举例中是拖动一个红色的方框,现在我们把方框扩充到四个,每一方框的ID标识名中都要包含一个字串“DRAG”,用来表示元件是可以拖动的。字串“DRAG”在名称中的位置可以任意,但必须要包含它。具体语句如下:
<DIV ID="elDRAGOne"></DIV>
<DIV ID="elTwoDRAG"></DIV>
<DIV ID="DRAGelThree"></DIV>
<DIV ID="elFour"></DIV>
在我们给出的四个ID中,有三个表明是可以拖动的。在给DIV标记指定ID标识前,请不要忘记首先在<STYLE>样式中分别定义这四个ID标识的样式名和属性,为了便于区分,可以给它们定义不同的背景颜色。
要实现多个元件的拖动,必须要改变grabEl函数的内容。为了使程序语句看起来更简明清晰,首先将页面的onmousedown事件发生的位置赋值给二个变量mouseX,mouseY。
function grabEl(e) {
  mouseX = e.pageX;
  mouseY = e.pageY;
}
接下来,以检查文档的层数组为条件建立一个循环的语句块,所有由CSS- positioned定义的元件都被包括在层数组内。首先检查元件的ID标识中是否包含着DRAG字串,如果没有,则执行continue语句返回前面的条件判断语句处继续检查下一个元件,只有遇到包含着DRAG字串的ID标识时,才开始检查鼠标的点击是否在此元件的边界之内。一个元件的左边界就是它的left属性,它的右边界是元件的clip.width的值加上它的左边界属性left 。同样道理,我们也可以根据元件的top属性和clip.height与topt属性相加的合来判断元件的上下边界区域。具体语句如下:
function grabEl(e) {
  mouseX = e.pageX;
  mouseY = e.pageY;

  for ( i=0; i<document.layers.length; i++ ) {
    tempLayer = document.layers[i];
    if ( tempLayer.id.indexOf("DRAG") == -1 ) { continue }
      if ( (mouseX > tempLayer.left) && (mouseX < (tempLayer.left + tempLayer.clip.width))
         && (mouseY > tempLayer.top) && (mouseY < (tempLayer.top + tempLayer.clip.height)) ) {
         whichEl = tempLayer;
      }
  }

  if (whichEl == null) { return };

  currentX = e.pageX;
  currentY = e.pageY;

  document.captureEvents(Event.MOUSEMOVE);
  document.onmousemove = moveEl;
}
如果鼠标的onmousedown事件出现在一个可拖动的元件上,最终会把这个元件对象赋值给变量whichEl。当循环结束时,检查whichEl变量,如果它仍为空,这说明在鼠标onmousedown事件发生处没有发现可拖动的元件。函数将会返回。如果不为空,则说明发现了可拖动元件,函数将继续执行。接下来,记录鼠标的位置和在文档对象中设置捕获onmousemove 鼠标事件(注意,现在的鼠标事件已经指定给页面文档了,这和前面程序指定给元件对象稍有差别。),并指定事件调用的moveEl句柄函数。moveEl函数没有太多的变化,只是用whicthEl 代替了以前的元件对象。(见下面的程序)

function moveEl(e) {
  distanceX = (e.pageX - currentX);
  distanceY = (e.pageY - currentY);
  currentX = e.pageX;
  currentY = e.pageY;
  whichEl.moveBy(distanceX,distanceY);
}
dropEl函数被所有的鼠标释放事件调用,因此要在函数内释放onmousemove事件捕获,并给whichEl变量赋一个空值,取消元件的拖动对象。(见下面的程序)

function dropEl() {
  document.releaseEvents(Event.MOUSEMOVE);
  whichEl = null;
}
现在可以在页面内插入以上的程序,看看执行的效果,其中的三个方框可以随意地在页面内拖动。
目前的程序并不能使一个正在被拖动的元件浮动在其它元件之上,这样给使用者的感觉还不是很自然。所以下面我们讨论如何解决这个问题。

四·如何保持被拖动的元件总在页面的最外层

首先创建一个变量activeEl,将页面上处于最外层的元件对象赋值给这个变量,在我们的程序中,选择的是elDRAGFour元件,因为它在程序中排在其它的层元件的最后面,如果没有特别的指定,排在最后面的元件在页面显示时会较前面的元件更接近页面上面。所以在我们程序中的最后一行语句应该是:activeEl = document.elDRAGFour。只要元件处于最外部,就可以选择使用这个元件。
在grabEl函数中,首先检查被拖动的元件是否就是处于页面最外层的元件,如果不是,则使用JavaScript 1.2 的“moveAbove()” 方法将元件放置到最外部的元件activeEl之上,这个方法接受一个元件对象参数,比如你想将拖动元件whichEl放置在已经处于页面最外层的activeEl元件之上,则这个参数就是activeEl。具体的语句为:whichEl.moveAbove(activeEl)
一旦被拖动的元件被放置到页面最外层,则将这个元件保存到activiEl变量中,替换以前保存的对象,以备下次使用。(修改后的程序见下面)

function grabEl(e) {
  mouseX = e.pageX;
  mouseY = e.pageY;

  for ( i=0; i<document.layers.length; i++ ) {
    tempLayer = document.layers[i];
    if ( tempLayer.id.indexOf("DRAG") == -1 ) { continue }
      if ( (mouseX > tempLayer.left) && (mouseX < (tempLayer.left + tempLayer.clip.width))
         && (mouseY > tempLayer.top) && (mouseY < (tempLayer.top + tempLayer.clip.height)) ) {
         whichEl = tempLayer;
      }
  }

  if (whichEl == null) { return };

  if (whichEl != activeEl) {
    whichEl.moveAbove(activeEl);
    activeEl = whichEl;
  }

  currentX = e.pageX;
  currentY = e.pageY;

  document.captureEvents(Event | MOUSEMOVE);
  document.onmousemove = moveEl;
}
现在我们可以把最后实现的程序放入你自己的一个页面程序中使用了,快去感觉一下元件拖动的乐趣吧!

五·完整的程序文档
下面给出以上讲解内容的完整程序文档,供读者实际使用时参考。

(1) 单个元件拖动的程序文档
·插入到页面文档的底部。

<SCRIPT LANGUAGE="JavaScript1.2">
<!--

  function grabEl(e) {
    currentX = e.pageX;
    currentY = e.pageY;
    captureEvents(Event.MOUSEMOVE);
    onmousemove = moveEl;
  }

  function moveEl(e) {
    distanceX = (e.pageX - currentX);
    distanceY = (e.pageY - currentY);
    currentX = e.pageX;
    currentY = e.pageY;
    document.elementName.moveBy(distanceX,distanceY);
  }

  function dropEl() {
    releaseEvents(Event.MOUSEMOVE);
  }

  document.elementName.document.onmousedown = grabEl;
  document.elementName.document.onmouseup = dropEl;

//-->
</SCRIPT>
</BODY>
</HTML>

(2)多个元件拖动时的程序文档
·插入到页面文档的底部。

<SCRIPT LANGUAGE="JavaScript1.2">
<!--

  currentX = currentY = 0;
  whichEl = null;

  function grabEl(e) {
    mouseX = e.pageX;
    mouseY = e.pageY;
  
    for ( i=0; i<document.layers.length; i++ ) {
      tempLayer = document.layers[i];
      if ( tempLayer.id.indexOf("DRAG") == -1 ) { continue }
        if ( (mouseX > tempLayer.left) && (mouseX < (tempLayer.left + tempLayer.clip.width))
           && (mouseY > tempLayer.top) && (mouseY < (tempLayer.top + tempLayer.clip.height)) ) {
           whichEl = tempLayer;
        }
    }
  
    if (whichEl == null) { return };

    if (whichEl != activeEl) {
      whichEl.moveAbove(activeEl)
      activeEl = whichEl;
    }
  
    currentX = e.pageX;
    currentY = e.pageY;

    document.captureEvents(Event.MOUSEMOVE);
    document.onmousemove = moveEl;
  }


  function moveEl(e) {
    distanceX = (e.pageX - currentX);
    distanceY = (e.pageY - currentY);
    currentX = e.pageX;
    currentY = e.pageY;
    whichEl.moveBy(distanceX,distanceY);
  }

  function dropEl() {
    document.releaseEvents(Event.MOUSEMOVE);
    whichEl = null;
  }

  document.captureEvents(Event.MOUSEDOWN | Event.MOUSEUP);
  document.onmousedown = grabEl;
  document.onmouseup = dropEl;

  activeEl = document.elementName;

//-->
</SCRIPT>
</BODY>
</HTML>

在Navigator浏览器下的拖动对象编程就介绍完了,是不是很简单!是啊,DHTML编程也并不难学,只要你稍稍有点耐心去认真的读完这篇文章,就已经入门了。注意:这个程序需要在Navigator 4.0以上的版本下运行,在IE浏览器下它并不能正确地执行,因为程序中并没有增加浏览器版本的判别语句。也许读者要问,我们现在大多都在使用IE浏览器,为什么要学习Navigator 下的编程呢?作为网页的编程人员,当然不想受到二种不同浏览器环境问题的烦扰,但实际问题并不这么简单,Internet是面向世界的,其实还有许多人都在使用着Navigator浏览器,如果你想要编出专业化的页面程序,就必须要考虑到更多人的使用习惯,使你的站点能够被更多的人顺利访问。在本文第二部分,将介绍在IE浏览器环境下的对象拖动编程,如果你已经阅读并理解了以上的内容,学习后面的内容就是“轻车熟路”了。最后将结合所学的两种浏览器的编程方法,制作出同时适合Navigator和IE浏览器的跨平台程序版本,当然,这也是本文的最终目的。