AlexZ33 / lessions

自己练习的各种demo和课程
12 stars 2 forks source link

JavaScript教程:DOM事件 #82

Open AlexZ33 opened 4 years ago

AlexZ33 commented 4 years ago

JavaScript基础 -- DOM事件

概要


事件概念、基础、常用事件

要了解事件处理,首先需要知道的一个概念就是“事件”。
“事件”本身是一个抽象的概念,也是一个对象, 它是表现另外的对象状态变化的对象。日常生活中,“事件”随处可见。例如,按下电脑的电源键电脑就开机了。在这里“按下电脑的电源键”就是一个事件,这个事件的结果就是“电脑开机了”,看到的就是电脑处理“按下电源键”这个事件的结果,而这个处理的过程就是本书要说的事件处理。
事件处理是面向对象编程的一个重要概念,而JavaScript是一种基于对象 与事件驱动的脚本语言,因此,事件处理在JavaScript中也是一个重要概念。简单地说,“事件处理” 就是当对象的状态改变时( 电脑的电源键由正常改变为被按下),对象应对这种改变的动作(电脑对电源键状态的改变进行的处理过程)。对事件进行处理的程序或函数称为“事件处理”。
在JavaScript中,鼠标、键盘、文档的状态改变(动作)称为事件;由鼠标、键盘、文档引发的一系列的程序动作, 称为事件驱动;对发生的事件进行相应处理的程序或函数,称为事件处理函数。JavaScript 的事件处理函数使用function函数的形式定义,方法如下:

function funcName(params,...) {
  Do something...//事件处理的脚本
}

其中,funcName 是事件处理函数的函数名,这是必需的(某些情况下用户可以定义匿名的事件处理函数,window.onload-function()...})。事件处理函数可以有()至多个参数,用户可以根据需要指定。指定某个事件的事件处理函数的方法是:事件=事件处理函数名()。例如,在如下所示的代码中,“ 单击”按钮的onclick事件有一个名为clkHandle的事件处理函数。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>JavaScript基础 - 04 - DOM事件</title>
    <meta name="description" content="图灵斯顿俱乐部--IT前端中级课程,
                                      JavaScript基础 - 04 - DOM事件,
                                      报名微信:hututu52533" />
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
    <p>
        事件处理演示:<input type="button" value="单击" onclick="clkHandle()" />
    </p>
    <script>
        function clkHandle() {
            //单击按钮 onclick 事件处理函数
            window.alert("点击“单击”按钮的onclick事件处理函数");
        }
    </script>
</body>
</html>

在上述代码中,指定了“单击”按钮的onclick事件及其名为ckHande()的事件处理函数。在JavaScript中,指定某个事件的事件处理函数的方法,就是给某个HTML元素的事件名属性赋值为函数的函数名。上述代码运行时,单击页面中的“单击”按钮,会弹出如下图所示的提示信息。
image.png
在JavaScript中,事件类型分为很多中,如鼠标事件、键盘事件、HTML事件、变动事件。本课程将对这些事件一一叙述、娓娓道来。

鼠标事件

鼠标事件是指鼠标状态的改变,包括鼠标在移动过程中、单击过程中、拖动过程中等有鼠标状态改变触发的事件。常用的鼠标事件有onclick单击事件、ondblClick 双击事件、onmouseout鼠标离开事件、onmouseover鼠标移到上方事件、onmouseup鼠标放开事件、onmousedown鼠标按下事件和onselect选中事件等。

onclick 单击事件

onclick 事件在鼠标单击某元素时触发。单击是指鼠标停留在对象上,按下鼠标按键,没有移动鼠标而放开鼠标按键这一个完整的过程。我们在以后的开发过程中,最常用也是最常碰到的事件处理就是onclick鼠标单击事件,从之前的课程中学员们应该有所体会。

document.getElementById("btn").onclick = function() {
  document.getElementById("welcome").innerHTML = 'Hello ' + document.getElementById("name").value;
}

ondblClick 双击事件

ondblClick事件在鼠标双击某元素时触发。双击事件是在较短的时间内,连续两次完成鼠标单击事件。
例如,要求双击ul-->li 中的某一行时, 此行显示与其他行不同的背景色(整个页面的色是白色),具体实现方法如代码所示:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>JavaScript基础 - 04 - DOM事件</title>
    <meta name="description" content="图灵斯顿俱乐部--IT前端中级课程,
                                      JavaScript基础 - 04 - DOM事件,
                                      报名微信:hututu52533" />
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
        .ul-color {
            display: flex;
            flex-direction: column;
            width: 120px;
        }

        .ul-color .ul-li {
            cursor: pointer;
            border-bottom: solid 1px #d1d1d1;
            line-height: 32px;
        }
    </style>
</head>
<body>
    <p>
        ondblClick 双击事件:
        <ul class="ul-color">
            <li class="ul-li" ondblclick="changeColor(this, 'red', 'white')">无中生有</li>
            <li class="ul-li">暗渡成仓</li>
            <li class="ul-li">凭空想象</li>
            <li class="ul-li">凭空捏造</li>
        </ul>
    </p>
    <script>
        function changeColor(ele, color, fontColor) {
            //单击按钮 ondblClick 事件处理函数
            ele.style.background = color;
            ele.style.color = fontColor
        }
    </script>
