文档章节

JavaScript响应事件

gaodq
 gaodq
发布于 2016/03/23 11:07
字数 3583
阅读 16
收藏 0

每当猴子们问我JavaScript和DOM里啥东西最牛逼时,我都会一巴掌打回去:卧槽还用问么当然是事件响应了啊!没它你能有时间和我讨论这个?你早去工地搬砖去了好么!浏览器没有事件响应就没有行为交互,那简直就是一夜回到解放前的感觉啊。此外,以事件驱动使得功能解耦也是个相当高端大气的技巧了,嘛,以此为主的Node.js 现在可是风生水起的说。

那现在我们就再抠抠事件监听的相关基础,让大家在心情愉悦的状态下获得更多的姿♂势。不过那些经常写<a href=”javascript:void(0)”>和在标签上写onclick=”foo()”的猴子们请自动回避,小心你看不懂又想不开,老衲徒增罪孽呀(偶八年前就解释了内联事件处理是自寻死路)。

再唠叨两句:本文的代码内容只涉及到原生JavaScript,像JQuery,YUI或Dojo什么的所提供的事件处理这里就不加以赘述了。我希望大家能够明白,使用这些库只是为了方便,但我们却不能完全依赖它。了解基础与理解本质是非常重要的,这样你就可以在不能使用傻瓜库的情况下,依旧可以提供一个牛逼的解决方案。

进化主义声明:这里我们使用的事件语法是“DOM Level 3 Events”规范定义的“addEventListener()。除了IE9以下版本以外的现代浏览器都支持。当然,我们可以使用JQuery,它会帮我解决这些浏览器兼容性的问题。但如果你还想让互联网可以良好发展和进化,你就应该立刻停止为兼容低级浏览器而再去写一坨屎一样的傻逼兼容代码。这条路虽然艰辛,但却无比正确。可以试着给你的产品进行功能降级,检测到是低级浏览器就不执行脚本,比如addEventListener()的DOMContentLoaded事件就能确保你的代码不在低级浏览器中运行,而页面可以将主体内容正常显示就OK的。

在我们进入事件的细节之前先看几个牛逼的演示,它利用onscroll事件得到了一个很nice的效果:

  • 因为要招设计师,Wealthfront的工程师们开发了Z轴滚动平移效果。这也是Beercamp 2011 website的一部分。Wealthfront的博客有细节介绍。

  • Stroll.js用的也是类似的手法,用户可以在滚动列表时看到很多种炫酷展现。

  • jQuery Scroll Path是一个JQ插件,它的功能是当用户在页面内能够跟随着一条路径去动态浏览内容。

以上所有都是基于浏览器的事件监听和处理功能,所以,让我们细细品味一下原汁原味的事件机制吧。

基础问题:啥是事件?

var log = document.getElementById( 'log' ),
     i = '' ,
     out = [];
for (i in window) {
   if ( /^on/.test(i)) { out[out.length] = i; }
}
log.innerHTML = out.join( ', ' );

在浏览器中运行如上代码,亲们可以得到如下:

onmouseenter, onmouseleave, onafterprint, onbeforeprint, onbeforeunload, onhashchange, onmessage, onoffline, ononline, onpopstate, onpagehide, onpageshow, onresize, onunload, ondevicemotion, ondeviceorientation, onabort, onblur, oncanplay, oncanplaythrough, onchange, onclick, oncontextmenu, ondblclick, ondrag, ondragend, ondragenter, ondragleave, ondragover, ondragstart, ondrop, ondurationchange, onemptied, onended, onerror, onfocus, oninput, oninvalid, onkeydown, onkeypress, onkeyup, onload, onloadeddata, onloadedmetadata, onloadstart, onmousedown, onmousemove, onmouseout, onmouseover, onmouseup, onmozfullscreenchange, onmozfullscreenerror, onpause, onplay, onplaying, onprogress, onratechange, onreset, onscroll, onseeked, onseeking, onselect, onshow, onstalled, onsubmit, onsuspend, ontimeupdate, onvolumechange, onwaiting, oncopy, oncut, onpaste, onbeforescriptexecute, onafterscriptexecute

