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

---------IE浏览器中的元件拖动编程
·概述
本文讨论IE浏览器环境下元件对象的拖动编程方法,并在最后给出能够同时在IE 浏览器和Navigator两种浏览器环境下使用的跨平台程序。如想更多地了解元件拖动的基本概念,可参考本文的上篇内容(Navigator浏览器中的元件拖动编程一文)。
在本篇文章中我们将讨论以下内容:
·在页面上设置可拖动的元件对象
·当onmousedown鼠标事件触发时,如何识别可拖动的元件
·元件拖动和结束元件拖动的过程
·一个独特的IE浏览器事件onSelectStart
·改变鼠标的光标形状
·把Navigator和IE浏览器的程序结合在一起,形成跨平台的程序。

IE浏览器所支持的“文档对象模式(DOM)”和它在脚本程序中的表现,可以明显地看到与Navigator有着较大的差别,尤其在元件拖动编程中更是如此。在IE 浏览器中,所有的元件都可以被拖动,而不需要一个封闭的元件标记。例如:一个图像元件了可以被直接拖动,不需要用<DIV> 或<SPAN>标记去把它封闭起来。编程者还可以在IE 浏览器的对象中创建一个新的属性,因为IE 浏览器的对象有一个setAttribute()方法。例如:创建一个“DRAG”属性,并用DRAG=TRUE来表示元件是可以拖动的,见下面的语句。
<IMG NAME="imDragOne" SRC="foobar.gif" WIDTH=100 HEIGHT=100 DRAG=TRUE>
然后使用对象的getAttribute()方法检查得到的DRAG属性的值,程序便可以按照得到的属性值而了解到这个元件是否可以拖动,这当然给编程者提供了很多方便。虽然IE 浏览器给我们提供了许多诸如此类的强大功能,但在这里并不推荐使用这些功能,因为我们的最终的目标是创建一个跨平台的浏览器程序,所以要尽量地保持与其它浏览器编程语句上的相似性,比如我们刚刚谈到的如何表示元件的可拖动特征,我们仍然采用与Navigator浏览器中一样的处理方式,即在ID的标识名中包含一个DRAG字串来表示元件是可拖动的。也就是说,要最大限度地保持与其它浏览器的兼容性。
一·拖动程序的基本结构
下面的程序在页面上定义一个黑色方框,并设置了拖动对象所必需的事件句柄,它是一个拖动程序的基本结构。当然,它并不执行任何动作,因为它包括的函数中还没有内容。但读者要认真的读一读下面的程序,并理解它。如有疑问,可随时参考本文的上篇内容,也许会对你有所帮助。

<HTML>
<HEAD>
<STYLE TYPE="text/css">
<!--
  #elDRAGOne {           /* "DRAG" 字串可放在名称中的任何位置 */
    position: absolute;
    left: 100; top: 100;     
    background-color: red;    /* 在IE中使用的标准CSS属性 */
    layer-background-color: red; /* 仅适用在Navigator中的CSS属性 */
    color: white;
    font-weight: bold;
    text-align: center;
    width: 100;
height: 100;
    clip: rect(0 100 100 0)
  }
-->
</STYLE>
</HEAD>
<BODY>
<DIV ID="elDRAGOne">Drag Me!</DIV>
<!-其它的HTML程序语句 -->

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

  currentX = currentY = 0; // 初始化跟踪鼠标位置的全局变量
  whichEl = null;      // 初始化保存拖动对象的变量
  
  function grabEl() {    // onmousedown事件函数
  }

  function moveEl() {    // onmousemove事件函数
  }

  function dropEl() {    // onmouseup事件函数
  }
  //在文档上指定事件及句柄函数
  document.onmousedown = grabEl;
  document.onmousemove = moveEl;
  document.onmouseup = dropEl;

//-->
</SCRIPT>
</BODY>
</HTML>
在以上的程序中可以看到,在程序的最后指定了三个文档对象的事件。这与Navigator 浏览器中的编程不同,不需要事件捕获语句。接下来我们逐步完善函数的内容,以实现元件拖动的目的。
二·识别一个拖动元件
只要使用者在页面上按下鼠标按钮,就会立即触发onmousedown鼠标事件,并调用grabEl函数。首先,要识别鼠标是在哪一个元件上面,并把这个元件对象保存到对象跟踪变量内。语句如下:
whichEl = event.srcElement;