</body>
</html>

在适当的地方正确使用鼠标双击事件可以提高页面的显示效果,改善用户体验。

onmouseover 鼠标滑入事件

onmousovser事件在鼠标进入对象范围(移到对象上方)时触发。文本框所在元素li的HTML,代码如下:

<p 
   style="text-indent: 20px;" 
   onmouseover="mouseover(this)"
>鼠标还没有来过。</p>

当鼠标进入单元格时,触发onmouseover事件,调用名称为modStyle的事件处理函数,完成对单元格样式的更改。onmouseover 事件可以应用在所有的HTML页面元素中,例如,鼠标经过文字上方时,显示效果为“鼠标来到了我的上面^_^”,鼠标离开后,显示效果为“鼠标从我上面离开了T。T”。其实现方法如下:

function mouseover(ele) {
  //鼠标移入事件 onmouseover
  ele.innerText = "鼠标来到了我的上面^_^";
}

onmouseout 鼠标离开事件

onmouseout事件在鼠标离开对象时触发。onmouseover事件通常与onmouseover事件共同使用改变对象的状态。

<p style="text-indent: 20px;" 
   onmouseover="mouseover(this)" 
   onmouseout="mouseout(this)">鼠标还没有来过。</p>
function mouseover(ele) {
  //鼠标移入事件 onmouseover
  ele.innerText = "鼠标来到了我的上面^_^";
}

function mouseout(ele) {
  //鼠标离开事件
  ele.innerText = "鼠标从我上面离开了T。T";
}

onmousedown 鼠标按下事件

onmousedown事件在用户把鼠标放在元素上按下鼠标是触发。下面我们来看下下面这个演示demo:

<p id="mousedown" onmousedown="mousedown(this)"></p>
function mousedown(ele) {
  //鼠标按下事件 onmousedown
  let xPst = event.clientX; //获得X坐标
  let yPst = event.clientY; //获得Y坐标
  ele.innerText = `X坐标为:::::${xPst} \n Y坐标为:::::${yPst}`;
  ele.style.cursor = "hand";
  ele.style.background = "red";
  ele.style.color = "white";
  ele.style.border = "5px solid blue";
}

onmouseup 鼠标放开事件

onmouseup事件在用户把鼠标放在对象上鼠标按键被按下的情况下,放开鼠标键时触发。如果接收鼠标键按下事件的对象与鼠标键放开时的对象不是同一个对象,那么onmouseup事件不会触发。onmousedown 事件与onmouseup事件有先后顺序,在同一个对象上前者在先后者在后。onmouseup事件通常与onmousedown事件共同使用控制同一对象的状态改变。
我们继续刚刚的代码示例,添加onmouseup事件进入:

<p id="mousedown" onmousedown="mousedown(this)" onmouseup="mouseup(this)"></p>
function mousedown(ele) {
  //鼠标按下事件 onmousedown
  let xPst = event.clientX; //获得X坐标
  let yPst = event.clientY; //获得Y坐标
  ele.innerText = `X坐标为:::::${xPst} \n Y坐标为:::::${yPst}`;
  ele.style.background = "red";
  ele.style.color = "white";
  ele.style.border = "5px solid blue";
}

function mouseup(ele) {
  ele.innerText = "鼠标松开了";
  let eleStyle = ele.style;
  eleStyle.background = "#d1d1d1";
  eleStyle.color = "#000";
  eleStyle.border = "none";
}

onselect 选中事件

onselect事件在文本框或是文本域的内容被选中时(选中的部分高亮显示)触发。onselect事件的具体过程是从鼠标按键被按下,到鼠标开始移动并选中内容的过程。这个过程并不包括鼠标键的放开。
例如在一一个 HTML页面中,当用户选择文本框中的文字时,在div中显示用户选中的文字内容。其实现方法如下:

<p>
  onselect 选中事件
<p>
  <input 
         style="width: 200px;" 
         type="text" 
         placeholder="请输入内容让后选中试试看吧" 
         onselect="selectDemo(this)" />
</p>
<p>
  <textarea style="width: 200px;" onselect="selectDemo(this)"></textarea>
</p>
<div id="showSelectValue">
  还没有选中内容哦,快选中内容试试看吧
</div>
</p>
function selectDemo(ele) {
  let choseStr = window.getSelection(); //获得当前选中文本
  document.getElementById("showSelectValue").innerText = choseStr;
}

[MDN] selection 详解

键盘事件

键盘事件是指键盘状态的改变,常用的键盘事件有onkeydown 按键事件、onkeypress 按下键事件和onkeyup
放开键事件等。

onkeydown 键盘事件

onkeydown事件在键盘的按键被按下时触发。onkeydown事件用于接收键盘的所有按键(包括功能键)被按下时的事件。onkeydown 事件与onkeypress事件都在按键按下时触发,但是两者有区别,详情下面onkeypress和onkeyup的讲解。
例如,在用户输入信息的界面中,为方便用户使用,通常情况下,当用户按回车键时,触发页面登陆、提交等等功能。