一大坨事件就够你吃几天的了,用addEventListener()方法可以进行事件监听:

element.addEventListener(event, handler, useCapture);

举个例子来说:

var a = document.querySelector( 'a' ); // grab the first link in the document
a.addEventListener( 'click' , ajaxloader, false );

我们在一个element上加了个事件监听,就好像是在命令她,“你被客人摸了你就给我喊起来!” The ajaxloader()是监听事件的回调方法,就好像是,“你就在这儿给我盯着,妞要是喊了,你就过去给客人道歉!” 将第三个参数useCapture设置为false是为了表示这次是在事件冒泡阶段进行触发,而不是在事件捕获阶段。咳咳,这是一个漫长而艰巨的课题,你也可以看看Dev.Opera对capture的解释。哎呀反正不用管那么多啦,99.7434%的情况下设置useCapture为false准没错!其实它默认就是false,所以按理来说是可以不用填写的,但Opera这逗比例外…

在事件被触发之时,回调方法会接收到一个事件对象。请试着在恰当的环境中运行如下代码,也可以直接点击这里测试这个例子,对象内的属性又够吃一盆的:

var log = document.getElementById( 'log' ),
     out = '' ;
 
document.addEventListener( 'click' , logeventinfo, false );
document.addEventListener( 'keypress' , logeventinfo, false );
 
function logeventinfo (ev) {
   log.innerHTML = '' ;
   out = '<ul>' ;
   for ( var i in ev) {
     if ( typeof ev[i] === 'function' || i === i.toUpperCase()) {
       continue ;
     }
     out += '<li><span>' +i+ '</span>: ' +ev[i]+ '</li>' ;
   }
   log.innerHTML += out + '</ul>' ;
}

你可以对同一事件进行多重监听,也可以对多个事件使用同一方法处理(如本例)。

参数ev是传回来的事件对象,下面是它所带的全部属性:

originalTarget: [object HTMLHtmlElement]
type: click
target: [object HTMLHtmlElement]
currentTarget: [object HTMLDocument]
eventPhase: 3
bubbles: true
cancelable: true
timeStamp: 574553210
defaultPrevented: false
which: 1
rangeParent: [object Text]
rangeOffset: 23
pageX: 182
pageY: 111
isChar: false
screenX: 1016
screenY: 572
clientX: 182
clientY: 111
ctrlKey: false
shiftKey: false
altKey: false
metaKey: false
button: 0
relatedTarget: null
mozPressure: 0
mozInputSource: 1
view: [object Window]
detail: 1
layerX: 182
layerY: 111
cancelBubble: false
explicitOriginalTarget: [object HTMLHtmlElement]
isTrusted: true
originalTarget: [object HTMLHeadingElement]
type: click
target: [object HTMLHeadingElement]
currentTarget: [object HTMLDocument]
eventPhase: 3
bubbles: true
cancelable: true
timeStamp: 574554192
defaultPrevented: false
which: 1
rangeParent: [object Text]
rangeOffset: 0
pageX: 1
pageY: 18
isChar: false
screenX: 835
screenY: 479
clientX: 1
clientY: 18
ctrlKey: false
shiftKey: false
altKey: false
metaKey: false
button: 0
relatedTarget: null
mozPressure: 0
mozInputSource: 1
view: [object Window]
detail: 1
layerX: 1
layerY: 18
cancelBubble: false
explicitOriginalTarget: [object Text]
isTrusted: true

试一下本例中点击鼠标和按键盘,不同的事件触发传回来的结果是不同的。可以看看完整的标准事件属性列表

知晓基础之后:阻止事件默认行为的执行和获得触发事件的目标元素

我们需要了解浏览器中关于事件对象有两个很重要的功能。阻止浏览器执行事件默认行为的ev.preventDefault(),和可以获得事件目标元素的ev.target.

比如说一个链接被点击了,但因为业务需要,我们并不想让浏览器打开新页面。这时候可以给这个链接加个点击事件监听,然后在回调函数中调用 preventDefault()方法即可。昂,就如下面这个例子,请看HTML:

<a class= "prevent" href= "http://smashingmagazine.com" >Smashing, my dear!</a>
<a class= "normal" href= "http://smashingmagazine.com" >Smashing, my dear!</a>