现在,我们并不知道这个元件是否可以拖动,但可以对元件做一个简单的判断,如果它不是可拖动元件,就从函数中返回,不执行任何动作。但问题是,在这个拖动元件内可能包含着其它元件,甚至包含的元件深度不只一层。如果用户的鼠标放置到内部包含的元件上,如何去识别这个可拖动的元件呢?解决的方法是利用元件对象的树形结构一层层地查找。利用while语句就可以满足这个要求,只要while语句的条件表达式为TRUE(真值),则while内部的语句就会不断地执行,也就是说,只要whichEl变量的代表的对象不是可拖动的元件,while循环将保持下去,如果发现元件是可拖动元件,即元件对象的ID标识名中包含着DRAG字串,程序便会跳出循环,执行后面的语句。(见下面的程序)

while (whichEl.id.indexOf("DRAG") == -1) {
  whichEl = whichEl.parentElement;
  if (whichEl == null) { return }
}
用户操作时只有二种可能的选择:鼠标指针位于一个可拖动元件的上面或鼠标指针位于一个不可拖动元件上。如果鼠标指针在一个可拖动元件上点击时,这个循环程序通过检索对象的树形结构最终就会发现它,因为它不断地把元件的父对象赋值给whichEl变量,一层层向上查找。如果鼠标指针位于一个不可拖动的元件上,则循环程序在检查完所有父元件后,whichEl变量仍为一个空值,并从函数调用中返回。
三·确定页面内的坐标位置
如果把鼠标放到一个可拖动的元件上,在实际移动一个元件前,首先要做一些重要的准备工作。见下面的语句:
whichEl.style.pixelLeft = whichEl.offsetLeft;
whichEl.style.pixelTop = whichEl.offsetTop;
以上语句是给这个元件进行一次重新定位,它只是一个预防性的措施。这里所讨论的坐标位置的值都是以屏幕的像素点表示的。每一个元件都有一个内部生成的只读属性:offsetLeft和 offsetTop,它们是相对于它的父元件左上角的垂直和水平距离。而pixelLeft 和 pixelTop属性体现的是CSS属性left和top的值,它也是相对于文档左上角的距离。它们的值是由编程者指定的,但有时它并不能反映出元件的实际位置,这其中的原因较为复杂,也不必去深究,为了保险起见,要人为地重新指定这个值,以保证能反映元件的正确位置。
虽然IE浏览器提供了很多反映页面位置的属性,但要想知道一个鼠标事件发生时所处的页面位置,却并不能简单地得到,需要通过计算才行。clientX 和 clientY表示的是事件发生时鼠标在客户区窗口的坐标位置。scrollLeft 和 scrollTop是所有可滚动元件BODY, DIV, FRAME, IFRAME, MARQUEE, SPAN and TEXTAREA.所拥有的属性。由于BODY是最上部的容器元件,所以document.body.scrollTop代表了从文档体顶部的起始端(也是页面的起始处)到文档体可见部分(客户窗口)的上边界距离,即文档页面向上滚动后,被文档窗口遮挡部分的尺寸。把这个属性值与event.clientY代表的客户窗口坐标位置的值相加,就完整地表示出鼠标在文档页面中所处的垂直位置。同样道理,也可以计算出在文档中所处的水平位置,计算的结果赋给二个全局变量供以其它程序使用。具体语句如下:
currentX = (event.clientX + document.body.scrollLeft);
currentY = (event.clientY + document.body.scrollTop);

也许你见过许多程序仅仅使用x 和y或clientX和clientY属性,是的,当文档页面不滚动并在一个固定尺寸的窗口内打开时,使用它们完全可以满足要求。但是,如果需要满足所有可能出现的情况时,就只有使用以上谈到的计算方法才能得到正确的文档坐标位置。

四·实际移动和放置一个元件
(1)·抓取元件的函数grabEl
通过以上的逐步讲解,经过完善的grabEl函数内容如下。