<input value="敲下回车试试看吧" 
       type="text" 
       name="keydown" 
       onkeydown="if(event.keyCode==13) alert('同学们好!!!');" />

onkeypress 按下事件

onkeypress事件在键盘的按键被按下时触发。onkeypress事件与onkeydown事件两者有先后顺序,onkeypress事件是在onkeydown事件之后发生的。此外,当按下键盘上的任何一个键时,都会触发onkeydown事件;但是onkeypres事件只在按下键盘上的任字符键(如A~Z、数字键)时触发,但是单独按下功能键(F1~F12)、Ctrl键、Shift键、Alt键等,不会触发onkeypress事件。
onkeydown事件与onkeypress事件执行的先后顺序,其代码如下:

function keydwnHandle() {
  alert("onkeydown 事件触发")
}

function keyprsHandle() {
  alert("onkeypress 事件触发")
}

onkeyup 放开事件

onkeyup事件在键盘的按键被按下然后放开时触发。
例如,页面中要求用户输入内容后,使用onkeyup事件,获取文本框中输入内容的长度,示例如下:

<p>
  onkeyup 放开键事件
  <input type="text" name="keyup" onkeyup="keyupHandle(this);" />
  <span id="keyupNum"></span>
</p>
function keyupHandle(ele) {
  document.getElementById("keyupNum").innerText = "输入的字数为::::::" + ele.value.length;
}

HTML事件

HmL事件必指HTML文件状志改变时触发的、用户可以捕获的事件,本部分介绍的常用的HTML事件包括onload窗口加载事件、onunload窗口离开事件、onresize 改变窗口大小触发的事件、onabort 中断事件、onerror异常事件、onreset 按下重置按钮事件、onsubmit按下提交按钮事件等。

onload 窗口加载事件

onload事件并不是在窗口加载过程中执行,而是在页面包括页面中的图片、插件、控件、Applet小应用等内容全部下载完成后执行。onload 事件一般在标记中添加。例如,在用户打开页面时,将光标自动停放在“用户名”文本框中,其实现方法如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>JavaScript基础 - 04 - DOM事件</title>
    <meta name="description" content="图灵斯顿俱乐部--IT前端中级课程,JavaScript基础 - 04 - DOM事件,报名微信:hututu52533" />
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
    </style>
</head>
<body onload="alert('进入页面了!!!')">
    <script>
    </script>
</body>
</html>

这段代码就是在标签中添加onload事件,当加载时弹出alert事件。一般onload事件多用于页面加载完毕后,用户进行操作之前的一些内部处理,如光标定位、页面显示效果更改、读取用户cookie信息等等。

onunload 窗口离开事件(现在在多数浏览器中,已被停止使用)

onunload 事件在窗口离开时触发,窗口离开的行为包括关闭浏览器窗口、通过地址处或收藏夹前往其他页面、单击“返回”、“前进”、 “刷新”、“主页”其中一个按钮、单击一个前往其他页面的URL链接等。在JavaScript中,当用window.open打开个页面或重新赋予location.href的值等情况下触发。onunload 事件多用在或标记中。其实现方法如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>JavaScript基础 - 04 - DOM事件</title>
    <meta name="description" content="图灵斯顿俱乐部--IT前端中级课程,JavaScript基础 - 04 - DOM事件,报名微信:hututu52533" />
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
    </style>
</head>
<body onunload="onunloadHandle('我悄悄的走了不带走一片云彩!')" onload="alert('我静悄悄的来了!')">
    <script>
        function onunloadHandle(str) {
            alert(str)
        }
    </script>
</body>
</html>

一般多用于,当用户关闭浏览器时,将页面中的全局变量废除。

onresize 改变窗口大小时,触发事件

onresize事件在当用户或者脚本更改窗口或框架大小时触发。onresize事件也需要在标记中指定其事件处理函数。例如,用户改变窗口大小时,实时获取当前宽度和高度。示例代码如下:

function resize() {
    document.getElementById("resizeW").innerText = window.outerWidth;
    document.getElementById("resizeH").innerText = window.outerHeight;
}

onabort 中断事件

onabort事件只有页面在加载img图像元素的过程中发生中断才会触发。onabort事件添加在标签中,示例代码如下:

<img name='imgName' src="imgSrc" onabort="abortHandle()">
function abortHandle() {
    alert("图片加载失败了T。T");
}

onerror 异常事件

当窗口中JavaScript存在异常时,会触发onerror事件。onerror 事件是window对象的属性,需要在JavaScript中使用window onerror指定其事件处理函数。使用onerror 事件以方便地获取各种错误信息,onerror 事件有3个默认的参数,分别是错误信息、错误页的URL、错误行号。例如在如下所示的代码中,在JavaScript中使用了一个未定义的变量使用onerror事件可以捕获到这个错误,并将详细信息获得。

onreset 按下重置按钮事件

onreset 事件一般在form表单被重置时触发使用。代码如下:

<form onreset="window.alert('表单将会被重置')">
  文字内容:
  <input type="text" name="txtContent" />
  <input type="reset" value="重置" />
</form>

onsubmit 按下提交按钮事件

onsubmit事件在一个表单被提交时触发。代码如下:

<form onreset="window.alert('表单将会被重置')" onsubmit="return submitHandle()">
  文字内容:
  <input type="text" name="txtContent" />
  <input type="reset" value="重置" />
</form>
function submitHandle() {
  alert("登陆的用户名是:" + document.getElementById('userName').value);
}

变动事件

变动事件是指由于光标位置的改变引起的状态的改变。常用的变动事件有onblur拾取焦点事件、onfocus获得焦点事件和onchange值改变时触发的事件。

onblur 失去焦点事件

onblur事件在得到焦点的对象失去焦点时触发。例如,在用户输入文本框信息后,当文本框失去焦点时,检验文本是否为空。其现方法如下:

<div>
  onblur 失去焦点事件
  <input type="text" onblur="onblurHandle(this)" />
</div>
function onblurHandle(ele) {
  if (ele.value.trim().length === 0) {
    console.log("填写的信息不能为空");
  } else {
    alert("输入的内容是" + ele.value)
  }
}

onfocus 获得焦点事件

onfocus 事件在未获得焦点的对象获得焦点时触发。例如,文本框获得焦点时候,边框变成蓝色,示例代码如下:

<div>
  onfocus 获得焦点事件
  <input type="text" onfocus="onfocusHandle(this)" />
</div>
function onfocusHandle(ele) {
    ele.style.border = "5px solid blue";
}

onchange 元素值改变事件

onchange事件只在事件对象的值发生改变并且事件对象失去焦点时触发。看上面的onblur示例代码,如果使用onblur事件,文本框每一次失去焦点 都会触发onblur事件,继而执行其事件处理函数,即使用户没有对数据进行任何修改,事件处理函数也会执行。如果不希望每次失去焦点时都触发事件,要求只是在用户对文本框的值进行修改后,失去焦点时触发,可以使用onchange事件。将代码中文本框的onblur事件去掉,更改为onchange 事件(修改如下所示),事件处理函数不进行任何修改,用户在未对文本框的值进行任何修改时,事件处理函数不会执行。

<input type="text" onchange="onchangeHandle(this)" />

onchange事件多用于监听用户是否更改下拉列表的选择。实现的多级下拉列表联动的方法中,就是使用select 元素的onchange事件,判断用户是否对选择的值进行更改,进而实现下拉列表的动态改变。在select元素中,使用onchange 的方法与在文本框中使用方法相同,只需要在select标记中添加onchange事件及其事件处理函数。

<select name="sltName" onchange="changeHandle()">

总结

本节课主要是以大量实例的形式介绍了JavaScript事件处理的概念,然后分别介绍了JavaScript的各种类型的事件,包括鼠标事件、键盘事件、HTML:事件、变动事件,同时详细介绍了各种事件触发的条件、发生过程和驱动机制等,并通过大量的实例,说明各种事件在实际应用中具体的使用方法。由于JavaScript 是种事件驱动的脚本语言, 事件处理 也是实际JavaScript编程中频繁使用的机制,因此本节课的内容需要熟练掌握。做大量的练习。

作业

任务一

手写一个登陆页面,要求做到用户名、密码判断不能为空,可以提交和重置表单数据。

任务二

手写一个文章输入页面,要求做到标题不能为空,文本内容在输入字符时获取当前文本内容长度。

任务三

所有课件中的实例,都必须手把手的敲一边。

P.S

所有作业提交到javascript-learning\04-DOM事件自己的分支下。周日上课时检查


事件深入衍生

DOM事件级别

DOM2

<div id="ear">Click me</div>
<div id="display"></div>

<script>

  var ear = document.getElementById('ear');
  ear.addEventListener("click", listener, false);

  function listener() {
    document.getElementById('display').innerHTML = "blah blah";
  }

</script>

注意
addEventListener是由浏览器提供的 api, 并非 JavaScript 原生 api. 用户触发 event 时, 浏览器会向 message queue 中加入 task, 并通过 Event Loop 执行 task 实现回调的效果.
拓展阅读: 

事件模型

**

事件流


严格来说,上图还少了一个层级window,在顶层。

**

描述DOM事件捕获/冒泡的具体流程

**

事件冒泡

什么是事件冒泡? 

看这段代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>冒泡事件的发生</title>
</head>
<style>
    #parent{
        width: 200px;
        height: 200px;
        background-color: #999999;
        position: relative;
    }
    #child{
        width: 80px;
        height: 80px;
        background-color: #669900;
        position: absolute;
        top: 200px;
        left: 100px;
    }
</style>
<body>
<div id="parent">
    我是父元素
    <div id="child">
        我是子元素
    </div>
</div>
</body>
<script>
    var parent = document.getElementById('parent')
    var child = document.getElementById('child')
    child.onclick = function (event) {
        console.log("我是个小孩子")
    }
    parent.onclick = function (event) {
        console.log("我是老纸啊")
    }