还有JavaScript:

var normal = document.querySelector( '.normal' ),
     prevent = document.querySelector( '.prevent' );
 
prevent.addEventListener( 'click' , function (ev) {
   alert( 'fabulous, really!' );
   ev.preventDefault();
}, false );
 
normal.addEventListener( 'click' , function (ev) {
   alert( 'fabulous, really!' );
}, false );

注意: document.querySelector() 是合理获取DOM元素的一种方式。和jQuery的 $() 差不多。 可以读读 W3C’s specification 和MDN的 explanatory code snippets 去了解。

如果点击.prevent链接,会弹出个对话框,点“确定”后啥事都没发生,呵~呵~,因为处理中有执行ev.preventDefault(),所以不会跳到 http://smashingmagazine.com。没有 preventDefault()的就会在弹对话框,且跳链接咯。不信你可以试一下嘛

通常情况下,处理事件的方法想要访问触发元素只能通过变量和this什么的去关联,虽然看似简单方便,但addEventListener()给了我们更好的选择:事件目标(target),不过它可能被其他的一些东西混淆,所以用ev.currentTarget更保险些。通常想要在点击、悬停或输入事件的回调方法中访问触发元素都是用变量 this 。虽然方便,但这个关键字你懂得…于是 addEventListener() 提供给我们一个更好的获取方式:event.target。 不过它可能会被混淆( this 被绑到奇怪的东西上的时候),所以用 ev.currentTarget 更保险些。

事件代理:高端,大气,上档次!

用事件对象的 target 属性,你可以得到触发事件的元素。

事件被激活后,会像猴子一样沿着DOM树从监听节点下滑到触发节点,然后再上爬回监听节点。也就是说,如果你监听了一个DOM节点,那也就等于你监听了其所有的后代节点。代理的意思就是只监听父节点的事件触发,以来代理对其后代节点的监听,而你需要做的只是通过 target 属性得到触发元素并作出回应。来看我下面的例子

<ul id= "resources" >
   <li><a href= "http://developer.mozilla.org" >MDN</a></li>
   <li><a href= "http://html5doctor.com" >HTML5 Doctor</a></li>
   <li><a href= "http://html5rocks.com" >HTML5 Rocks</a></li>
   <li><a href= "http://beta.theexpressiveweb.com/" >Expressive Web</a></li>
   <li><a href= "http://creativeJS.com/" >CreativeJS</a></li>
</ul>

这个例子中的HTML结构是个无序列表,当鼠标悬停会显示相应的标签信息。下面是它JS代码,你将看到它只用到了一个事件监听,然后在处理函数中得到target,以它的 tagName 来进行区分。

var resources = document.querySelector( '#resources' ),
     log = document.querySelector( '#log' );
 
resources.addEventListener( 'mouseover' , showtarget, false );
 
function showtarget(ev) {
   var target = ev.target;
   if (target.tagName === 'A' ) {
     log.innerHTML = 'A link, with the href:' + target.href;
   }
   if (target.tagName === 'LI' ) {
     log.innerHTML = 'A list item' ;
   }
   if (target.tagName === 'UL' ) {
     log.innerHTML = 'The list itself' ;
   }
}

我费事吧啦解释了半天,乃们懂得这么做意味着什么么?这意味着你可以节省大量重复的事件监听,以减少浏览器资源消耗。大多数人可能会用jQuery的$(‘a’).click(…) 啥啥啥的(虽然 jQuery的 on 方法优化的还OK啦不过偶还是蛮鄙视他的),这么做看似一句话蛮带感的,可其实它是把获取到的所有A元素一个一个的注册监听!然后在某个时刻,充斥着无数事件监听的页面终于觉累不爱,自爆以鸣冤屈。

她还有一个好处就是让HTML独立起来,比如之后还有要加子元素的需求,也不需要再为其单独加事件监听了。

事件监测,顺便说一下超级牛逼的CSS平滑效果