function grabEl() {
  whichEl = event.srcElement;//保存事件对象

  while (whichEl.id.indexOf("DRAG") == -1) {
    whichEl = whichEl.parentElement;
    if (whichEl == null) { return }
  }

  whichEl.style.pixelLeft = whichEl.offsetLeft;
  whichEl.style.pixelTop = whichEl.offsetTop;

  currentX = (event.clientX + document.body.scrollLeft);
  currentY = (event.clientY + document.body.scrollTop);
}
(2)·移动元件的函数moveEl
移动元件的函数moveEl在原理上与Navigator浏览器中编程是相同的,但它们使用的属性并不相同。因为有很多的鼠标事件被不断地触发,onmousemove事件当然也不例外,所以在这个函数里首先要检查whichEl是否为空,如果为空,则说明目前没有抓取一个拖动元件。这时,函数将会返回,不做任何进一步的处理。见下面这条语句:

if (whichEl == null) { return };
如果变量whichEl不为空值,则说明已经抓取了一个可拖动的元件,程序会继续执行。自onmousedown 事件发生后,一但移动了鼠标,就需要重新计算新的鼠标位置,然后把结果保存到二个新的变量newX和newY中去。语句如下:

newX = (event.clientX + document.body.scrollLeft);
newY = (event.clientY + document.body.scrollTop);

利用鼠标新的位置值,与上次鼠标所处的位置值(currentX和currentY)相减,得到了鼠标移动的距离。见下面的语句:
distanceX = (newX - currentX);
distanceY = (newY - currentY);
保存这个新的鼠标位置到变量currentX 和 currentY中,为下次计算鼠标的移动位置做好准备。语句如下:
currentX = newX;
currentY = newY;

要实际的去移动一个元件,需要将计算出的移动距离累加到pixelLeft 和pixelTop属性中。具体语句如下:
whichEl.style.pixelLeft += distanceX;
whichEl.style.pixelTop += distanceY;

对于moveEl函数的返回值,我们让它返回一个false(假)值,但这并不是必需的。
event.returnValue = false;

完整的moveEl函数如下:
function moveEl() {
  if (whichEl == null) { return };

  newX = (event.clientX + document.body.scrollLeft);
  newY = (event.clientY + document.body.scrollTop);

  distanceX = (newX - currentX);
  distanceY = (newY - currentY);
  currentX = newX;
  currentY = newY;

  whichEl.style.pixelLeft += distanceX;
  whichEl.style.pixelTop += distanceY;
  event.returnValue = false;
}
(3)·结束元件拖动的函数dropEl
这是一个最简单的函数,只使用一条语句就可以实现,只需把元件拖动的跟踪变量置为空值即可。语句如下:
function dropEl() {
  whichEl = null;
}
五·怎样保持一个被拖动的元件总处于页面的最外层。
为了保持一个正在拖动的元件总是处于页面的最外层,与我们在本文的“上篇”中所述一样,在脚本程序的最后一行定义一个activeEl变量,然后将页面上的最外层元件对象赋值给它。此后,它总是跟踪页面上最后被拖动的对象。具体语句如下:
activeEl = topmostElement;
注意:topmostElement应该是你页面上的一个具体的元件名。
例如:document.all.elDRAGOne 。

与Navigator浏览器中的编程方法不同,在IE 浏览器 浏览器中没有一个可调用的“对象方法”能将一个元件放置到另一个元件之上。IE 浏览器 中要使用元件的z-index属性,先检索到activeEl中对象的z-index属性,然后将这个值加一后赋值给whichEl变量所代表的拖动元件的z-index属性,然后再将这个拖动对象保存到activeEl变量中,为下次使用做好准备,因为现在这个拖动对象是处于页面最外层的元件。具体语句如下:

  if (whichEl != activeEl) {
    whichEl.style.zIndex = activeEl.style.zIndex + 1;
    activeEl = whichEl;
  }