</script>
</html>

这里运行后显示成这样:
image.png

(关于子元素,父元素并不是传统视觉上的包含关系,具体要看html的代码结构)
点击子元素:chrome 审查元素查看后
image.png
看,触发了父类的点击事件。
这个过程就是冒泡:由内而外触发,像水滴的水晕一样直到最后一层。

综上,现在举个例子看一下冒泡所带来的困扰:

需求:点击父标签,子标签显示,点击其他空白区域,子标签隐藏。
实现代码:**

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>冒泡事件的发生</title>
</head>
<style>
    #parent{
        width: 200px;
        height: 200px;
        background-color: #999999;
        position: relative;
    }
    #child{
        width: 80px;
        height: 80px;
        background-color: #669900;
        position: absolute;
        top: 200px;
        left: 100px;
        display: none;  
    }
</style>
<body>
<div id="parent">
    我是父元素
    <div id="child">
        我是子元素
    </div>
</div>
</body>
<script>
    var parent = document.getElementById('parent')
    var child = document.getElementById('child')
//    child.onclick = function (event) {
//        console.log("我是个小孩子")
//    }
    parent.onclick = function (event) {
        console.log('显示啦')
        child.style.display = 'block'
    }
    window.onclick = function (event) {
        console.log('隐藏啦')
        child.style.display = 'none'
    }
</script>
</html>

点击后,点击父元素,子元素总是无法显示(曾经我找了好久啊)。
这就是冒泡在作妖啊, 它先触发 parent点击——再触发window点击
image.png
导致子元素显示之后,又迅速隐藏了。

如何解决冒泡所带来的困扰呢——阻止冒泡!
标准的W3C 方式:e.stopPropagation();这里的stopPropagation是标准的事件对象的一个方法,调用即可
非标准的IE方式:ev.cancelBubble=true; 这里的cancelBubble是 IE事件对象的属性,设为true就可以了
通常我们会封装这样一个函数:(这一段参考了链接代码的)

function stopBubble(e) {
    //如果提供了事件对象,则这是一个非IE浏览器
   if ( e && e.stopPropagation )
      //因此它支持W3C的stopPropagation()方法
      e.stopPropagation();
  else
  //否则,我们需要使用IE的方式来取消事件冒泡
    window.event.cancelBubble = true;
}

下面我们尝试下~~

parent.onclick = function (event) {
        console.log('显示啦')
        child.style.display = 'block'
        stopBubble(event)
    }

点击父元素:运行看看
image.png
子元素显示出啦啦,也没有触发window的点击函数哈,这样就完美的解决了冒泡的困扰。。。。。

Event对象的常见应用

事件名称      target属性       relatedTarget属性
focusin       接受焦点的节点    丧失焦点的节点
focusout     丧失焦点的节点    接受焦点的节点
mouseenter    将要进入的节点    将要离开的节点
mouseleave    将要离开的节点    将要进入的节点
mouseout      将要离开的节点    将要进入的节点
mouseover     将要进入的节点    将要离开的节点
dragenter     将要进入的节点    将要离开的节点
dragexit      将要离开的节点    将要进入的节点

**

自定义事件

自定义事件不是由DOM原生触发的,它的目的是让开发人员创建自己的事件。
Event()
  最简单的就是使用Event()构造函数。
CustomEvent()
  如果需要在触发事件的同时,传入指定的数据,需要使用CustomEvent构造函数生成自定义的事件对象。
this.dispatchEvent(customEvent(this))触发。

var eve = new Event('custome') // 注册事件
dom.addEventListener('custome', function() {
  // 绑定事件名称
  console.log('我是自定义事件')
})
dom.dispatchEvent(eve) // 通过dispatchEvent 来触发事件

拓展讲下--->

  1. dispatchEvent应用(感兴趣同学可自己看)

    事件触发器

addEventListener(type,fun,bool)是监听一个事件,当触发事件时,执行函数,那么什么时候触发事件呢,当监听到dispatchEvent(type)时,触发

dispatchEvent

dispatchEvent是发布事件的意思,在学习three.js源码时候看到这样一段加载vtk文件的代码

// 构造函数
THREE.VTKLoader = function () {
    THREE.EventDispatcher.call( this ); // 继承自监听器,使这个类有监听的功能
};