以前我们会用mouseover|mouseout事件来暗挫挫的实现hover效果,而现在用CSS伪选择器的:hover和:focus什么的就直接搞定了。想到这里,内心止不住的伤感啊……魔法师们坚持住!咳咳,当然,CSS也并不是万能的,有些事情还是要跟事件配合完成,比如下面这个例子,对鼠标指针进行定位。这是相当简单的了是不,我们先搞个绝对定位的小球元素,下面是它的HTML:

<div class="plot"></div>

这是它的CSS:

.plot {
   position:absolute;
   background:rgb(175,50,50);
   width: 20px;
   height: 20px;
   border-radius: 20px;
   display: block;
   top:0;
   left:0;
}

我们监听并处理doucment的click事件,利用PageX和pageY对小球进行定位。注意啊这里,我们需要减去球的半径,以让球的中心在鼠标指针上:

var plot = document.querySelector( '.plot' ),
     offset = plot.offsetWidth / 2;
document.addEventListener( 'click' , function (ev) {
   plot.style.left = (ev.pageX - offset) + 'px' ;
   plot.style.top = (ev.pageY - offset) + 'px' ;
}, false );

随便点击屏幕的任意位置,小球都会随之闪现到那。不过它并不是平滑过去的,但如果你勾选这个示例的复选框,你会发现小球就会很圆润的滑过来了。这个效果呢,过去的话可能只能用JS库来完成,但现在啊,时代不同了……我们只需要用CSS写个过渡效果的类,而剩下的事情就让浏览器去处理。为至于此,我们写个类名为smooth的样式,在复选框被选中之后,将其应用到小球上:

.smooth {
   -webkit-transition: 0.5s;
      -moz-transition: 0.5s;
       -ms-transition: 0.5s;
        -o-transition: 0.5s;
           transition: 0.5s;
}

添加JavaScript

var cb = document.querySelector('input[type=checkbox]');

cb.addEventListener( 'click' , function (ev) {
   plot.classList.toggle( 'smooth' );
}, false );

随着新世界的来临,CSS和JavaScript双剑合璧,谁与争锋!嘛顺便说下,在JS中也有跟CSS过渡和动画效果有关的事件噢。

撸一个键需要多长时间?

正如你之前在可用事件列表中看到的,我们也可以监听按键输入事件。不过很遗憾的是,浏览器对键盘事件处理的并不是很到位,你可以看看Jan Wolter对此的详细解释接下来让我们看一个keytime的例子,它会输出用户按键的毫秒间隔。代码并不难:

var resources = document.querySelector( '#resources' ),
     log = document.querySelector( '#log' ),
     time = 0;
 
document.addEventListener( 'keydown' , keydown, false );
document.addEventListener( 'keyup' , keyup, false );
 
function keydown(ev) {
   if (time === 0) {
     time = ev.timeStamp;
     log.classList.add( 'animate' );
   }
}
function keyup(ev) {
   if (time !== 0) {
     log.innerHTML = ev.timeStamp - time;
     time = 0;
     log.classList.remove( 'animate' );
   }
}

先定义我们想要操纵的元素并设置time为0。然后我们在document上监听两个键盘输入事件 keydown和keyup。

在keydown事件处理中,我们检查变量time是否为0,如果是则把事件对象的timeStamp赋值给time。再加个CSS动画类animate给log节点,让它向滚动条一样动起来

在keyup事件处理中,如果time还是为0则忽略(在按着键盘的期间keydown事件是连续不断被触发的),如果不是则通过两者时间戳相减去计算一次按键操作经过多长时间。最后让time为0并移除log节点的animate类

弄个CSS过渡(动画)效果

当浏览器运行CSS过渡效果是会在JavaScript中触发一个独立事件,叫transitionend。这个事件对象会有两个属性:被其所影响到的属性名propertyName,和其过渡所经历的时间elapsedTime。

可以查看这个demo感受一下,代码很简单,下面是它的CSS:

plot {
   background:rgb(175,50,50);
   width: 20px;
   height: 20px;
   border-radius: 20px;
   display: block;
   -webkit-transition: 0.5s;
      -moz-transition: 0.5s;
       -ms-transition: 0.5s;
        -o-transition: 0.5s;
           transition: 0.5s;
}
 
.plot:hover {
   width: 50px;
   height: 50px;
   border-radius: 100px;
   background: blue;
}