将这段程序增加到grabEl函数中,见如下的函数内容。
function grabEl() {
  whichEl = event.srcElement;

  while (whichEl.id.indexOf("DRAG") == -1) {
    whichEl = whichEl.parentElement;
    if (whichEl == null) { return }
  }

  if (whichEl != activeEl) { //新增加的内容
    whichEl.style.zIndex = activeEl.style.zIndex + 1;
    activeEl = whichEl;
  }

  whichEl.style.pixelLeft = whichEl.offsetLeft;
  whichEl.style.pixelTop = whichEl.offsetTop;

  currentX = (event.clientX + document.body.scrollLeft);
  currentY = (event.clientY + document.body.scrollTop);
}
六·关于onSelectStart的使用
IE 浏览器 中的一个新的onSelectStart事件可以对用户在文档内实行的选择动作做出反映,当用户在文档内拖动鼠标时,默认的动作是将页面内容改变为一个高亮度的选择区域。在页面内移动一个元件时,最好取消对这个动作的响应。为使用这个事件,要增加一行鼠标事件的设置语句和相应的事件调用函数。见下面的语句:
document.onselectstart = checkEl;

function checkEl() {
  if (whichEl!=null) { return false }
}
以上语句表明,正在拖动一个元件时whichEl变量肯定不会为空,所以函数句柄返回一个false(假)值,这就会取消鼠标的选择动作。如果目前没有正在拖动的元件,则函数返回它默认的ture(真)值,选择动作可正常执行。
七·改变鼠标的光标
当用户的鼠标移动到可拖动的元件上时,鼠标光标的形状改变为“十字箭头”的形状,向用户表明这个元件是可以拖动的。当鼠标在页面上的多个元件表面掠过时,在可拖动的元件上将会改变鼠标光标的形状,当鼠标从可拖动的元件上移开时,鼠标光标又会恢复原来的形状。完成这个效果的语句如下:
function cursEl() {
  if (event.srcElement.id.indexOf("DRAG") != -1) {
    event.srcElement.style.cursor = "move"
  }
}
document.onmouseover = cursEl;

至此,我们可以很安全地将以上程序放置到自己的页面上试试元件拖动的特性了。

八·几点提示:
·以上程序是专门针对IE 4以上版本的,所以并不适合在老的版本下运行。当然也不能在Navigator浏览器下正常使用。
·所有被拖动的元件都必须在CSS中声明position属性,不管是在STYLE标记内设置还是使用STYLE=attribute 方式均可。
·所有的可拖动元件都必须在它们的ID标识名中包含有“DRAG”字串,以表明它是可以被拖动的。
·编程者必须给activeEl变量手工指定一个最外层的元件对象。
九·在IE环境下完整的脚本程序内容
<SCRIPT LANGUAGE="JScript">
<!--

  currentX = currentY = 0;
  whichEl = null;

  function grabEl() {
    whichEl = event.srcElement;

    while (whichEl.id.indexOf("DRAG") == -1) {
      whichEl = whichEl.parentElement;
      if (whichEl == null) { return }
    }

    if (whichEl != activeEl) {
      whichEl.style.zIndex = activeEl.style.zIndex + 1;
      activeEl = whichEl;
    }

    whichEl.style.pixelLeft = whichEl.offsetLeft;
    whichEl.style.pixelTop = whichEl.offsetTop;

    currentX = (event.clientX + document.body.scrollLeft);
    currentY = (event.clientY + document.body.scrollTop);
  }

  function moveEl() {
    if (whichEl == null) { return };

    newX = (event.clientX + document.body.scrollLeft);
    newY = (event.clientY + document.body.scrollTop);
    distanceX = (newX - currentX);
    distanceY = (newY - currentY);
    currentX = newX;
    currentY = newY;

    whichEl.style.pixelLeft += distanceX;
    whichEl.style.pixelTop += distanceY;
    event.returnValue = false;
  }

  function checkEl() {
    if (whichEl!=null) { return false }
  }

  function dropEl() {
    whichEl = null;
  }

  function cursEl() {
    if (event.srcElement.id.indexOf("DRAG") != -1) {
      event.srcElement.style.cursor = "move"
    }
  }

  document.onmousedown = grabEl;
  document.onmousemove = moveEl;
  document.onmouseup = dropEl;
  document.onmouseover = cursEl;
  document.onselectstart = checkEl;

  activeEl = elementName;

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