// VTKLoader的原型函数,里面包含了VTKloader的成员函数,成员变量的定义
THREE.VTKLoader.prototype = {
    // 构造函数
    constructor: THREE.VTKLoader,
    // 加载函数,url表示要加载的vtk文件的url路径,callback表示加载完成后要调用的后续处理函数,这里是异步操作,加载需要一个过程,不能将程序阻塞在这里,所以需要异步回调
    load: function ( url, callback ) {
// 将类自身保存在scope中,scope表示域的意思,这里是为了避免this的歧义,因为,每一个地方使用this,其意义不一样。
        var scope = this;
// ajax 异步请求
        var request = new XMLHttpRequest();
// 加载完成的监听器,加载完成后,将调用第二个参数定义的回调函数
        request.addEventListener( 'load', function ( event ) {
            // 对服务器加载下来的数据进行解析,后面详细解释
            var geometry = scope.parse( event.target.responseText );
// 解析完成后,发一个load事件,表示数据解析完成
            scope.dispatchEvent( { type: 'load', content: geometry } );
// 如果设置了回调函数,那么调用回调函数
            if ( callback ) callback( geometry );
        }, false );
// 加载过程中,向自身发送进度progress信息,信息中包含已经加载的数据的字节数和文件总共的字节数,通过两者的百分比能够了解加载进度。
        request.addEventListener( 'progress', function ( event ) {
// 发送正在加载的消息,两个参数分别是已经加载了多少字节,总共多少字节
            scope.dispatchEvent( { type: 'progress', loaded: event.loaded, total: event.total } );
        }, false );
// 加载出错的监听器,加载的过程也可能出错,这里如果出错,进行错误处理,
        request.addEventListener( 'error', function () {
// 加载出错之后需要发布的错误消息,
            scope.dispatchEvent( { type: 'error', message: 'Couldn\'t load URL [' + url + ']' } );

        }, false );
// 初始化 HTTP 请求参数,例如 URL 和 HTTP 方法,但是并不发送请求。
        request.open( 'GET', url, true );
//发送 HTTP 请求,开始下载
        request.send( null );
},

// parse函数在上面调用过,主要负责解析数据的功能,我们将在后面详细介绍解析函数,这里就不介绍了。
    parse: function ( data ) {
        var geometry = new THREE.Geometry();
        function vertex( x, y, z ) {
            geometry.vertices.push( new THREE.Vector3( x, y, z ) );
        }

        function face3( a, b, c ) {
            geometry.faces.push( new THREE.Face3( a, b, c ) );
        }

        function face4( a, b, c, d ) {
            geometry.faces.push( new THREE.Face4( a, b, c, d ) );
        }

        var pattern, result;

        // float float float

        pattern = /([\+|\-]?[\d]+[\.][\d|\-|e]+)[ ]+([\+|\-]?[\d]+[\.][\d|\-|e]+)[ ]+([\+|\-]?[\d]+[\.][\d|\-|e]+)/g;

        while ( ( result = pattern.exec( data ) ) != null ) {

            // ["1.0 2.0 3.0", "1.0", "2.0", "3.0"]

            vertex( parseFloat( result[ 1 ] ), parseFloat( result[ 2 ] ), parseFloat( result[ 3 ] ) );

        }

        // 3 int int int

        pattern = /3[ ]+([\d]+)[ ]+([\d]+)[ ]+([\d]+)/g;

        while ( ( result = pattern.exec( data ) ) != null ) {

            // ["3 1 2 3", "1", "2", "3"]

            face3( parseInt( result[ 1 ] ), parseInt( result[ 2 ] ), parseInt( result[ 3 ] ) );

        }

        // 4 int int int int

        pattern = /4[ ]+([\d]+)[ ]+([\d]+)[ ]+([\d]+)[ ]+([\d]+)/g;

        while ( ( result = pattern.exec( data ) ) != null ) {

            // ["4 1 2 3 4", "1", "2", "3", "4"]

            face4( parseInt( result[ 1 ] ), parseInt( result[ 2 ] ), parseInt( result[ 3 ] ), parseInt( result[ 4 ] ) );

        }

        geometry.computeCentroids();
        geometry.computeFaceNormals();
        geometry.computeVertexNormals();
        geometry.computeBoundingSphere();

        return geometry;

    }

}

注意这里

request.addEventListener( 'load', function ( event ) {

            var geometry = scope.parse( event.target.responseText );

            scope.dispatchEvent( { type: 'load', content: geometry } );

            if ( callback ) callback( geometry );

        }, false );

这里event.target.responseText是服务器返回的文本数据,也就是vtk文件里的所有数据,我们通过scope.parse方法将其转换为geometry。

转换完后,我们会通过dispathEvent向自身发送一个加载完成的消息,消息中返回了geometry几何体。这个几何体是可以和Mesh合体,最终显示在场景中的。
最后,如果callback不为null的话,那么我们就调用这个回调函数。在这个回调函数中,会做一些模型加载完成后,应该做的事情,例如,将模型放到某一个位置。

更多详情戳这里3D模型的加载与使用

这里我们主要说dispatchEvent

dispatchEvent是作为高级浏览器(如chrome、Firfox等)的事件触发器来使用的,那么什么是事件触发器?就是触发事件的东西。可能有人觉得有点莫名其妙,触发事件不是在交互中 被触发的吗?的确,通常情况下,事件的触发都是由用户的行为如点击、刷新等操作实现,但是,其实有的情况下,事件的触发必须又程序来实现,比如ajax框架的一些自定义事件。正如事件的绑定一样,对于浏览器而言,绑定事件分为高级浏览器和IE浏览器两派,事件触发器也是分为高级浏览器和IE两派,而dispatchEvent正是用于高级浏览器的事件触发

<!-- 
Author: AlexZ33
--> 

<!DOCTYPE html>  
<html>  
<head lang="en">  
    <meta charset="UTF-8">  
    <title>dispatchEvent</title>  
