自定义事件

 

 

你有权力在你的思想-而不是外部事件。意识到这一点, 你就会找到力量。

马库斯.

Picture a Rube Goldberg machine

某些程序使用直接用户输入 (如鼠标和键盘操作)。这种输入不是作为一个组织良好的数据结构来提供的--它是以片断的形式出现的, 在实时的情况下, 该程序将在它发生时对其做出响应。

字面量实现 众所周知,减少全局变量的方法之一就是使用全局变量(其他如闭包)。

var Event = {
    _listeners: {},    
    // 添加
    addEvent: function(type, fn) {
        if (typeof this._listeners[type] === "undefined") {
            this._listeners[type] = [];
        }
        if (typeof fn === "function") {
            this._listeners[type].push(fn);
        }    
        return this;
    },
    // 触发
    fireEvent: function(type) {
        var arrayEvent = this._listeners[type];
        if (arrayEvent instanceof Array) {
            for (var i=0, length=arrayEvent.length; i<length; i+=1) {
                if (typeof arrayEvent[i] === "function") {
                    arrayEvent[i]({ type: type });    
                }
            }
        }    
        return this;
    },
    // 删除
    removeEvent: function(type, fn) { var arrayEvent = this._listeners[type]; if (typeof type === "string" && arrayEvent instanceof Array) { if (typeof fn === "function") { // 清除当前type类型事件下对应fn方法 for (var i=0, length=arrayEvent.length; i<length; i+=1){ if (arrayEvent[i] === fn){ this._listeners[type].splice(i, 1); break; } } } else { // 如果仅仅参数type, 或参数fn邪魔外道,则所有type类型事件清除 delete this._listeners[type]; } } return this; } };

使用类似下面:Event.addEvent("alert", function() { alert("弹出!"); });

实例
<button id="aa"></button>
var qq = document.getElementById('aa');
//添加事件并传参数
   Event.addEvent("smile", function() {
    alert("How are you!");
});
   //触发事件并传参数
   qq.onclick = function() {
    return Event.fireEvent("smile");           //单击button弹出How are you!
};   

效果:

 

 

您可以狠狠地点击这里:JS自定义事件字面量书写demo

默认页面document通过Event.addEvent()绑定了两个自定义的alert事件,因此,此时您点击页面的空白区域(非按钮与示例代码区域),就会有如下图所示的连续两个alert框:

demo页面还有两个按钮,用来清除已经绑定的alert事件。第一个按钮清除所有alert事件,而点击第二个按钮清除第一个alert事件。例如我们点击第二个按钮:

清除完毕后再点击页面的空白区域, 您会发现只会弹出“第二个弹出!”字样的弹出框了。这表明,第一个绑定自定义事件被remove掉了。

字面量实现虽然减少了全局变量,但是其属性方法等都是暴露而且都是唯一的,一旦某个关键属性(如_listeners)不小心在某事件处reset了下,则整个全局的自定义事件都会崩溃。因此,我们可以进一步改进,例如,使用原型链继承,让继承的属性(如_listeners)即使出问题也不会影响全局。

原型模式实现 代码如下(相比上面增加了addEvents, fireEvents, removeEvents多事件绑定、执行与删除方法,篇幅较长,增加滚动限高,点击这里完整展示 – JS交互,  RSS中无效果)(一堆代码看得头大,建议直接跳过):

var EventTarget = function() {
    this._listener = {};
};

EventTarget.prototype = {
    constructor: this,
    addEvent: function(type, fn) {
        if (typeof type === "string" && typeof fn === "function") {
            if (typeof this._listener[type] === "undefined") {
                this._listener[type] = [fn];
            } else {
                this._listener[type].push(fn);    
            }
        }
        return this;
    },
    addEvents: function(obj) {
        obj = typeof obj === "object"? obj : {};
        var type;
        for (type in obj) {
            if ( type && typeof obj[type] === "function") {
                this.addEvent(type, obj[type]);    
            }
        }
        return this;
    },
    fireEvent: function(type) {
        if (type && this._listener[type]) {
            var events = {
                type: type,
                target: this    
            };
            
            for (var length = this._listener[type].length, start=0; start<length; start+=1) {
                this._listener[type][start].call(this, events);
            }
        }
        return this;
    },
    fireEvents: function(array) {
        if (array instanceof Array) {
            for (var i=0, length = array.length; i<length; i+=1) {
                this.fireEvent(array[i]);
            }
        }
        return this;
    },
    removeEvent: function(type, key) {
        var listeners = this._listener[type];
        if (listeners instanceof Array) {
            if (typeof key === "function") {
                for (var i=0, length=listeners.length; i<length; i+=1){
                    if (listeners[i] === key){
                        listeners.splice(i, 1);
                        break;
                    }
                }
            } else if (key instanceof Array) {
                for (var lis=0, lenkey = key.length; lis<lenkey; lis+=1) {
                    this.removeEvent(type, key[lenkey]);
                }
            } else {
                delete this._listener[type];
            }
        }
        return this;
    },
    removeEvents: function(params) { if (params instanceof Array) { for (var i=0, length = params.length; i<length; i+=1) { this.removeEvent(params[i]); } } else if (typeof params === "object") { for (var type in params) { this.removeEvent(type, params[type]); } } return this; } };

啰哩吧嗦的代码直接跳过,其实上面代码跟字面量方法相比,就是增加了下面点东西:

var EventTarget = function() {
    this._listener = {};
};

EventTarget.prototype = {
    constructor: this,
    // .. 完全就是字面量模式实现脚本
};

然后,需要实现自定义事件功能时候,先new构造下:

var myEvents = new EventTarget();
var yourEvents = new EventTarget();

这样,即使myEvents的事件容器_listener跛掉,也不会污染yourEvents中的自定义事件(_listener安然无恙)。

您可以狠狠地点击这里:原型模式下的JS自定义事件demo

从demo右半区域的源代码展示可以看出如何使用addEvents, fireEvents方法同时添加和触发多个自定义事件的。

 

友情链接:https://www.zhangxinxu.com/wordpress/2012/04/js-dom%E8%87%AA%E5%AE%9A%E4%B9%89%E4%BA%8B%E4%BB%B6/

方法一:
<input id= "btn" value= "按钮" type= "button" onclick= "showmsg();" >
  <script>
   function showmsg(){
   alert( "HTML添加事件处理" );
   }
  </script>
 
方法二:
<input id= "btn" value= "按钮" type= "button" >
  <script>
     var btn= document.getElementById( "btn" );
   btn.onclick= function (){
      alert( "DOM级添加事件处理" );
    }
    btn.onclick= null ; //如果想要删除btn的点击事件,将其置为null即可
  </script>
 
方法三:
<input id= "btn" value= "按钮" type= "button" >
  <script>
   var btn=document.getElementById( "btn" );
   btn.addEventListener( "click" ,showmsg, false ); //这里我们把最后一个值置为false,即不在捕获阶段处理,一般来说冒泡处
理在各浏览器中兼容性较好
   function showmsg(){
   alert( "DOM级添加事件处理程序" );
   }
   btn.removeEventListener( "click" ,showmsg, false ); //如果想要把这个事件删除,只需要传入同样的参数即可
  </script>
 

调用栈                                   》》》》》》》正在执行;

事件表(注册表)            》》》》》》》事件表会时不时监视是否有事件发生从而触发将对应的函数移动到事件队列中等待被执行。   等候调用通知,若收到通知,赶紧去事件队列排队吧。

事件队列                                》》》》》》》》等待被调用哩

事件循环                                》》》》》》》》》调用栈是否为空,该程序会检查事件队列里是否会有正在等待被执行的函数。如果有,队列中的第一个函数会被移动到调用栈中然后被执行。如果事件队列为空,这个监视程序将会一直保持运行,瞧! 我刚刚描述的就是臭名昭着的事件循环。

 

 

事件委托

html:

事件是JavaScript非常重要的一部分。我们想给一个列表中的链接绑定点击事件,一般的做法是写一个循环,给每个链接对象绑定事件,HTML代码如下

<ul id="resources">
  <li><a href="http://opera.com/wsc">Opera Web Standards Curriculum</a></li>
  <li><a href="http://sitepoint.com">Sitepoint</a></li>
  <li><a href="http://alistapart.com">A List Apart</a></li>
  <li><a href="http://yuiblog.com">YUI Blog</a></li>
  <li><a href="http://caibaojian.com">WEB frontend blog</a></li>
  <li><a href="http://www.devtoutiao.com">Develop news</a></li>
</ul>

 

old:

(function(){
  var resources = document.getElementById('resources');
  var links = resources.getElementsByTagName('a');
  var all = links.length;
  for(var i=0;i<all;i++){
    // Attach a listener to each link
    links[i].addEventListener('click',handler,false);
  };
  function handler(e){
    var x = e.target; // Get the link that was clicked
    alert(x);
    e.preventDefault();
  };
})();

 

new:

更合理的写法是只给列表的父对象绑定事件,代码如下:

(function(){
  var resources = document.getElementById('resources');
  resources.addEventListener('click',handler,false);                //在冒泡阶段
  function handler(e){
    var x = e.target; // get the link tha
    if(x.nodeName.toLowerCase() === 'a'){
      alert('Event delegation:' + x);
      e.preventDefault();
    }
  };
})();



http://caibaojian.com/toutiao/5888

事件处理程序

想象一下一个接口, 它唯一的方法是查看键盘上的键是否被按下, 以读取该键的当前状态。为了能够对按键做出反应, 你必须不断地阅读钥匙的状态, 这样你才能在它再次被释放之前抓住它。这将是危险的执行其他时间密集型计算, 因为你可能会错过一个 keypress。

一些原始机器确实处理这样的输入。从这一步起, 将是硬件或操作系统注意到 keypress 并将其放入队列中。然后, 程序可以定期检查队列中的新事件并对其发现的内容做出响应。

当然, 它必须记住查看队列, 并经常这样做, 因为在被按下的键和程序注意到事件之间的任何时间都将导致软件感到不响应。此方法称为轮询。大多数程序员都喜欢避开它。

一个更好的机制是系统在事件发生时主动通知代码。浏览器通过允许我们将函数注册为特定事件的处理程序来实现此目的。

<p>Click this document to activate the handler.</p> <script> window.addEventListener("click", () => { console.log("You knocked?"); }); </script>

绑定引用浏览器提供的内置对象。它表示包含文档的浏览器窗口。调用其方法将在每次发生由其第一个参数描述的事件时注册第二个参数。windowaddEventListener

事件和 DOM 节点

每个浏览器事件处理程序都在上下文中注册。在前面的示例中, 我们调用对象来注册整个窗口的处理程序。在 DOM 元素和一些其他类型的对象上也可以找到这样的方法。只有当事件发生在注册对象的上下文中时, 才会调用事件侦听器。addEventListenerwindow

<button>Click me</button> <p>No handler here.</p> <script> let button = document.querySelector("button"); button.addEventListener("click", () => { console.log("Button clicked."); }); </script>

该示例将处理程序附加到按钮节点。单击按钮会导致该处理程序运行, 但单击文档其余部分则不会。

给节点一个属性具有类似的效果。这适用于大多数类型的事件-您可以通过名称为事件名称的属性附加处理程序。onclickon

但是, 节点只能有一个属性, 因此您只能用这种方式注册每个节点的一个处理程序。该方法允许您添加任意数量的处理程序, 这样即使元素上已经有另一个处理程序, 也可以安全地添加处理程序。onclickaddEventListener

使用类似于的参数调用的方法将移除处理程序。removeEventListeneraddEventListener

<button>Act-once button</button> <script> let button = document.querySelector("button"); function once() { console.log("Done."); button.removeEventListener("click", once); } button.addEventListener("click", once); </script>

给定的函数必须与给定的函数值相同。因此, 要注销处理程序, 您需要给该函数一个名称 (在示例中), 以便能够将相同的函数值传递给这两个方法。removeEventListeneraddEventListeneronce

事件对象

虽然我们已经忽略它到目前为止, 事件处理程序函数传递一个参数:事件对象。此对象包含有关该事件的其他信息。例如, 如果我们想知道按了哪个鼠标按钮, 可以查看事件对象的属性。button

<button>Click me any way you want</button> <script> let button = document.querySelector("button"); button.addEventListener("mousedown", event => { if (event.button == 0) { console.log("Left button"); } else if (event.button == 1) { console.log("Middle button"); } else if (event.button == 2) { console.log("Right button"); } }); </script>

事件对象中存储的信息不同于每种事件类型。我们将在本章后面讨论不同的类型。对象的属性始终包含标识事件的字符串 (如或)。type"click""mousedown"

传播

对于大多数事件类型, 在带子节点上注册的处理程序也将接收在子级发生的事件。如果单击段落内的某个按钮, 则该段落上的事件处理程序也将看到 click 事件。

但是, 如果该段落和按钮都有一个处理程序, 则更具体的处理程序 (按钮上的一个) 就会先进行。据说该事件向外传播, 从它发生在该节点的父节点上的节点到文档的根。最后, 在特定节点上注册的所有处理程序都已轮到它们之后, 在整个窗口中注册的处理程序都有机会响应该事件。

在任何时候, 事件处理程序都可以调用事件对象上的方法, 以防止处理程序从接收事件中更进一步。例如, 在另一个可单击的元素内有一个按钮, 而不希望单击按钮以激活外部元素的 click 行为时, 这一点很有用。stopPropagation

下面的示例在按钮及其周围的段落上注册处理程序。用鼠标右键单击时, 按钮调用的处理程序将阻止该段落上的处理程序运行。使用其他鼠标按钮单击按钮时, 两个处理程序都将运行。"mousedown"stopPropagation

<p>A paragraph with a <button>button</button>.</p> <script> let para = document.querySelector("p"); let button = document.querySelector("button"); para.addEventListener("mousedown", () => { console.log("Handler for paragraph."); }); button.addEventListener("mousedown", event => { console.log("Handler for button."); if (event.button == 2) event.stopPropagation(); }); </script>

大多数事件对象都有一个属性, 它引用它们起源的节点。您可以使用此属性来确保不意外地处理从不希望处理的节点中传播的某些内容。target

还可以使用该属性为特定类型的事件转换大网。例如, 如果有一个节点包含一长串的按钮, 那么在外部节点上注册一个 click 处理程序并让它使用该属性来计算是否单击了某个按钮会更方便, 而不是在所有的按钮。targettarget

<button>A</button> <button>B</button> <button>C</button> <script> document.body.addEventListener("click", event => { if (event.target.nodeName == "BUTTON") { console.log("Clicked", event.target.textContent); } }); </script>

默认操作

许多事件都有与之关联的默认操作。如果单击某个链接, 则会将其带到链接的目标。如果按下箭头, 浏览器将向下滚动该页。如果右键单击, 您将得到一个上下文菜单。等。

对于大多数类型的事件, JavaScript 事件处理程序在默认行为发生之前调用。如果处理程序不希望此正常行为发生, 通常是因为它已经处理了事件, 它可以调用事件对象上的方法。preventDefault

这可用于实现您自己的键盘快捷键或上下文菜单。它还可以用来排演干扰用户所期望的行为。例如, 下面是一个无法遵循的链接:

<a href="https://developer.mozilla.org/">MDN</a> <script> let link = document.querySelector("a"); link.addEventListener("click", event => { console.log("Nope."); event.preventDefault(); }); </script>

除非你有很好的理由, 否则不要做这样的事。这将是不愉快的人谁使用你的网页时, 预期的行为被打破。

根据浏览器的不同, 某些事件根本无法被拦截。例如, 在 Chrome 上, 关闭当前选项卡的键盘快捷方式 (控制w 或命令w) 不能由 JavaScript 处理。

关键事件

按下键盘上的键时, 浏览器将触发事件。当它被释放时, 你会得到一个事件。"keydown""keyup"

<p>This page turns violet when you hold the V key.</p> <script> window.addEventListener("keydown", event => { if (event.key == "v") { document.body.style.background = "violet"; } }); window.addEventListener("keyup", event => { if (event.key == "v") { document.body.style.background = ""; } }); </script>

尽管它的名字, 火不仅当钥匙被物理上推下来。当按键被按下并保持时, 每次键重复时事件都会再次触发。有时候你得小心点例如, 如果在按键被按下时向 DOM 中添加一个按钮, 并且在释放键时再次将其删除, 则当键被按住较长时间时, 可能会意外添加数以百计的按钮。"keydown"

该示例查看事件对象的属性, 以了解事件的关键字。此属性包含一个字符串, 对于大多数键, 它对应于按键将键入的东西。对于特殊键 (如enter), 它保存一个字符串来命名键 (在本例中)。如果按住shift键的同时, 这也可能影响键的名称-成为, 并可能成为, 如果这是按下shift-1 在您的键盘上产生。key"Enter""v""V""1""!"

修改键, 如shift、控件、 alt和元(Mac 上的命令) 生成关键事件, 就像普通键一样。但是, 在查找组合键时, 您还可以通过查看键盘和鼠标事件的、和属性来确定这些键是否被按住。shiftKeyctrlKeyaltKeymetaKey

<p>Press Control-Space to continue.</p> <script> window.addEventListener("keydown", event => { if (event.key == " " && event.ctrlKey) { console.log("Continuing!"); } }); </script>

键事件起源的 DOM 节点取决于按下键时具有焦点的元素。大多数节点都不能有焦点, 除非给它们一个属性, 但链接、按钮和表单域之类的内容可以。我们将回到18 章中的窗体字段。当没有什么特别具有焦点时, 充当关键事件的目标节点。tabindexdocument.body

当用户键入文本时, 使用关键事件来找出正在键入的内容是有问题的。一些平台, 尤其是 Android 手机上的虚拟键盘, 不发射关键事件。但是, 即使你有老式的键盘, 某些类型的文本输入不匹配按键的直接方式, 如输入方法编辑器(IME) 软件的人使用的脚本不适合键盘, 其中多键笔画组合创建字符。

要注意在键入某物时, 可以键入的元素, 如和标记, 每当用户更改其内容时, 都会发生火灾事件。要获取所键入的实际内容, 最好直接从焦点字段中读取它。18 章将展示如何。<input><textarea>"input"

指针事件

目前有两种广泛使用的方法指向屏幕上的东西: 老鼠 (包括像老鼠一样的设备, 如触摸板和 trackballs) 和触摸屏。这些产生不同种类的事件。

鼠标单击

按下鼠标按钮会导致许多事件发生。当按钮被按下并释放时, 和事件类似并发生火灾。这些情况发生在事件发生时鼠标指针下方的 DOM 节点上。"mousedown""mouseup""keydown""keyup"

事件发生后, 事件将在包含媒体和按钮释放的最特定节点上触发。例如, 如果在一个段落上按下鼠标按钮, 然后将指针移动到另一个段落并松开该按钮, 则该事件将发生在包含这些段落的元素上。"mouseup""click""click"

如果两次点击发生在一起, 在第二个 click 事件之后, (双击) 事件也会触发。"dblclick"

若要获取有关发生鼠标事件的位置的精确信息, 可以查看其和属性, 其中包含事件的坐标 (以像素为单位) 相对于窗口的左上角, 或者, 相对于整体的左上角cument (当窗口滚动时可能会有所不同)。clientXclientYpageXpageY

下面实现了一个原始绘图程序。每次单击该文档时, 都会在鼠标指针下添加一个点。有关较少原始绘图程序, 请参见19 章

<style>
  body {
    height: 200px; background: beige; } .dot { height: 8px; width: 8px; border-radius: 4px; /* rounds corners */ background: blue; position: absolute; } </style> <script> window.addEventListener("click", event => { let dot = document.createElement("div"); dot.className = "dot"; dot.style.left = (event.pageX - 4) + "px"; dot.style.top = (event.pageY - 4) + "px"; document.body.appendChild(dot); }); </script>

鼠标运动

每次鼠标指针移动时, 都会触发一个事件。此事件可用于跟踪鼠标的位置。在实现某种形式的鼠标拖动功能时, 这一常见情况非常有用。"mousemove"

例如, 下面的程序显示一个条形图并设置事件处理程序, 以便在该条形图上向左或向右拖动使其变窄或更宽:

<p>Drag the bar to change its width:</p> <div style="background: orange; width: 60px; height: 20px"> </div> <script> let lastX; // Tracks the last observed mouse X position let bar = document.querySelector("div"); bar.addEventListener("mousedown", event => { if (event.button == 0) { lastX = event.clientX; window.addEventListener("mousemove", moved); event.preventDefault(); // Prevent selection } }); function moved(event) { if (event.buttons == 0) { window.removeEventListener("mousemove", moved); } else { let dist = event.clientX - lastX; let newWidth = Math.max(10, bar.offsetWidth + dist); bar.style.width = newWidth + "px"; lastX = event.clientX; } } </script>

请注意, 该处理程序已在整个窗口中注册。即使在调整大小时鼠标在条形图之外, 只要按住按钮, 我们仍希望更新其大小。"mousemove"

在松开鼠标按钮时, 我们必须停止调整条形图的大小。为此, 我们可以使用属性 (注释复数), 它告诉我们当前按住的按钮。当这是零, 没有按钮下来。当按钮被持有, 它的值是这些按钮的代码的总和-左按钮有代码 1, 右按钮 2, 中间一个4。这样, 您可以检查给定的按钮是否按下了其余的值及其代码。buttonsbuttons

请注意, 这些代码的顺序不同于使用的, 其中中间的按钮出现在右侧。如上所述, 一致性并不是浏览器编程接口的强项。button

触摸事件

我们使用的图形浏览器的风格是设计与鼠标界面, 在这个时候触摸屏是罕见的。为了在早期触摸屏手机上进行网络 "工作", 这些设备的浏览器在一定程度上假装触摸事件是鼠标事件。如果你点击你的屏幕, 你会得到, 和事件。"mousedown""mouseup""click"

但这种错觉不是很健壮。触摸屏的工作方式与鼠标不同: 它没有多个按钮, 在屏幕上无法跟踪手指 (模拟), 并且它允许多个手指同时在屏幕上。"mousemove"

鼠标事件仅在简单的情况下覆盖触摸交互-如果将处理程序添加到按钮, 则触摸用户仍然可以使用它。但是类似于上例中的 resizeable 栏的东西在触摸屏上不起作用。"click"

有一些特定的事件类型由触摸交互激发。当手指开始接触屏幕时, 你会得到一个事件。当它在触摸时移动, 事件会触发。最后, 当它停止触摸屏幕时, 您将看到一个事件。"touchstart""touchmove""touchend"

因为许多触摸屏可以同时检测多个手指, 所以这些事件没有与它们关联的一组坐标。相反, 它们的事件对象具有一个属性, 它包含一个类似于数组的点对象, 每一个都有它自己的、和属性。touchesclientXclientYpageXpageY

你可以做这样的事情, 在每个感人的手指上显示红色圆圈:

<style>
  dot { position: absolute; display: block; border: 2px solid red; border-radius: 50px; height: 100px; width: 100px; } </style> <p>Touch this page</p> <script> function update(event) { for (let dot; dot = document.querySelector("dot");) { dot.remove(); } for (let i = 0; i < event.touches.length; i++) { let {pageX, pageY} = event.touches[i]; let dot = document.createElement("dot"); dot.style.left = (pageX - 50) + "px"; dot.style.top = (pageY - 50) + "px"; document.body.appendChild(dot); } } window.addEventListener("touchstart", update); window.addEventListener("touchmove", update); window.addEventListener("touchend", update); </script>

您通常希望调用 "触摸事件处理程序" 来覆盖浏览器的默认行为 (可能包括在刷卡时滚动页面), 并防止触发鼠标事件, 您可以有一个处理程序。preventDefault

滚动事件

每当滚动元素时, 就会触发一个事件。这有多种用途, 例如了解用户当前正在查看的内容 (用于禁用屏幕动画或将间谍报告发送到您的邪恶总部) 或显示一些进展的指示 (通过突出显示目录的一部分或显示页面号)。"scroll"

下面的示例在文档的上方绘制一个进度栏, 并在向下滚动时更新它以进行填充:

<style>
  #progress {
    border-bottom: 2px solid blue; width: 0; position: fixed; top: 0; left: 0; } </style> <div id="progress"></div> <script> // Create some content document.body.appendChild(document.createTextNode( "supercalifragilisticexpialidocious ".repeat(1000))); let bar = document.querySelector("#progress"); window.addEventListener("scroll", () => { let max = document.body.scrollHeight - innerHeight; bar.style.width = `${(pageYOffset / max) * 100}%`; }); </script>

给一个元素 a 的行为很像一个位置, 但也阻止它与文档的其余部分一起滚动。效果是让我们的进度栏保持在顶端。更改其宽度以指示当前进度。我们使用, 而不是作为一个单位时, 设置的宽度, 使该元素的大小相对于页面宽度。positionfixedabsolute%px

全局绑定为我们提供了窗口的高度, 我们必须从总可滚动的高度中减去它-当您点击文档底部时, 您无法继续滚动。还有一个为窗口宽度。通过划分, 当前滚动位置, 由最大滚动位置和乘以 100, 我们得到进度栏的百分比。innerHeightinnerWidthpageYOffset

调用滚动事件不会阻止滚动发生。实际上, 只有在滚动发生之后才调用事件处理程序。preventDefault

焦点事件

当元素获得焦点时, 浏览器会在其上触发一个事件。当它失去焦点时, 元素将获取一个事件。"focus""blur"

与前面讨论的事件不同, 这两个事件不传播。当子元素获取或丢失焦点时, 不会通知父元素上的处理程序。

下面的示例显示当前具有焦点的文本字段的帮助文本:

<p>Name: <input type="text" data-help="Your full name"></p> <p>Age: <input type="text" data-help="Your age in years"></p> <p id="help"></p> <script> let help = document.querySelector("#help"); let fields = document.querySelectorAll("input"); for (let field of Array.from(fields)) { field.addEventListener("focus", event => { let text = event.target.getAttribute("data-help"); help.textContent = text; }); field.addEventListener("blur", event => { help.textContent = ""; }); } </script>

当用户从显示文档的浏览器选项卡或窗口移动时, 窗口对象将接收和事件。"focus""blur"

加载事件

当页面完成加载时, 事件会在窗口和文档正文对象上触发。这通常用于计划需要生成整个文档的初始化操作。请记住, 当遇到标记时, 标记的内容会立即运行。这可能太快了, 例如, 当脚本需要对标记后面出现的文档的某些部分执行某项内容时。"load"<script><script>

诸如图像和加载外部文件的脚本标记之类的元素也有一个事件, 指示它们引用的文件已加载。与焦点相关的事件类似, 加载事件不会传播。"load"

当页面关闭或导航离开时 (例如, 通过跟踪链接), 事件将触发。此事件的主要用途是通过关闭文档来防止用户意外丢失工作。如您所料, 阻止页面卸载并不像您预期的那样使用该方法。相反, 它是通过从处理程序返回非 null 值来完成的。这样做时, 浏览器将向用户显示一个对话框, 询问他们是否确定要离开该页。这种机制可以确保用户始终能够离开, 即使是在恶意的网页上, 他们希望永远保持他们在那里, 并迫使他们看不靠谱的减肥广告。"beforeunload"preventDefault

事件和事件循环

在事件循环的上下文中, 如11 章所述, 浏览器事件处理程序的行为与其他异步通知类似。它们是在事件发生时排定的, 但必须等待正在运行的其他脚本在他们有机会运行之前完成。

只有在没有其他运行时才可以处理事件的事实意味着, 如果事件循环与其他工作捆绑在一起, 则与该页的任何交互 (通过事件发生) 将被延迟, 直到有时间处理它。所以, 如果你安排太多的工作, 无论是长时间运行的事件处理程序或大量的短期运行的, 页面将变得缓慢和繁琐的使用。

如果你的想在不冻结页面的情况下在后台做一些耗时的事情, 浏览器会提供一些叫做web 工作者的东西。工作人员是一个 JavaScript 进程, 它与主脚本一起运行在其自己的时间轴上。

假设一个数字的平方是一个沉重的, 长时间运行的计算, 我们要在一个单独的线程中执行。我们可以通过计算一个正方形并发送一条消息来编写一个名为响应消息的文件。code/squareworker.js

 
 
 
 
 
3
 
 
 
1
addEventListener("message", event => {
2
  postMessage(event.data * event.data);
3
});
 
 
 
 
addEventListener("message", event => {
  postMessage(event.data * event.data); });

为避免多个线程接触相同数据的问题, 工作人员不与主脚本的环境共享其全局范围或任何其他数据。相反, 您必须通过来回发送消息来与它们进行通信。

此代码生成运行该脚本的工作人员, 向其发送一些消息, 并输出响应。

 
 
 
 
 
6
 
 
 
1
let squareWorker = new Worker("code/squareworker.js");
2
squareWorker.addEventListener("message", event => {
3
  console.log("The worker responded:", event.data);
4
});
5
squareWorker.postMessage(10);
6
squareWorker.postMessage(24);
 
 
 
 
let squareWorker = new Worker("code/squareworker.js"); squareWorker.addEventListener("message", event => { console.log("The worker responded:", event.data); }); squareWorker.postMessage(10); squareWorker.postMessage(24);

该函数发送一条消息, 这将导致在接收器中引发事件。创建该工作人员的脚本通过对象发送和接收消息, 而工作人员通过直接发送和侦听其全局范围来与创建它的脚本进行对话。只有可以表示为 JSON 的值才能作为消息发送-另一侧将收到它们的副本, 而不是值本身。postMessage"message"Worker

定时器

我们在11 章中看到了这个功能。在给定的毫秒数之后, 它会安排另一个函数稍后调用。setTimeout

有时, 您需要取消已计划的函数。这是通过存储返回的值和调用它来完成的。setTimeoutclearTimeout

let bombTimer = setTimeout(() => {
  console.log("BOOM!"); }, 500); if (Math.random() < 0.5) { // 50% chance console.log("Defused."); clearTimeout(bombTimer); }

该函数的工作方式与-在返回的值上调用它将取消该框架 (假定它尚未被调用) 相同。cancelAnimationFrameclearTimeoutrequestAnimationFrame

一组类似的函数, 用于设置应该每隔X毫秒重复一次的计时器。setIntervalclearInterval

let ticks = 0;
let clock = setInterval(() => { console.log("tick", ticks++); if (ticks == 10) { clearInterval(clock); console.log("stop."); } }, 200);

Debouncing

某些类型的事件有可能快速地进行多次 (例如, 事件)。当处理此类事件时, 您必须注意不要做太耗时的事情, 否则处理程序将占用这么多时间, 与文档的交互开始感到缓慢。"mousemove""scroll"

如果你确实需要在这样的处理程序中做一些不平凡的事情, 你可以用它来确保你做的不是太频繁。这通常称为debouncing事件。对此有几种略有不同的方法。setTimeout

在第一个示例中, 我们希望在用户键入某种内容时做出反应, 但我们不想在每次输入事件时立即对其进行响应。当他们快速打字时, 我们只想等到暂停。我们没有立即在事件处理程序中执行操作, 而是设置了超时。我们还清除以前的超时 (如果有), 以便当事件发生在一起 (比超时延迟更接近) 时, 上一事件的超时时间将被取消。

<textarea>Type something here...</textarea> <script> let textarea = document.querySelector("textarea"); let timeout; textarea.addEventListener("input", () => { clearTimeout(timeout); timeout = setTimeout(() => console.log("Typed!"), 500); }); </script>

在已触发的超时中给出未定义的值或调用它不会产生任何影响。因此, 我们不必对何时调用它很小心, 我们只是这样做的每一个事件。clearTimeout

我们可以使用一个稍微不同的模式, 如果我们想要的空间响应, 使他们被分开至少有一定的时间, 但希望在一系列事件, 而不是事后, 他们点燃。例如, 我们可能希望通过显示鼠标的当前坐标, 但仅每隔250毫秒来响应事件。"mousemove"

<script>
  let scheduled = null; window.addEventListener("mousemove", event => { if (!scheduled) { setTimeout(() => { document.body.textContent = `Mouse at ${scheduled.pageX}, ${scheduled.pageY}`; scheduled = null; }, 250); } scheduled = event; }); </script>

总结

事件处理程序使我们能够检测和响应网页中发生的事件。该方法用于注册此类处理程序。addEventListener

每个事件都有一个类型 (, 等等) 来标识它。大多数事件在特定的 DOM 元素上调用, 然后传播到该元素的祖先, 允许与这些元素关联的处理程序处理它们。"keydown""focus"

调用事件处理程序时, 会将事件对象传递到事件的附加信息。此对象还具有允许我们停止进一步传播 () 并防止浏览器对事件 () 进行默认处理的方法。stopPropagationpreventDefault

按下关键火灾和事件。按下鼠标按钮火, 和事件。移动鼠标触发事件。触摸屏交互将导致, 和事件。"keydown""keyup""mousedown""mouseup""click""mousemove""touchstart""touchmove""touchend"

可以通过事件检测到滚动, 并且可以使用 and 事件检测焦点更改。当文档完成加载时, 窗口上会触发一个事件。"scroll""focus""blur""load"

练习

气球

编写显示气球的页面 (使用气球 emoji 表情)。当你按向上箭头, 它应该膨胀 (增长) 10%, 当你按下箭头, 它应该压缩 (收缩) 10%。

通过在其父元素上设置 CSS 属性 (), 可以控制文本的大小 (emoji 表情为文本)。请记住在值中包括一个单位, 例如像素 ()。font-sizestyle.fontSize10px

箭头键的键名为和。请确保键只更改气球, 而不滚动页面。"ArrowUp""ArrowDown"

当它起作用时, 添加一个功能, 如果你把气球吹过某个尺寸, 它就会爆炸。在这种情况下, 爆炸意味着它被 emoji 表情替换, 并且事件处理程序被移除 (这样你就不能充气或压缩爆炸)。

<p>?</p> <script> // Your code here </script>

您将需要为该事件注册一个处理程序, 并查看是否按下了向上键或向下箭头。"keydown"event.key

可以将当前大小保留在绑定中, 以便可以将新大小基于它。在 DOM 中定义一个函数来更新大小 (绑定和气球的样式), 以便您可以从事件处理程序中调用它, 并且在启动时也有可能会设置初始大小。

通过将文本节点替换为另一个 (使用) 或将其父节点的属性设置为新字符串, 可以将该气球更改为爆炸。replaceChildtextContent

鼠标跟踪

在 JavaScript 的早期, 这是一个华丽的网页, 有很多动画图像的高时间, 人们想出了一些真正鼓舞人心的方式使用的语言。

其中之一是鼠标跟踪--在您将鼠标指针移动到页面上时, 将跟随该元素的一系列内容。

在本练习中, 我希望您实现鼠标跟踪。使用具有固定大小和背景颜色的绝对定位元素 (请参阅 "鼠标单击" 部分中的代码以进行示例)。创建一堆这样的元素, 当鼠标移动时, 在鼠标指针后显示它们。<div>

这里有各种可能的方法。您可以使您的解决方案尽可能简单或复杂。开始使用的一个简单的解决方案是保持固定数量的跟踪元素并循环通过它们, 在每次发生事件时将下一条移动到鼠标的当前位置。"mousemove"

<style>
  .trail { /* className for the trail elements */
    position: absolute; height: 6px; width: 6px; border-radius: 3px; background: teal; } body { height: 300px; } </style> <script> // Your code here. </script>

创建元素最好用一个循环来完成。将它们追加到文档中以使其显示出来。为了能够在以后访问它们以更改它们的位置, 您需要将元素存储在数组中。

循环通过它们可以通过保持一个计数器变量, 并增加 1, 每次事件触发。然后, 可以使用余数运算符 () 获取有效的数组索引, 以便在给定事件期间选取要定位的元素。"mousemove"% elements.length

另一个有趣的效果可以通过建模一个简单的物理系统来实现。仅使用此事件可更新跟踪鼠标位置的一对绑定。然后用于模拟被吸引到鼠标指针位置的尾随元素。在每个动画步骤中, 根据相对于指针的位置更新其位置 (也可以选择为每个元素存储的速度)。想出一个好办法来做这事由你自己来做。"mousemove"requestAnimationFrame

标签

选项卡式面板广泛应用于用户界面。通过从元素上方 "伸出" 的多个选项卡中选择, 可以选择接口面板。

在本练习中, 您必须实现一个简单的选项卡式接口。编写一个函数, 它采用 DOM 节点, 并创建一个选项卡式接口, 显示该节点的子元素。它应该插入节点顶部的元素列表, 每个子元素都有一个, 其中包含从子属性检索的文本。只有一个原始的孩子应该被隐藏 (给定的风格)。可以通过单击按钮来选择当前可见的节点。asTabs<button>data-tabnamedisplaynone

如果该方法有效, 请将其扩展为当前所选选项卡的按钮样式不同, 以便明显选择哪个选项卡。

<tab-panel>
  <div data-tabname="one">Tab one</div> <div data-tabname="two">Tab two</div> <div data-tabname="three">Tab three</div> </tab-panel> <script> function asTabs(node) { // Your code here. } asTabs(document.querySelector("tab-panel")); </script>

可能遇到的一个陷阱是不能直接将节点的属性用作制表符节点的集合。首先, 当您添加这些按钮时, 它们也将成为子节点, 并最终在此对象中, 因为它是一个实时数据结构。另外, 为节点之间的空白创建的文本节点也在其中, 但不应获取它们自己的选项卡。可以使用而不是忽略文本节点。childNodeschildNodeschildrenchildNodes

您可以开始建立一个选项卡数组, 以便您可以轻松地访问它们。若要实现按钮的样式, 可以存储包含 "选项卡" 面板及其按钮的对象。

我建议为更改制表符编写一个单独的函数。您可以存储以前选定的选项卡, 只更改隐藏该标签所需的样式, 并显示新选项, 也可以在每次选择新标签时更新所有选项卡的样式。

您可能希望立即调用此函数以使接口以可见的第一个选项卡开始。

 

 

 

友情链接:http://eloquentjavascript.net/15_event.html

 

转载于:https://www.cnblogs.com/Longhua-0/p/9318894.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值