十·跨平台的浏览器应用程序
现在我们可以从本文的“上篇”中取得Navigator浏览器的程序语句,把它们与以上的程序结合在一起,创建一个跨平台的浏览器应用程序。
提示:
·如果把这个程序在页面内使用,则LANGUAGE= attribute的值可能改变为LANGUAGE="JavaScript1.2",而ver4 浏览器检查可以省略,因为只有Navigator 4.0和IE 4.0版本才能识别这个值。
·必须人为设置activeEl变量中的最外层元件的对象。
注意看以下的语句:
  IE4 = (document.all) ? 1 : 0;
NS4 = (document.layers) ? 1 : 0;
这就是识别浏览器类型的判断方法,很实用的!
跨平台的浏览器实用程序如下:
<SCRIPT LANGUAGE="JavaScript">
<!--

  IE4 = (document.all) ? 1 : 0;
  NS4 = (document.layers) ? 1 : 0;
  ver4 = (IE4 || NS4) ? 1 : 0;

  currentX = currentY = 0;
  whichEl = null;
  
  function grabEl(e) {
    if (IE4) {
      whichEl = event.srcElement;
  
      while (whichEl.id.indexOf("DRAG") == -1) {
        whichEl = whichEl.parentElement;
        if (whichEl == null) { return }
      }
    }
    else {
      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) {
      if (IE4) { whichEl.style.zIndex = activeEl.style.zIndex + 1 }
        else { whichEl.moveAbove(activeEl) };
        activeEl = whichEl;
    }
  
    if (IE4) {
      whichEl.style.pixelLeft = whichEl.offsetLeft;
      whichEl.style.pixelTop = whichEl.offsetTop;
  
      currentX = (event.clientX + document.body.scrollLeft);
      currentY = (event.clientY + document.body.scrollTop);
  
    }
    else {
      currentX = e.pageX;
      currentY = e.pageY;
  
      document.captureEvents(Event.MOUSEMOVE);
      document.onmousemove = moveEl;
    }
  }
  
  function moveEl(e) {
    if (whichEl == null) { return };
  
    if (IE4) {
      newX = (event.clientX + document.body.scrollLeft);
      newY = (event.clientY + document.body.scrollTop);
    }
    else {
      newX = e.pageX;
      newY = e.pageY;
    }

    distanceX = (newX - currentX);
    distanceY = (newY - currentY);
    currentX = newX;
    currentY = newY;
  
    if (IE4) {
      whichEl.style.pixelLeft += distanceX;
      whichEl.style.pixelTop += distanceY;
      event.returnValue = false;
    }
    else { whichEl.moveBy(distanceX,distanceY) }
  }
  
  function checkEl() {
    if (whichEl!=null) { return false }
  }
  
  function dropEl() {
    if (NS4) { document.releaseEvents(Event.MOUSEMOVE) }
    whichEl = null;
  }
  
  function cursEl() {
    if (event.srcElement.id.indexOf("DRAG") != -1) {
      event.srcElement.style.cursor = "move"
    }
  }
  
  if (ver4) {
    if (NS4) {
      document.captureEvents(Event.MOUSEDOWN | Event.MOUSEUP);
      activeEl = document.elementName;
    }
    else {
      document.onmousemove = moveEl;
      document.onselectstart = checkEl;
      document.onmouseover = cursEl;
      activeEl = document.all.elementName;
    }
  
    document.onmousedown = grabEl;
    document.onmouseup = dropEl;
  }

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

可以把这个程序放到一个通用的外部文中使用。
省略activeEl变量赋值这一行语句,在每个调用外部文件的页面内设置这个变量的值。见下面语句的用法。
<SCRIPT LANGUAGE="JavaScript" SRC="dragDrop.js></SCRIPT>
<SCRIPT LANGUAGE="JavaScript1.2">
<!--
  activeEl = (IE4) ? document.all.elementName : document.elementName;
//-->
</SCRIPT>
</BODY>
</HTML>