</head>  
<body>  

</body>  
<script type="text/javascript">  
    //document上绑定自定义事件oneating  
    document.addEventListener('oneating', function (event) {  
        alert(event.mingzi+','+event.message);  
    }, false);  

    //创建event的对象实例。  
    var event = document.createEvent('HTMLEvents');  
    // 3个参数:事件类型,是否冒泡,是否阻止浏览器的默认行为  
    event.initEvent("oneating", true, true);  
    /*属性,随便自己定义*/  
    event.mingzi = 'hello,我是鲸鱼';  
    event.message = '我是个书虫';  

    //触发自定义事件oneating  
    document.dispatchEvent(event);  
</script>  
</html>

dispatchEvent大概就是这三步,上面的例子结果是:在页面载入的时候,会弹出提示框,也就是触发了oneating这个自定义事件。

var fireEvent = function(element,event){  
        if (document.createEventObject){  
            // IE浏览器支持fireEvent方法  
            var evt = document.createEventObject();  
            return element.fireEvent('on'+event,evt)  
        }  
        else{  
            // 其他标准浏览器使用dispatchEvent方法  
            var evt = document.createEvent( 'HTMLEvents' );  
            evt.initEvent(event, true, true);  
            return !element.dispatchEvent(evt);  
        }  
    };

document.creatEventObject()是IE创建event对象实例的方法,和document.creatEvent('HTMLEvents')在非IE主流浏览器下的作用相同,fireEvent是IE下的事件触发器,与dispatchEvent在非IE主流浏览器下作用相同。

  1. 设计模式 :发布-订阅 模式


异步类型

一般而言,异步任务有以下三种类型:
  1、普通事件,如click、resize等
  2、资源加载,如load、error等
  3、定时器,包括setInterval、setTimeout等
同步变异步是性能优化的手段之一,如利用定时器对数组进行分块。

事件对象e的坐标位置

clientX/Y与x/y
  clientX/Y表示鼠标指针在可视区域中的水平和垂直坐标。
screenX/Y
  screenX/Y表示鼠标指针相对于屏幕的水平和垂直坐标
PageX/YlayerX/Y
  pageX/Y表示相对于页面的水平和垂直坐标,它与clientX/clientY的区别是不随滚动条的位置变化。
offsetX/Y
  offsetX/Y表示相对于定位父级的水平和垂直坐标。
当页面无定位元素时,body是元素的定位父级。由于body的默认margin是8px,所以offsetX/Y与clientX/Y差(8,8)。

<div id="box" style="height:100px;width:300px;background:pink;"></div>
<script>
var oBox = document.getElementById('box');
oBox.onmousemove=function(e){
    e = e || event;
    oBox.innerHTML = 'clientX:' + e.clientX +';clientY:'+e.clientY + '<br>offsetX:' + e.offsetX + ';offsetY:' + e.offsetY;
}
</script>

时间间隔

系统为了防止按键误被连续按下,所以在第一次触发keydown事件后,有500ms的延迟,才会触发第二次keydown事件。
  [注意]类似的,keypress事件也存在500ms的时间间隔。

顺序

如果用户一直按键不松开,就会连续触发键盘事件,触发的顺序如下
1、keydown 2、keypress 3、keydown 4、keypress 5、(重复以上过程) 6、keyup

变动事件mutation

7个变动事件,浏览器兼容性都不是太好。说的过得去就是DOMNodeInsertedDOMNodeRemovedDOMSubtreeModified这三个事件,不兼容IE8-浏览器。
剪贴板操作包括剪切(cut)、复制(copy)和粘贴(paste)这三个操作,快捷键分别是ctrl+x、ctrl+c、ctrl+v。当然也可以使用鼠标右键菜单进行操作。
如果DOM结构发生变化,触发的是变动事件,如果文本框中的文本发生变化,触发的是文本事件。

change

说起文本变化,最先想到的可能就是change事件。
  对于<input><textarea>元素,在它们失去焦点且value值改变时触发;对于<select>元素,在其选项改变时触发。

textInput

DOM3级事件引人了一个新事件——textInput,用来替代keypress事件。当用户在可编辑区域中输入字符时,就会触发这个事件。
  [注意]该事件只支持DOM2级事件处理程序,且只有chrome和safari浏览器支持。
  由于<input type="range">的游标并不是可编辑区域,所以,textInput事件对游标变化无作用。

input

文本事件中,除了textInput事件,还有一个input事件。
HTML5新增了一个input事件,只要输入框内容发生变化就会立即触发,但通过JS改变value时不会触发
所以这事件与change事件的区别就是不需要移除焦点就可以触发。
该事件可以在chrome/safari/firefox/IE9浏览器中,实时监测游标的变化。

加载事件

load

如果页面从浏览器缓存加载,并不会触发load事件,图像和框架iframe也可以触发load事件。
要在指定图像的src属性之前先指定事件,图像是从设置src属性之后开始下载。
script元素也可以触发load事件,以便开发人员确定动态加载的JS文件是否加载完毕。与图像不同,只有在设置了script元素的src属性并将该元素添加到文档后,才会开始下载JS文件。换句话说,指定src属性和指定事件处理程序的先后顺序不重要。
类似地,link元素可以触发load事件,且无兼容性问题。与script类似,在未指定href属性并将link元素添加到文档之前也不会开始下载样式表。