这是他的JavaScript:

plot.addEventListener( 'transitionend' , function (ev) {
   log.innerHTML += ev.propertyName + ':' + ev.elapsedTime + 's ' ;
}, false );

但因为Fire/Chrome/Safari/Opera等这些浏览器厂商各自为政,也是因为这些事件还不成熟,所以这些事件名通常都会被加上前缀,那在使用时你就不得不判断下浏览器兼容性。可以看看这个David Calhoun’s gist

CSS动画事件和上面演示的过渡事件基本一个意思,它有三个事件:animationstart,animationend和animationiteration。可以看MDN的demo

速度,距离和角度,没有问题!

后面还有看连接http://blog.jobbole.com/51889/

本文转载自:http://blog.jobbole.com/51889/

gaodq
粉丝 0
博文 6
码字总数 791
作品 0
浦东
私信 提问
Cocos2d-JS键盘事件

Cocos2d-JS中的键盘事件与触摸事件不同,它没有空间方面信息。键盘事件不仅可以响应键盘,还可以响应设备的菜单。 键盘事件是EventKeyboard,对应的键盘事件监听器(cc.EventListener.KEYBOA...

智捷课堂
2015/04/01
81
0
Responsive Javascript 是什么?

Responsive Javascript 是什么? 简单来说就是可以根据浏览器的状态做出响应。响应包括对视窗大小的反应,根据你设备是否支持触摸事件或地理定位功能来决定是否显示特定内容,不一而足。 什么...

oschina
2014/02/23
2.9K
3
Javascript的事件模型和Promise实现

1. Javascript的运行时模型——事件循环 JS的运行时是个单线程的运行时,它不像其他编程语言,比如C++,Java,C#这些可以进行多线程操作的语言。当它执行一个函数时,它只会一条路走到黑,不...

远方无风
2018/07/12
0
0
JS与Object-C交互补充

有一个场景,用户点击Web页面,产生JS交互时,如何根据JS事件,App做出响应。 我们可以自己做一个简单的js交互,实现基于的原理是: 1. object-c利用stringByEvaluatingJavaScriptFromStrin...

treebug
2015/06/06
432
0
关于Javascript技术体系

Javascript技术体系主要包含了五个方面的内容: Javascript核心语言定义:每一种编程语言都有自已的核心语法,包括数据类型,变量,常量,运算符,语句等。 原生对象和内置对象:为方便程序员...

倪九林
2014/09/15
288
0

没有更多内容

加载失败,请刷新页面

加载更多

Giraph源码分析(八)—— 统计每个SuperStep中参与计算的顶点数目

作者|白松 目的:科研中,需要分析在每次迭代过程中参与计算的顶点数目,来进一步优化系统。比如,在SSSP的compute()方法最后一行,都会把当前顶点voteToHalt,即变为InActive状态。所以每次...

数澜科技
今天
4
0
Xss过滤器(Java)

问题 最近旧的系统,遇到Xss安全问题。这个系统采用用的是spring mvc的maven工程。 解决 maven依赖配置 <properties><easapi.version>2.2.0.0</easapi.version></properties><dependenci......

亚林瓜子
今天
10
0
Navicat 快捷键

操作 结果 ctrl+q 打开查询窗口 ctrl+/ 注释sql语句 ctrl+shift +/ 解除注释 ctrl+r 运行查询窗口的sql语句 ctrl+shift+r 只运行选中的sql语句 F6 打开一个mysql命令行窗口 ctrl+l 删除一行 ...

低至一折起
今天
9
0
Set 和 Map

Set 1:基本概念 类数组对象, 内部元素唯一 let set = new Set([1, 2, 3, 2, 1]); console.log(set); // Set(3){ 1, 2, 3 } [...set]; // [1, 2, 3] 接收数组或迭代器对象 ...

凌兮洛
今天
4
0
PyTorch入门笔记一

张量 引入pytorch,生成一个随机的5x3张量 >>> from __future__ import print_function>>> import torch>>> x = torch.rand(5, 3)>>> print(x)tensor([[0.5555, 0.7301, 0.5655],......

仪山湖
今天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部