error

load事件在加载成功时触发,而error事件与之正相反,在加载失败时触发。凡是可以触发load事件的元素,同样可以触发error事件。
任何没有通过try-catch处理的错误都会触发window对象的error事件。
error事件可以接收三个参数:错误消息、错误所在的URL和行号。多数情况下,只有错误消息有用,因为URL只是给出了文档的位置,而行号所指的代码行既可能出自嵌入的JS代码,也可能出自外部的文件。
要指定onerror事件处理程序,可以使用DOM0级技术,也可以使用DOM2级事件的标准格式。
这个事件处理程序是避免浏览器报告错误的最后一道防线。理想情况下,只要可能就不应该使用它。只要能够适当地使用try-catch语句,就不会有错误交给浏览器,也就不会触发error事件。
图像也支持error事件。只要图像的src特性中的URL不能返回可以被识别的图像格式,就会触发error事件。此时的error事件遵循DOM格式,会返回一个以图像为目标的event对象。
发生error事件时,图像下载过程已经结束,也就是不能再重新下载了。但是,可以在error事件中,重新设置图像的src属性,指向备用图像的地址。

abort

元素加载中止时,(如加载过程中按下ESC键,停止加载),触发该事件,常用于图片加载。

unload

与load事件对应的是unload事件,该事件在文档被完全卸载后触发,刷新页面时,也会触发该事件。
在卸载页面的时候,会导致“空事件处理程序”的发生。“空事件处理程序”是指内存中存留的过时不用的事件处理程序,它们是造成Web应用程序内存与性能问题的主要原因。一般来说,最好的做法是在页面卸载之前,先通过onunload事件处理程序移除所有事件处理程序。因此,只要是通过onload事件处理程序添加的东西,最后都应该通过onunload事件处理程序将它们移除。

beforeunload

beforeunload事件在关闭网页或刷新网页时触发。它一般地用来防止用户不小心关闭网页。

DOMContentLoaded

DOMContentLoaded事件则在形成完整的DOM树之后就会触发,而不理会图像、JS文件、CSS文件或其他资源是否下载完毕。与load事件不同,DOMContentLoaded支持在页面下载的早期添加事件处理程序,这也就意味着用户能够尽早地与页面进行交互。
[注意]网页的JS脚本是同步执行的,所以定义DOMContentLoaded事件的监听函数,应该放在所有脚本的最前面。否则脚本一旦发生堵塞,将推迟触发DOMContentLoaded事件。

readystatechange

readystatechange事件发生在Document对象和XMLHttpRequest对象,它们的readyState属性发生变化时触发。

焦点管理

activeElement
 document.activeElement属性用于管理DOM焦点,保存着当前获得焦点的元素。

获得焦点

元素获得焦点的方式有4种,包括页面加载、用户输入(按tab键)、focus()方法和autofocus属性。
【1】页面加载
  默认情况下,文档刚刚加载完成时,document.activeElement中保存的是body元素的引用。文档加载期间,document.activeElement的值为null。
【2】用户输入(按tab键)
  用户通常可以使用tab键移动焦点,使用空格键激活焦点。比如,如果焦点在一个链接上,此时按一下空格键,就会跳转到该链接。
1、如果tabindex=-1,tab键跳过当前元素。
2、如果tabindex=0,表示tab键将遍历当前元素。如果一个元素没有设置tabindex,默认值就是0。
3、如果tabindex大于0,表示tab键优先遍历。值越大,就表示优先级越小
  下列代码中,使用tab键时,button获得焦点的顺序是2、5、1、3

<button tabindex= "3">1</button>
    <button tabindex= "1">2</button>
    <button tabindex= "0">3</button>
    <button tabindex= "-1">4</button>
    <button tabindex= "2">5</button>    
</div>
<script>
box.onkeyup = function(){
    document.activeElement.style.background = 'pink';
}
</script>

hasFocus()
  document.hasFocus()方法返回一个布尔值,表示当前文档之中是否有元素被激活或获得焦点。通过检测文档是否获得了焦点,可以知道是不是正在与页面交互。

焦点事件

在页面获得或失去焦点时触发。利用这些事件并与document.hasFocus()方法及 document.activeElement属性配合,可以知晓用户在页面上的行踪。
  焦点事件共包括下面4个:
blur
  blur事件在元素失去焦点时触发。这个事件不会冒泡。
focus
  focus事件在元素获得焦点时触发。这个事件不会冒泡。
focusin
  focusin事件在元素获得焦点时触发。这个事件与focus事件等价,但它冒泡。
focusout
  focusour事件在元素失去焦点时触发。这个事件与blur事件。

拓展阅读

学习如何用 JavaScript 操作 DOM 样式

再次理解 JavaScript 的事件机制,并了解事件代理

我们顺便学习一个新知识:定时