JavaScript 教程(二)

<div style="background-color:red; border:1px solid black;" />

style不僅可以使用字符串讀寫,它本身還是一個對象,部署了 CSSStyleDeclaration 接口(詳見下面的介紹),可以直接讀寫個別屬性

e.style.fontSize = '18px';
e.style.color = 'black';

CSSStyleDeclaration 接口

簡介

CSSStyleDeclaration 接口用來操作元素的樣式,三個地方部署了這個接口:

1.元素節點的style屬性(Element.style)

2.CSSStyle實例的style屬性

3.window.getComputedStyle()的返回值

CSSStyleDeclaration 接口可以直接讀寫 CSS 的樣式屬性,不過,連詞號需要變成駱駝拼寫法

var divStyle = document.querySelector('div').style;
divStyle.backgroundColor = 'red';
divStyle.border = '1px solid black';
divStyle.width = '100px';
divStyle.height = '100px';
divStyle.fontSize = '10em';
divStyle.backgroundColor // red
divStyle.border // 1px solid black
divStyle.height // 100px
divStyle.width // 100px

上面代碼中,style屬性的值是一個 CSSStyleDeclaration 實例;這個對象所包含的屬性與 CSS 規則一一對應,但是名字需要改寫,比如background-color寫成backgroundColor。改寫的規則是將橫槓從 CSS 屬性名中去除,然後將橫槓後的第一個字母大寫;如果 CSS 屬性名是 JavaScript 保留字,則規則名之前需要加上字符串css,比如float寫成cssFloat

注意,該對象的屬性值都是字符串,設置時必須包括單位,但是不含規則結尾的分號;比如,divStyle.width不能寫爲100,而要寫爲100px;另外,Element.style返回的只是行內樣式,並不是該元素的全部樣式;通過樣式表設置的樣式,或者從父元素繼承的樣式,無法通過這個屬性得到;元素的全部樣式要通過window.getComputedStyle()得到

CSSStyleDeclaration 實例屬性

CSSStyleDeclaration.cssText

CSSStyleDeclaration.cssText屬性用來讀寫當前規則的所有樣式聲明文本

var divStyle = document.querySelector('div').style;
divStyle.cssText = 'background-color: red;' + 'border: 1px solid black;' + 'height: 100px;' + 'width: 100px;';

注意,cssText的屬性值不用改寫 CSS 屬性名;刪除一個元素的所有行內樣式,最簡便的方法就是設置cssText爲空字符串

divStyle.cssText = '';
CSSStyleDeclaration.length

CSSStyleDeclaration.length屬性返回一個整數值,表示當前規則包含多少條樣式聲明

<div id="myDiv" style="height: 1px;width: 100%;background-color: #CA1;" ></div>
var myDiv = document.getElementById('myDiv');
var divStyle = myDiv.style;
divStyle.length // 3

上面代碼中,myDiv元素的行內樣式共包含3條樣式規則

CSSStyleDeclaration.parentRule

CSSStyleDeclaration.parentRule屬性返回當前規則所屬的那個樣式塊(CSSRule 實例)。如果不存在所屬的樣式塊,該屬性返回null;該屬性只讀,且只在使用 CSSRule 接口時有意義

var declaration = document.styleSheets[0].rules[0].style;
declaration.parentRule === document.styleSheets[0].rules[0] // true

CSSStyleDeclaration 實例方法

CSSStyleDeclaration.getPropertyPriority()

CSSStyleDeclaration.getPropertyPriority方法接受 CSS 樣式的屬性名作爲參數,返回一個字符串,表示有沒有設置important優先級。如果有就返回important,否則返回空字符串

<div id="myDiv" style="margin: 10px!important; color: red;"/>
var style = document.getElementById('myDiv').style;
style.margin // "10px"
style.getPropertyPriority('margin') // "important"
style.getPropertyPriority('color') // ""

上面代碼中,margin屬性有important優先級,color屬性沒有

CSSStyleDeclaration.getPropertyValue()

CSSStyleDeclaration.getPropertyValue方法接受 CSS 樣式屬性名作爲參數,返回一個字符串,表示該屬性的屬性值

<div id="myDiv" style="margin: 10px!important; color: red;"/>
var style = document.getElementById('myDiv').style;
style.margin // "10px"
style.getPropertyValue("margin") // "10px"
CSSStyleDeclaration.item()

CSSStyleDeclaration.item方法接受一個整數值作爲參數,返回該位置的 CSS 屬性名

<div id="myDiv" style="color: red; background-color: white;"/>
var style = document.getElementById('myDiv').style;
style.item(0) // "color"
style.item(1) // "background-color"

上面代碼中,0號位置的 CSS 屬性名是color,1號位置的 CSS 屬性名是background-color;如果沒有提供參數,這個方法會報錯;如果參數值超過實際的屬性數目,這個方法返回一個空字符值

CSSStyleDeclaration.removeProperty()

CSSStyleDeclaration.removeProperty方法接受一個屬性名作爲參數,在 CSS 規則裏面移除這個屬性,返回這個屬性原來的值

<div id="myDiv" style="color: red; background-color: white;"> 111 </div>
var style = document.getElementById('myDiv').style;
style.removeProperty('color') // 'red'
// HTML 代碼變爲 <div id="myDiv" style="background-color: white;">

上面代碼中,刪除color屬性以後,字體顏色從紅色變成默認顏色

CSSStyleDeclaration.setProperty()

CSSStyleDeclaration.setProperty方法用來設置新的 CSS 屬性。該方法沒有返回值;該方法可以接受三個參數:

第一個參數:屬性名,該參數是必需的

第二個參數:屬性值,該參數可選。如果省略,則參數值默認爲空字符串

第三個參數:優先級,該參數可選。如果設置,唯一的合法值是important,表示 CSS 規則裏面的!important

<div id="myDiv" style="color: red; background-color: white;"> 111 </div>
var style = document.getElementById('myDiv').style;
style.setProperty('border', '1px solid blue');

上面代碼執行後,myDiv元素就會出現藍色的邊框

CSS 模塊的偵測

CSS 的規格發展太快,新的模塊層出不窮;不同瀏覽器的不同版本,對 CSS 模塊的支持情況都不一樣;有時候需要知道當前瀏覽器是否支持某個模塊,這就叫做“CSS模塊的偵測”;一個比較普遍適用的方法是,判斷元素的style對象的某個屬性值是否爲字符串

typeof element.style.animationName === 'string';
typeof element.style.transform === 'string';

如果該 CSS 屬性確實存在,會返回一個字符串。即使該屬性實際上並未設置,也會返回一個空字符串。如果該屬性不存在,則會返回undefined

document.body.style['maxWidth'] // ""
document.body.style['maximumWidth'] // undefined

上面代碼說明,這個瀏覽器支持max-width屬性,但是不支持maximum-width屬性;注意,不管 CSS 屬性名的寫法帶不帶連詞線,style屬性上都能反映出該屬性是否存在

document.body.style['backgroundColor'] // ""
document.body.style['background-color'] // ""

另外,使用的時候,需要把不同瀏覽器的 CSS 前綴也考慮進去

var content = document.getElementById('content');
typeof content.style['webkitAnimation'] === 'string'

這種偵測方法可以寫成一個函數

function isPropertySupported(property) {
  if (property in document.body.style) return true;
  var prefixes = ['Moz', 'Webkit', 'O', 'ms', 'Khtml'];
  var prefProperty = property.charAt(0).toUpperCase() + property.substr(1);
  for(var i = 0; i < prefixes.length; i++){ if((prefixes[i] + prefProperty) in document.body.style) return true; }
  return false;
}
isPropertySupported('background-clip') // true

CSS 對象

瀏覽器原生提供 CSS 對象,爲 JavaScript 操作 CSS 提供一些工具方法;這個對象目前有兩個靜態方法

CSS.escape()

CSS.escape方法用於轉義 CSS 選擇器裏面的特殊字符

<div id="foo#bar">

上面代碼中,該元素的id屬性包含一個#號,該字符在 CSS 選擇器裏面有特殊含義;不能直接寫成document.querySelector('#foo#bar'),只能寫成document.querySelector('#foo\#bar');這裏必須使用雙斜槓的原因是,單引號字符串本身會轉義一次斜槓,CSS.escape方法就用來轉義那些特殊字符

document.querySelector('#' + CSS.escape('foo#bar'))

CSS.supports()

CSS.supports方法返回一個布爾值,表示當前環境是否支持某一句 CSS 規則;它的參數有兩種寫法,一種是第一個參數是屬性名,第二個參數是屬性值;另一種是整個參數就是一行完整的 CSS 語句

// 第一種寫法
CSS.supports('transform-origin', '5px') // true
// 第二種寫法
CSS.supports('display: table-cell') // true

注意,第二種寫法的參數結尾不能帶有分號,否則結果不準確

CSS.supports('display: table-cell;') // false

window.getComputedStyle()

行內樣式(inline style)具有最高的優先級,改變行內樣式,通常會立即反映出來;但是,網頁元素最終的樣式是綜合各種規則計算出來的;因此,如果想得到元素實際的樣式,只讀取行內樣式是不夠的,需要得到瀏覽器最終計算出來的樣式規則。window.getComputedStyle方法,就用來返回瀏覽器計算後得到的最終規則;它接受一個節點對象作爲參數,返回一個 CSSStyleDeclaration 實例,包含了指定節點的最終樣式信息。所謂“最終樣式信息”,指的是各種 CSS 規則疊加後的結果

var div = document.querySelector('div');
var styleObj = window.getComputedStyle(div);
styleObj.backgroundColor

上面代碼中,得到的背景色就是div元素真正的背景色;注意,CSSStyleDeclaration 實例是一個活的對象,任何對於樣式的修改,會實時反映到這個實例上面;另外,這個實例是隻讀的。getComputedStyle方法還可以接受第二個參數,表示當前元素的僞元素(比如:before、:after、:first-line、:first-letter等)

var result = window.getComputedStyle(div, ':before');

下面的例子是如何獲取元素的高度

var elem = document.getElementById('elem-container');
var styleObj = window.getComputedStyle(elem, null)
var height = styleObj.height;
// 等同於
var height = styleObj['height'];
var height = styleObj.getPropertyValue('height');

上面代碼得到的height屬性,是瀏覽器最終渲染出來的高度,比其他方法得到的高度更可靠;由於styleObj是 CSSStyleDeclaration 實例,所以可以使用各種 CSSStyleDeclaration 的實例屬性和方法;有幾點需要注意:

1.CSSStyleDeclaration 實例返回的 CSS 值都是絕對單位。比如,長度都是像素單位(返回值包括px後綴),顏色是rgb(#, #, #)或rgba(#, #, #, #)格式

2.CSS 規則的簡寫形式無效。比如,想讀取margin屬性的值,不能直接讀,只能讀marginLeft、marginTop等屬性;再比如,font屬性也是不能直接讀的,只能讀font-size等單個屬性

3.如果讀取 CSS 原始的屬性名,要用方括號運算符,比如styleObj['z-index'];如果讀取駱駝拼寫法的 CSS 屬性名,可以直接讀取styleObj.zIndex

4.該方法返回的 CSSStyleDeclaration 實例的cssText屬性無效,返回undefined

CSS 僞元素

CSS 僞元素是通過 CSS 向 DOM 添加的元素,主要是通過:before和:after選擇器生成,然後用content屬性指定僞元素的內容

<div id="test">Test content</div>

CSS 添加僞元素:before的寫法如下

#test:before { content: 'Before '; color: #FF0; }

節點元素的style對象無法讀寫僞元素的樣式,這時就要用到window.getComputedStyle()。JavaScript 獲取僞元素,可以使用下面的方法

var test = document.querySelector('#test');
var result = window.getComputedStyle(test, ':before').content;
var color = window.getComputedStyle(test, ':before').color;

此外,也可以使用 CSSStyleDeclaration 實例的getPropertyValue方法,獲取僞元素的屬性

var result = window.getComputedStyle(test, ':before').getPropertyValue('content');
var color = window.getComputedStyle(test, ':before').getPropertyValue('color');

StyleSheet 接口

概述

StyleSheet接口代表網頁的一張樣式表,包括元素加載的樣式表和

var sheets = document.styleSheets;
var sheet = document.styleSheets[0];
sheet instanceof StyleSheet // true

如果是

<style id="myStyle"></style>
var myStyleSheet = document.getElementById('myStyle').sheet;
myStyleSheet instanceof StyleSheet // true

嚴格地說,StyleSheet接口不僅包括網頁樣式表,還包括 XML 文檔的樣式表。所以,它有一個子類CSSStyleSheet表示網頁的 CSS 樣式表;我們在網頁裏面拿到的樣式表實例,實際上是CSSStyleSheet的實例。這個子接口繼承了StyleSheet的所有屬性和方法,並且定義了幾個自己的屬性,下面把這兩個接口放在一起介紹

實例屬性

StyleSheet實例有以下屬性

StyleSheet.disabled

StyleSheet.disabled返回一個布爾值,表示該樣式表是否處於禁用狀態。手動設置disabled屬性爲true,等同於在元素裏面,將這張樣式表設爲alternate stylesheet,即該樣式表將不會生效;注意,disabled屬性只能在 JavaScript 腳本中設置,不能在 HTML 語句中設置

Stylesheet.href

Stylesheet.href返回樣式表的網址。對於內嵌樣式表,該屬性返回null。該屬性只讀

document.styleSheets[0].href
StyleSheet.media

StyleSheet.media屬性返回一個類似數組的對象(MediaList實例),成員是表示適用媒介的字符串;表示當前樣式表是用於屏幕(screen),還是用於打印(print)或手持設備(handheld),或各種媒介都適用(all)。該屬性只讀,默認值是screen

document.styleSheets[0].media.mediaText // "all"

MediaList實例的appendMedium方法,用於增加媒介;deleteMedium方法用於刪除媒介

document.styleSheets[0].media.appendMedium('handheld');
document.styleSheets[0].media.deleteMedium('print');
StyleSheet.title

StyleSheet.title屬性返回樣式表的title屬性

StyleSheet.type

StyleSheet.type屬性返回樣式表的type屬性,通常是text/css

document.styleSheets[0].type  // "text/css"
StyleSheet.parentStyleSheet

CSS 的@import命令允許在樣式表中加載其他樣式表;StyleSheet.parentStyleSheet屬性返回包含了當前樣式表的那張樣式表;如果當前樣式表是頂層樣式表,則該屬性返回null

if (stylesheet.parentStyleSheet) {
  sheet = stylesheet.parentStyleSheet;
} else {
  sheet = stylesheet;
}
StyleSheet.ownerNode

StyleSheet.ownerNode屬性返回StyleSheet對象所在的 DOM 節點,通常是或

<link rel="StyleSheet" href="example.css" type="text/css" />
document.styleSheets[0].ownerNode // [object HTMLLinkElement]
CSSStyleSheet.cssRules

CSSStyleSheet.cssRules屬性指向一個類似數組的對象(CSSRuleList實例),裏面每一個成員就是當前樣式表的一條 CSS 規則。使用該規則的cssText屬性,可以得到 CSS 規則對應的字符串

var sheet = document.querySelector('#styleElement').sheet;
sheet.cssRules[0].cssText // "body { background-color: red; margin: 20px; }"
sheet.cssRules[1].cssText // "p { line-height: 1.4em; color: blue; }"

每條 CSS 規則還有一個style屬性,指向一個對象,用來讀寫具體的 CSS 命令

cssStyleSheet.cssRules[0].style.color = 'red';
cssStyleSheet.cssRules[1].style.color = 'purple';
CSSStyleSheet.ownerRule

有些樣式表是通過@import規則輸入的,它的ownerRule屬性會返回一個CSSRule實例,代表那行@import規則。如果當前樣式表不是通過@import引入的,ownerRule屬性返回null

實例方法

CSSStyleSheet.insertRule()

CSSStyleSheet.insertRule方法用於在當前樣式表的插入一個新的 CSS 規則

var sheet = document.querySelector('#styleElement').sheet;
sheet.insertRule('#block { color: white }', 0);
sheet.insertRule('p { color: red }', 1);

該方法可以接受兩個參數,第一個參數是表示 CSS 規則的字符串,這裏只能有一條規則,否則會報錯。第二個參數是該規則在樣式表的插入位置(從0開始),該參數可選,默認爲0(即默認插在樣式表的頭部)。注意,如果插入位置大於現有規則的數目,會報錯;該方法的返回值是新插入規則的位置序號

注意,瀏覽器對腳本在樣式表裏面插入規則有很多限制;所以,這個方法最好放在try...catch裏使用

CSSStyleSheet.deleteRule()

CSSStyleSheet.deleteRule方法用來在樣式表裏面移除一條規則,它的參數是該條規則在cssRules對象中的位置。該方法沒有返回值

document.styleSheets[0].deleteRule(1);

實例:添加樣式表

網頁添加樣式表有兩種方式。一種是添加一張內置樣式表,即在文檔中添加一個

// 寫法一
var style = document.createElement('style');
style.setAttribute('media', 'screen');
style.innerHTML = 'body{color:red}';
document.head.appendChild(style);
// 寫法二
var style = (function () {
  var style = document.createElement('style');
  document.head.appendChild(style);
  return style;
})();
style.sheet.insertRule('.foo{color:red;}', 0);

另一種是添加外部樣式表,即在文檔中添加一個節點,然後將href屬性指向外部樣式表的 URL

var linkElm = document.createElement('link');
linkElm.setAttribute('rel', 'stylesheet');
linkElm.setAttribute('type', 'text/css');
linkElm.setAttribute('href', 'reset-min.css');
document.head.appendChild(linkElm);

CSSRuleList 接口

CSSRuleList 接口是一個類似數組的對象,表示一組 CSS 規則,成員都是 CSSRule 實例;獲取 CSSRuleList 實例,一般是通過StyleSheet.cssRules屬性

<style id="myStyle">
  h1 { color: red; }
  p { color: blue; }
</style>
var myStyleSheet = document.getElementById('myStyle').sheet;
var crl = myStyleSheet.cssRules;
crl instanceof CSSRuleList // true

CSSRuleList 實例裏面,每一條規則(CSSRule 實例)可以通過rules.item(index)或者rules[index]拿到。CSS 規則的條數通過rules.length拿到。還是用上面的例子

crl[0] instanceof CSSRule // true
crl.length // 2

注意,添加規則和刪除規則不能在 CSSRuleList 實例操作,而要在它的父元素 StyleSheet 實例上,通過StyleSheet.insertRule()和StyleSheet.deleteRule()操作

CSSRule 接口

概述

一條 CSS 規則包括兩個部分:CSS 選擇器和樣式聲明。下面就是一條典型的 CSS 規則

.myClass {
  color: red;
  background-color: yellow;
}

JavaScript 通過 CSSRule 接口操作 CSS 規則。一般通過 CSSRuleList 接口(StyleSheet.cssRules)獲取 CSSRule 實例

<style id="myStyle">
.myClass { color: red; background-color: yellow; }
</style>
var myStyleSheet = document.getElementById('myStyle').sheet;
var ruleList = myStyleSheet.cssRules;
var rule = ruleList[0];
rule instanceof CSSRule // true

CSSRule 實例的屬性

CSSRule.cssText

CSSRule.cssText屬性返回當前規則的文本,還是使用上面的例子

rule.cssText // ".myClass { color: red; background-color: yellow; }"

如果規則是加載(@import)其他樣式表,cssText屬性返回@import 'url'

CSSRule.parentStyleSheet

CSSRule.parentStyleSheet屬性返回當前規則所在的樣式表對象(StyleSheet 實例),還是使用上面的例子

rule.parentStyleSheet === myStyleSheet // true
CSSRule.parentRule

CSSRule.parentRule屬性返回包含當前規則的父規則,如果不存在父規則(即當前規則是頂層規則),則返回null;父規則最常見的情況是,當前規則包含在@media規則代碼塊之中

<style id="myStyle">
@supports (display: flex) {
  @media screen and (min-width: 900px) {
    article { display: flex; }
  }
}
</style>
var myStyleSheet = document.getElementById('myStyle').sheet;
var ruleList = myStyleSheet.cssRules;
var rule0 = ruleList[0];
rule0.cssText
// "@supports (display: flex) {
//    @media screen and (min-width: 900px) {
//      article { display: flex; }
//    }
// }"
// 由於這條規則內嵌其他規則,所以它有 cssRules 屬性,且該屬性是 CSSRuleList 實例
rule0.cssRules instanceof CSSRuleList // true
var rule1 = rule0.cssRules[0];
rule1.cssText
// "@media screen and (min-width: 900px) {
//   article { display: flex; }
// }"
var rule2 = rule1.cssRules[0];
rule2.cssText
// "article { display: flex; }"
rule1.parentRule === rule0 // true
rule2.parentRule === rule1 // true
CSSRule.type

CSSRule.type屬性返回一個整數值,表示當前規則的類型;最常見的類型有以下幾種:

1:普通樣式規則(CSSStyleRule 實例)

3:@import規則

4:@media規則(CSSMediaRule 實例)

5:@font-face規則

CSSStyleRule 接口

如果一條 CSS 規則是普通的樣式規則(不含特殊的 CSS 命令),那麼除了 CSSRule 接口,它還部署了 CSSStyleRule 接口;CSSStyleRule 接口有以下兩個屬性

CSSStyleRule.selectorText

CSSStyleRule.selectorText屬性返回當前規則的選擇器;這個屬性是可寫的

var stylesheet = document.styleSheets[0];
stylesheet.cssRules[0].selectorText // ".myClass"
CSSStyleRule.style

CSSStyleRule.style屬性返回一個對象(CSSStyleDeclaration 實例),代表當前規則的樣式聲明,也就是選擇器後面的大括號裏面的部分

<style id="myStyle">
  p { color: red; }
</style>
var styleSheet = document.getElementById('myStyle').sheet;
styleSheet.cssRules[0].style instanceof CSSStyleDeclaration // true

CSSStyleDeclaration 實例的cssText屬性,可以返回所有樣式聲明,格式爲字符串

styleSheet.cssRules[0].style.cssText // "color: red;"
styleSheet.cssRules[0].selectorText // "p"

CSSMediaRule 接口

如果一條 CSS 規則是@media代碼塊,那麼它除了 CSSRule 接口,還部署了 CSSMediaRule 接口;該接口主要提供media屬性和conditionText屬性;前者返回代表@media規則的一個對象(MediaList 實例),後者返回@media規則的生效條件

<style id="myStyle">
@media screen and (min-width: 900px) {
  article { display: flex; }
}
</style>
var styleSheet = document.getElementById('myStyle').sheet;
styleSheet.cssRules[0] instanceof CSSMediaRule // true

styleSheet.cssRules[0].media{
  0: "screen and (min-width: 900px)",
  appendMedium: function,
  deleteMedium: function,
  item: function,
  length: 1,
  mediaText: "screen and (min-width: 900px)"
}
styleSheet.cssRules[0].conditionText // "screen and (min-width: 900px)"

window.matchMedia()

基本用法

window.matchMedia方法用來將 CSS 的MediaQuery條件語句,轉換成一個 MediaQueryList 實例

var mdl = window.matchMedia('(min-width: 400px)');
mdl instanceof MediaQueryList // true

注意,如果參數不是有效的MediaQuery條件語句,window.matchMedia不會報錯,依然返回一個 MediaQueryList 實例

window.matchMedia('bad string') instanceof MediaQueryList // true

MediaQueryList 接口的實例屬性

MediaQueryList 實例有三個屬性

MediaQueryList.media

MediaQueryList.media屬性返回一個字符串,表示對應的 MediaQuery 條件語句

var mql = window.matchMedia('(min-width: 400px)');
mql.media // "(min-width: 400px)"
MediaQueryList.matches

MediaQueryList.matches屬性返回一個布爾值,表示當前頁面是否符合指定的 MediaQuery 條件語句

if (window.matchMedia('(min-width: 400px)').matches) {
  /* 當前視口不小於 400 像素 */
} else {
  /* 當前視口小於 400 像素 */
}

下面的例子根據mediaQuery是否匹配當前環境,加載相應的 CSS 樣式表

var result = window.matchMedia("(max-width: 700px)");
if (result.matches){
  var linkElm = document.createElement('link');
  linkElm.setAttribute('rel', 'stylesheet');
  linkElm.setAttribute('type', 'text/css');
  linkElm.setAttribute('href', 'small.css');
  document.head.appendChild(linkElm);
}
MediaQueryList.onchange

如果 MediaQuery 條件語句的適配環境發生變化,會觸發change事件。MediaQueryList.onchange屬性用來指定change事件的監聽函數。該函數的參數是change事件對象(MediaQueryListEvent 實例),該對象與 MediaQueryList 實例類似,也有media和matches屬性

var mql = window.matchMedia('(max-width: 600px)');
mql.onchange = function(e) {
  if (e.matches) {
    /* 視口不超過 600 像素 */
  } else {
    /* 視口超過 600 像素 */
  }
}

上面代碼中,change事件發生後,存在兩種可能。一種是顯示寬度從700像素以上變爲以下,另一種是從700像素以下變爲以上,所以在監聽函數內部要判斷一下當前是哪一種情況

MediaQueryList 接口的實例方法

MediaQueryList 實例有兩個方法MediaQueryList.addListener()和MediaQueryList.removeListener(),用來爲change事件添加或撤銷監聽函數

var mql = window.matchMedia('(max-width: 600px)');
mql.addListener(mqCallback); // 指定監聽函數
mql.removeListener(mqCallback); // 撤銷監聽函數
function mqCallback(e) {
  if (e.matches) {
    /* 視口不超過 600 像素 */
  } else {
    /* 視口超過 600 像素 */
  }
}

Mutation Observer API

概述

Mutation Observer API 用來監視 DOM 變動;DOM 的任何變動,比如節點的增減、屬性的變動、文本內容的變動,這個 API 都可以得到通知。概念上,它很接近事件,可以理解爲 DOM 發生變動就會觸發 Mutation Observer 事件;但是,它與事件有一個本質不同:事件是同步觸發,也就是說,DOM 的變動立刻會觸發相應的事件;Mutation Observer 則是異步觸發,DOM 的變動並不會馬上觸發,而是要等到當前所有 DOM 操作都結束才觸發;這樣設計是爲了應付 DOM 變動頻繁的特點。舉例來說,如果文檔中連續插入1000個

元素,就會連續觸發1000個插入事件,執行每個事件的回調函數,這很可能造成瀏覽器的卡頓;而 Mutation Observer 完全不同,只在1000個段落都插入結束後纔會觸發,而且只觸發一次。

Mutation Observer 有以下特點。

1.它等待所有腳本任務完成後,纔會運行(即異步觸發方式)。

2.它把 DOM 變動記錄封裝成一個數組進行處理,而不是一條條個別處理 DOM 變動。

3.它既可以觀察 DOM 的所有類型變動,也可以指定只觀察某一類變動

MutationObserver 構造函數

使用時,首先使用MutationObserver構造函數,新建一個觀察器實例,同時指定這個實例的回調函數

var observer = new MutationObserver(callback);

上面代碼中的回調函數,會在每次 DOM 變動後調用。該回調函數接受兩個參數,第一個是變動數組,第二個是觀察器實例,下面是一個例子

var observer = new MutationObserver(function (mutations, observer) {
  mutations.forEach(function(mutation) { console.log(mutation); });
});

MutationObserver 的實例方法

observe()

observe方法用來啓動監聽,它接受兩個參數:

第一個參數:所要觀察的 DOM 節點

第二個參數:一個配置對象,指定所要觀察的特定變動

var article = document.querySelector('article');
var  options = { 'childList': true, 'attributes':true };
observer.observe(article, options);

上面代碼中,observe方法接受兩個參數,第一個是所要觀察的DOM元素是article,第二個是所要觀察的變動類型(子節點變動和屬性變動)。

觀察器所能觀察的 DOM 變動類型(即上面代碼的options對象),有以下幾種:

childList:子節點的變動(指新增,刪除或者更改)

attributes:屬性的變動

characterData:節點內容或節點文本的變動

想要觀察哪一種變動類型,就在option對象中指定它的值爲true。需要注意的是,必須同時指定childList、attributes和characterData中的一種或多種,若未均指定將報錯。

除了變動類型,options對象還可以設定以下屬性:

1.subtree:布爾值,表示是否將該觀察器應用於該節點的所有後代節點。

2.attributeOldValue:布爾值,表示觀察attributes變動時,是否需要記錄變動前的屬性值。

3.characterDataOldValue:布爾值,表示觀察characterData變動時,是否需要記錄變動前的值。

4.attributeFilter:數組,表示需要觀察的特定屬性(比如['class','src'])

mutationObserver.observe(document.documentElement, { // 開始監聽文檔根節點(即<html>標籤)的變動
  attributes: true, characterData: true, childList: true, subtree: true, attributeOldValue: true, characterDataOldValue: true
});

對一個節點添加觀察器,就像使用addEventListener方法一樣,多次添加同一個觀察器是無效的,回調函數依然只會觸發一次。但是,如果指定不同的options對象,就會被當作兩個不同的觀察器。

下面的例子是觀察新增的子節點

var insertedNodes = [];
var observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    for (var i = 0; i < mutation.addedNodes.length; i++) { insertedNodes.push(mutation.addedNodes[i]); }
  });
  console.log(insertedNodes);
});
observer.observe(document, { childList: true, subtree: true });

disconnect(),takeRecords()

disconnect方法用來停止觀察。調用該方法後,DOM 再發生變動,也不會觸發觀察器

observer.disconnect();

takeRecords方法用來清除變動記錄,即不再處理未處理的變動。該方法返回變動記錄的數組

observer.takeRecords();

下面是一個例子

var changes = mutationObserver.takeRecords();// 保存所有沒有被觀察器處理的變動
mutationObserver.disconnect();// 停止觀察

MutationRecord 對象

DOM 每次發生變化,就會生成一條變動記錄(MutationRecord 實例);該實例包含了與變動相關的所有信息。Mutation Observer 處理的就是一個個MutationRecord實例所組成的數組;MutationRecord對象包含了DOM的相關信息,有如下屬性:

type:觀察的變動類型(attributes、characterData或者childList)

target:發生變動的DOM節點

addedNodes:新增的DOM節點

removedNodes:刪除的DOM節點

previousSibling:前一個同級節點,如果沒有則返回null

nextSibling:下一個同級節點,如果沒有則返回null

attributeName:發生變動的屬性。如果設置了attributeFilter,則只返回預先指定的屬性

oldValue:變動前的值。這個屬性只對attribute和characterData變動有效,如果發生childList變動,則返回null

應用示例

子元素的變動

下面的例子說明如何讀取變動記錄

var callback = function (records){
  records.map(function(record){
    console.log('Mutation type: ' + record.type);
    console.log('Mutation target: ' + record.target);
  });
};
var mo = new MutationObserver(callback);
var option = { 'childList': true, 'subtree': true };
mo.observe(document.body, option);

上面代碼的觀察器,觀察

的所有下級節點(childList表示觀察子節點,subtree表示觀察後代節點)的變動。回調函數會在控制檯顯示所有變動的類型和目標節點

屬性的變動

下面的例子說明如何追蹤屬性的變動

var callback = function (records) {
  records.map(function (record) { console.log('Previous attribute value: ' + record.oldValue); });
};
var mo = new MutationObserver(callback);
var element = document.getElementById('#my_element');
var options = { 'attributes': true, 'attributeOldValue': true }
mo.observe(element, options);

上面代碼先設定追蹤屬性變動('attributes': true),然後設定記錄變動前的值。實際發生變動時,會將變動前的值顯示在控制檯

取代 DOMContentLoaded 事件

網頁加載的時候,DOM 節點的生成會產生變動記錄,因此只要觀察 DOM 的變動,就能在第一時間觸發相關事件,因此也就沒有必要使用DOMContentLoaded事件

var observer = new MutationObserver(callback);
observer.observe(document.documentElement, { childList: true, subtree: true });

上面代碼中,監聽document.documentElement(即HTML節點)的子節點的變動,subtree屬性指定監聽還包括後代節點。因此,任意一個網頁元素一旦生成,就能立刻被監聽到。下面的代碼,使用MutationObserver對象封裝一個監聽 DOM 生成的函數

(function(win){
  'use strict';
  var listeners = [];
  var doc = win.document;
  var MutationObserver = win.MutationObserver || win.WebKitMutationObserver;
  var observer;
  function ready(selector, fn){    
    listeners.push({ selector: selector, fn: fn }); // 儲存選擇器和回調函數
    if(!observer){
      // 監聽document變化
      observer = new MutationObserver(check);
      observer.observe(doc.documentElement, { childList: true, subtree: true });
    }    
    check(); // 檢查該節點是否已經在DOM中
  }
  function check(){   
    for(var i = 0; i < listeners.length; i++){ // 檢查是否匹配已儲存的節點
      var listener = listeners[i];      
      var elements = doc.querySelectorAll(listener.selector); // 檢查指定節點是否有匹配
      for(var j = 0; j < elements.length; j++){
        var element = elements[j];        
        if(!element.ready){ // 確保回調函數只會對該元素調用一次
          element.ready = true;          
          listener.fn.call(element, element); // 對該節點調用回調函數
        }
      }
    }
  }  
  win.ready = ready; // 對外暴露ready
})(this);
ready('.foo', function(element){
  // ...
});

事件

EventTarget 接口

事件的本質是程序各個組成部分之間的一種通信方式,也是異步編程的一種實現。DOM 支持大量的事件,這裏開始介紹 DOM 的事件編程

概述

DOM 的事件操作(監聽和觸發),都定義在EventTarget接口;所有節點對象都部署了這個接口,其他一些需要事件通信的瀏覽器內置對象(比如,XMLHttpRequest、AudioNode、AudioContext)也部署了這個接口。該接口主要提供三個實例方法:

addEventListener:綁定事件的監聽函數

removeEventListener:移除事件的監聽函數

dispatchEvent:觸發事件

EventTarget.addEventListener()

EventTarget.addEventListener()用於在當前節點或對象上,定義一個特定事件的監聽函數。一旦這個事件發生,就會執行監聽函數。該方法沒有返回值

target.addEventListener(type, listener[, useCapture]);

該方法接受三個參數:

1.type:事件名稱,大小寫敏感

2.listener:監聽函數。事件發生時,會調用該監聽函數

3.useCapture:布爾值,表示監聽函數是否在捕獲階段(capture)觸發,默認爲false(監聽函數只在冒泡階段被觸發)。該參數可選

function hello() { console.log('Hello world'); }
var button = document.getElementById('btn');
button.addEventListener('click', hello, false);

上面代碼中,button節點的addEventListener方法綁定click事件的監聽函數hello,該函數只在冒泡階段觸發;關於參數,有兩個地方需要注意:

1.第二個參數除了監聽函數,還可以是一個具有handleEvent方法的對象

buttonElement.addEventListener('click', {
  handleEvent: function (event) { console.log('click'); } //addEventListener方法的第二個參數,就是一個具有handleEvent方法的對象
});

2.第三個參數除了布爾值useCapture,還可以是一個屬性配置對象。該對象有以下屬性:

(1)capture:布爾值,表示該事件是否在捕獲階段觸發監聽函數

(2)once:布爾值,表示監聽函數是否只觸發一次,然後就自動移除

(3)passive:布爾值,表示監聽函數不會調用事件的preventDefault方法。如果監聽函數調用了,瀏覽器將忽略這個要求,並在監控臺輸出一行警告

element.addEventListener('click', function (event) {
  // 只執行一次的代碼
}, {once: true});

addEventListener方法可以爲針對當前對象的同一個事件,添加多個不同的監聽函數;這些函數按照添加順序觸發,即先添加先觸發;如果爲同一個事件多次添加同一個監聽函數,該函數只會執行一次,多餘的添加將自動被去除(不必使用removeEventListener方法手動去除)

如果希望向監聽函數傳遞參數,可以用匿名函數包裝一下監聽函數

function print(x) { console.log(x); }
var el = document.getElementById('div1');
el.addEventListener('click', function () { print('Hello'); }, false);

上面代碼通過匿名函數,向監聽函數print傳遞了一個參數。監聽函數內部的this,指向當前事件所在的那個對象

<p id="para">Hello</p>
var para = document.getElementById('para');
para.addEventListener('click', function (e) {
  console.log(this.nodeName); // "P"
}, false);

上面代碼中,監聽函數內部的this指向事件所在的對象para

EventTarget.removeEventListener()

EventTarget.removeEventListener方法用來移除addEventListener方法添加的事件監聽函數。該方法沒有返回值

div.addEventListener('click', listener, false);
div.removeEventListener('click', listener, false);

removeEventListener方法的參數,與addEventListener方法完全一致。它的第一個參數“事件類型”,大小寫敏感;注意,removeEventListener方法移除的監聽函數,必須是addEventListener方法添加的那個監聽函數,而且必須在同一個元素節點,否則無效

div.addEventListener('click', function (e) {}, false);
div.removeEventListener('click', function (e) {}, false); //removeEventListener方法無效,因爲監聽函數不是同一個匿名函數
或
element.addEventListener('mousedown', handleMouseDown, true);
element.removeEventListener("mousedown", handleMouseDown, false); //removeEventListener方法也是無效的,因爲第三個參數不一樣

EventTarget.dispatchEvent()

EventTarget.dispatchEvent方法在當前節點上觸發指定事件,從而觸發監聽函數的執行。該方法返回一個布爾值,只要有一個監聽函數調用了Event.preventDefault(),則返回值爲false,否則爲true

target.dispatchEvent(event)

dispatchEvent方法的參數是一個Event對象的實例

para.addEventListener('click', hello, false);
var event = new Event('click');
para.dispatchEvent(event); //在當前節點觸發了click事件

如果dispatchEvent方法的參數爲空,或者不是一個有效的事件對象,將報錯;下面代碼根據dispatchEvent方法的返回值,判斷事件是否被取消了

var canceled = !cb.dispatchEvent(event);
if (canceled) {
  console.log('事件取消');
} else {
  console.log('事件未取消');
}

事件模型

監聽函數

瀏覽器的事件模型,就是通過監聽函數(listener)對事件做出反應;事件發生後,瀏覽器監聽到了這個事件,就會執行對應的監聽函數;這是事件驅動編程模式(event-driven)的主要編程方式。JavaScript 有三種方法,可以爲事件綁定監聽函數

HTML 的 on- 屬性

HTML 語言允許在元素的屬性中,直接定義某些事件的監聽代碼

<body onload="doSomething()">
<div onclick="console.log('觸發事件')">

元素的事件監聽屬性,都是on加上事件名,比如onload就是on + load,表示load事件的監聽代碼。注意,這些屬性的值是將會執行的代碼,而不是一個函數;一旦指定的事件發生,on-屬性的值是原樣傳入 JavaScript 引擎執行。因此如果要執行函數,不要忘記加上一對圓括號;使用這個方法指定的監聽代碼,只會在冒泡階段觸發。直接設置on-屬性,與通過元素節點的setAttribute方法設置on-屬性,效果是一樣的

el.setAttribute('onclick', 'doSomething()');
等同於
<Element onclick="doSomething()">

元素節點的事件屬性

元素節點對象的事件屬性,同樣可以指定監聽函數

window.onload = doSomething;
div.onclick = function (event) { console.log('觸發事件'); };

使用這個方法指定的監聽函數,也是只會在冒泡階段觸發。注意,這種方法與 HTML 的on-屬性的差異是,它的值是函數名(doSomething),而不像後者,必須給出完整的監聽代碼(doSomething())

EventTarget.addEventListener()

所有 DOM 節點實例都有addEventListener方法,用來爲該節點定義事件的監聽函數

小結

上面三種方法,第一種“HTML 的 on- 屬性”,違反了 HTML 與 JavaScript 代碼相分離的原則,將兩者寫在一起,不利於代碼分工,因此不推薦使用;第二種“元素節點的事件屬性”的缺點在於,同一個事件只能定義一個監聽函數,也就是說,如果定義兩次onclick屬性,後一次定義會覆蓋前一次。因此,也不推薦使用;第三種EventTarget.addEventListener是推薦的指定監聽函數的方法。它有如下優點:

1.同一個事件可以添加多個監聽函數

2.能夠指定在哪個階段(捕獲階段還是冒泡階段)觸發監聽函數

3.除了 DOM 節點,其他對象(比如window、XMLHttpRequest等)也有這個接口,它等於是整個 JavaScript 統一的監聽函數接口

this 的指向

監聽函數內部的this指向觸發事件的那個元素節點

<button id="btn" onclick="console.log(this.id)">點擊</button> //btn

其他兩種監聽函數的寫法,this的指向也是如此

<button id="btn">點擊</button>
var btn = document.getElementById('btn');
// 寫法一
btn.onclick = function () {
  console.log(this.id);  //btn
};
// 寫法二
btn.addEventListener(
  'click', function (e) { console.log(this.id); }, false  //btn
);

事件的傳播

一個事件發生後,會在子元素和父元素之間傳播(propagation),這種傳播分成三個階段:

第一階段:從window對象傳導到目標節點(上層傳到底層),稱爲“捕獲階段”(capture phase)

第二階段:在目標節點上觸發,稱爲“目標階段”(target phase)

第三階段:從目標節點傳導回window對象(從底層傳回上層),稱爲“冒泡階段”(bubbling phase)

這種三階段的傳播模型,使得同一個事件會在多個節點上觸發

<div> <p>點擊</p> </div>

如果對這兩個節點,都設置click事件的監聽函數(每個節點的捕獲階段和監聽階段,各設置一個監聽函數),共計設置四個監聽函數。然後,對

點擊,click事件會觸發四次

var phases = { 1: 'capture', 2: 'target', 3: 'bubble' };
var div = document.querySelector('div');
var p = document.querySelector('p');
div.addEventListener('click', callback, true);
p.addEventListener('click', callback, true);
div.addEventListener('click', callback, false);
p.addEventListener('click', callback, false);
function callback(event) {
  var tag = event.currentTarget.tagName;
  var phase = phases[event.eventPhase];
  console.log("Tag: '" + tag + "'. EventPhase: '" + phase + "'");
}
// 點擊以後的結果
// Tag: 'DIV'. EventPhase: 'capture'
// Tag: 'P'. EventPhase: 'target'
// Tag: 'P'. EventPhase: 'target'
// Tag: 'DIV'. EventPhase: 'bubble'

上面代碼表示,click事件被觸發了四次:

節點的捕獲階段和冒泡階段各1次,

節點的目標階段觸發了2次。

捕獲階段:事件從

傳播時,觸發

的click事件;目標階段:事件從
到達

時,觸發

的click事件;冒泡階段:事件從

傳回

時,再次觸發
的click事件。其中,

節點有兩個監聽函數(addEventListener方法第三個參數的不同,會導致綁定兩個監聽函數),因此它們都會因爲click事件觸發一次。所以,

會在target階段有兩次輸出

注意,瀏覽器總是假定click事件的目標節點,就是點擊位置嵌套最深的那個節點(本例是

節點裏面的

節點)。所以,

節點的捕獲階段和冒泡階段,都會顯示爲target階段。事件傳播的最上層對象是window,接着依次是document,html(document.documentElement)和body(document.body)。也就是說,上例的事件傳播順序,在捕獲階段依次爲window、document、html、body、div、p,在冒泡階段依次爲p、div、body、html、document、window

事件的代理

由於事件會在冒泡階段向上傳播到父節點,因此可以把子節點的監聽函數定義在父節點上,由父節點的監聽函數統一處理多個子元素的事件。這種方法叫做事件的代理(delegation)

var ul = document.querySelector('ul');
ul.addEventListener('click', function (event) {
  if (event.target.tagName.toLowerCase() === 'li') {
    // some code
  }
});

如果希望事件到某個節點爲止,不再傳播,可以使用事件對象的stopPropagation方法

p.addEventListener('click', function (event) { event.stopPropagation(); }, true); // 事件傳播到 p 元素後,就不再向下傳播了
p.addEventListener('click', function (event) { event.stopPropagation(); }, false); // 事件冒泡到 p 元素後,就不再向上冒泡了

stopPropagation方法只會阻止事件的傳播,不會阻止該事件觸發

節點的其他click事件的監聽函數。也就是說,不是徹底取消click事件

p.addEventListener('click', function (event) {
  event.stopPropagation();
  console.log(1);
});
p.addEventListener('click', function(event) {  
  console.log(2); // 會觸發
});

上面代碼中,p元素綁定了兩個click事件的監聽函數。stopPropagation方法只能阻止這個事件的傳播,不能取消這個事件,因此,第二個監聽函數會觸發。輸出結果會先是1,然後是2。如果想要徹底取消該事件,不再觸發後面所有click的監聽函數,可以使用stopImmediatePropagation方法

p.addEventListener('click', function (event) {
  event.stopImmediatePropagation();
  console.log(1);
});
p.addEventListener('click', function(event) {  
  console.log(2); // 不會被觸發
});

Event 對象

概述

事件發生以後,會產生一個事件對象,作爲參數傳給監聽函數。瀏覽器原生提供一個Event對象,所有的事件都是這個對象的實例,或者說繼承了Event.prototype對象。Event對象本身就是一個構造函數,可以用來生成新的實例

event = new Event(type, options);

Event構造函數接受兩個參數。第一個參數type是字符串,表示事件的名稱;第二個參數options是一個對象,表示事件對象的配置。該對象主要有下面兩個屬性:

1.bubbles:布爾值,可選,默認爲false,表示事件對象是否冒泡

2.cancelable:布爾值,可選,默認爲false,表示事件是否可以被取消,即能否用Event.preventDefault()取消這個事件。一旦事件被取消,就好像從來沒有發生過,不會觸發瀏覽器對該事件的默認行爲

var ev = new Event( 'look', { 'bubbles': true, 'cancelable': false } );
document.dispatchEvent(ev);

上面代碼新建一個look事件實例,然後使用dispatchEvent方法觸發該事件。注意,如果不是顯式指定bubbles屬性爲true,生成的事件就只能在“捕獲階段”觸發監聽函數

<div><p>Hello</p></div>
var div = document.querySelector('div');
var p = document.querySelector('p');
function callback(event) {
  var tag = event.currentTarget.tagName;
  console.log('Tag: ' + tag); // 沒有任何輸出
}
div.addEventListener('click', callback, false);
var click = new Event('click');
p.dispatchEvent(click);

上面代碼中,p元素髮出一個click事件,該事件默認不會冒泡。div.addEventListener方法指定在冒泡階段監聽,因此監聽函數不會觸發。如果寫成div.addEventListener('click', callback, true),那麼在“捕獲階段”可以監聽到這個事件。另一方面,如果這個事件在div元素上觸發:

div.dispatchEvent(click);

那麼,不管div元素是在冒泡階段監聽,還是在捕獲階段監聽,都會觸發監聽函數。因爲這時div元素是事件的目標,不存在是否冒泡的問題,div元素總是會接收到事件,因此導致監聽函數生效

實例屬性

Event.bubbles,Event.eventPhase

Event.bubbles屬性返回一個布爾值,表示當前事件是否會冒泡。該屬性爲只讀屬性,一般用來了解 Event 實例是否可以冒泡。前面說過,除非顯式聲明,Event構造函數生成的事件,默認是不冒泡的。Event.eventPhase屬性返回一個整數常量,表示事件目前所處的階段。該屬性只讀

var phase = event.eventPhase;

Event.eventPhase的返回值有四種可能:

0,事件目前沒有發生

1,事件目前處於捕獲階段,即處於從祖先節點向目標節點的傳播過程中

2,事件到達目標節點,即Event.target屬性指向的那個節點

3,事件處於冒泡階段,即處於從目標節點向祖先節點的反向傳播過程中

Event.cancelable,Event.cancelBubble,event.defaultPrevented

Event.cancelable屬性返回一個布爾值,表示事件是否可以取消。該屬性爲只讀屬性,一般用來了解 Event 實例的特性。大多數瀏覽器的原生事件是可以取消的;比如,取消click事件,點擊鏈接將無效。但是除非顯式聲明,Event構造函數生成的事件,默認是不可以取消的

var evt = new Event('foo');
evt.cancelable  // false

當Event.cancelable屬性爲true時,調用Event.preventDefault()就可以取消這個事件,阻止瀏覽器對該事件的默認行爲;如果事件不能取消,調用Event.preventDefault()會沒有任何效果。所以使用這個方法之前,最好用Event.cancelable屬性判斷一下是否可以取消

function preventEvent(event) {
  if (event.cancelable) {
    event.preventDefault();
  } else {
    console.warn('This event couldn\'t be canceled.');
    console.dir(event);
  }
}

Event.cancelBubble屬性是一個布爾值,如果設爲true,相當於執行Event.stopPropagation(),可以阻止事件的傳播;Event.defaultPrevented屬性返回一個布爾值,表示該事件是否調用過Event.preventDefault方法。該屬性只讀

if (event.defaultPrevented) { console.log('該事件已經取消了'); }

Event.currentTarget,Event.target

Event.currentTarget屬性返回事件當前所在的節點,即正在執行的監聽函數所綁定的那個節點;Event.target屬性返回原始觸發事件的那個節點,即事件最初發生的節點。事件傳播過程中,不同節點的監聽函數內部的Event.target與Event.currentTarget屬性的值是不一樣的,前者總是不變的,後者則是指向監聽函數所在的那個節點對象

<p id="para">Hello <em>World</em></p>
function hide(e) {
  console.log(this === e.currentTarget);  // 總是 true
  console.log(this === e.target);  // 有可能不是 true
  e.target.style.visibility = 'hidden';
}
para.addEventListener('click', hide, false);

上面代碼中,如果在para節點的子節點上面點擊,則e.target指向子節點,導致子節點(即 World 部分)會不可見。如果點擊 Hello 部分,則整個para都將不可見

Event.type

Event.type屬性返回一個字符串,表示事件類型;事件的類型是在生成事件的時候指定的;該屬性只讀

var evt = new Event('foo');
evt.type // "foo"

Event.timeStamp

Event.timeStamp屬性返回一個毫秒時間戳,表示事件發生的時間。它是相對於網頁加載成功開始計算的

var evt = new Event('foo');
evt.timeStamp // 3683.6999999995896

它的返回值有可能是整數,也有可能是小數(高精度時間戳),取決於瀏覽器的設置。下面是一個計算鼠標移動速度的例子,顯示每秒移動的像素數量

var previousX;
var previousY;
var previousT;
window.addEventListener('mousemove', function(event) {
  if ( previousX !== undefined && previousY !== undefined && previousT !== undefined ) {
    var deltaX = event.screenX - previousX;
    var deltaY = event.screenY - previousY;
    var deltaD = Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
    var deltaT = event.timeStamp - previousT;
    console.log(deltaD / deltaT * 1000);
  }
  previousX = event.screenX;
  previousY = event.screenY;
  previousT = event.timeStamp;
});

Event.isTrusted

Event.isTrusted屬性返回一個布爾值,表示該事件是否由真實的用戶行爲產生;比如,用戶點擊鏈接會產生一個click事件,該事件是用戶產生的;Event構造函數生成的事件,則是腳本產生的

var evt = new Event('foo');
evt.isTrusted // false

上面代碼中,evt對象是腳本產生的,所以isTrusted屬性返回false

Event.detail

Event.detail屬性只有瀏覽器的 UI (用戶界面)事件才具有。該屬性返回一個數值,表示事件的某種信息。具體含義與事件類型相關。比如,對於click和dblclick事件,Event.detail是鼠標按下的次數(1表示單擊,2表示雙擊,3表示三擊);對於鼠標滾輪事件,Event.detail是滾輪正向滾動的距離,負值就是負向滾動的距離,返回值總是3的倍數

<p>Hello</p>
function giveDetails(e) { console.log(e.detail); }
document.querySelector('p').onclick = giveDetails;

實例方法

Event.preventDefault()

Event.preventDefault方法取消瀏覽器對當前事件的默認行爲。比如點擊鏈接後,瀏覽器默認會跳轉到另一個頁面,使用這個方法以後,就不會跳轉了;再比如,按一下空格鍵,頁面向下滾動一段距離,使用這個方法以後也不會滾動了。該方法生效的前提是,事件對象的cancelable屬性爲true,如果爲false,調用該方法沒有任何效果。注意,該方法只是取消事件對當前元素的默認影響,不會阻止事件的傳播。如果要阻止傳播,可以使用stopPropagation()或stopImmediatePropagation()方法

<input type="checkbox" id="my-checkbox" />
var cb = document.getElementById('my-checkbox');
cb.addEventListener( 'click', function (e){ e.preventDefault(); }, false ); //瀏覽器的默認行爲是單擊會選中單選框,取消這個行爲,就導致無法選中單選框

利用這個方法,可以爲文本輸入框設置校驗條件。如果用戶的輸入不符合條件,就無法將字符輸入文本框

<input type="text" id="my-input" />
var input = document.getElementById('my-input');
input.addEventListener('keypress', checkName, false);
function checkName(e) {
  if (e.charCode < 97 || e.charCode > 122) { e.preventDefault(); } //爲文本框的keypress事件設定監聽函數後將只能輸入小寫字母,否則輸入事件的默認行爲(寫入文本框)將被取消,導致不能向文本框輸入內容
}

Event.stopPropagation()

stopPropagation方法阻止事件在 DOM 中繼續傳播,防止再觸發定義在別的節點上的監聽函數,但是不包括在當前節點上其他的事件監聽函數

function stopEvent(e) { e.stopPropagation(); }
el.addEventListener('click', stopEvent, false);

Event.stopImmediatePropagation()

Event.stopImmediatePropagation方法阻止同一個事件的其他監聽函數被調用,不管監聽函數定義在當前節點還是其他節點。也就是說,該方法阻止事件的傳播,比Event.stopPropagation()更徹底。如果同一個節點對於同一個事件指定了多個監聽函數,這些函數會根據添加的順序依次調用。只要其中有一個監聽函數調用了Event.stopImmediatePropagation方法,其他的監聽函數就不會再執行了

function l1(e){ e.stopImmediatePropagation(); }
function l2(e){ console.log('hello world'); }
el.addEventListener('click', l1, false);
el.addEventListener('click', l2, false);

上面代碼在el節點上,爲click事件添加了兩個監聽函數l1和l2。由於l1調用了event.stopImmediatePropagation方法,所以l2不會被調用

Event.composedPath()

Event.composedPath()返回一個數組,成員是事件的最底層節點和依次冒泡經過的所有上層節點

<div> <p>Hello</p> </div>
var div = document.querySelector('div');
var p = document.querySelector('p');
div.addEventListener('click', function (e) { console.log(e.composedPath()); }, false); // [p, div, body, html, document, Window]

鼠標事件

鼠標事件的種類

鼠標事件指與鼠標相關的事件,繼承了MouseEvent接口。具體的事件主要有以下一些:

1.click:按下鼠標(通常是按下主按鈕)時觸發

2.dblclick:在同一個元素上雙擊鼠標時觸發

3.mousedown:按下鼠標鍵時觸發

4.mouseup:釋放按下的鼠標鍵時觸發

5.mousemove:當鼠標在一個節點內部移動時觸發。當鼠標持續移動時,該事件會連續觸發。爲了避免性能問題,建議對該事件的監聽函數做一些限定,比如限定一段時間內只能運行一次

6.mouseenter:鼠標進入一個節點時觸發,進入子節點不會觸發這個事件

7.mouseover:鼠標進入一個節點時觸發,進入子節點會再一次觸發這個事件

8.mouseout:鼠標離開一個節點時觸發,離開父節點也會觸發這個事件

9.mouseleave:鼠標離開一個節點時觸發,離開父節點不會觸發這個事件

10.contextmenu:按下鼠標右鍵時(上下文菜單出現前)觸發,或者按下“上下文菜單鍵”時觸發

11.wheel:滾動鼠標的滾輪時觸發,該事件繼承的是WheelEvent接口

click事件指的是,用戶在同一個位置先完成mousedown動作,再完成mouseup動作。因此,觸發順序是,mousedown首先觸發,mouseup接着觸發,click最後觸發。dblclick事件則會在mousedown、mouseup、click之後觸發。mouseover事件和mouseenter事件,都是鼠標進入一個節點時觸發;兩者的區別是,mouseenter事件只觸發一次,而只要鼠標在節點內部移動,mouseover事件會在子節點上觸發多次

<ul>
   <li>item 1</li>
   <li>item 2</li>
  <li>item 3</li>
 </ul>
var ul = document.querySelector('ul');
// 進入 ul 節點以後,mouseenter 事件只會觸發一次;以後只要鼠標在節點內移動,都不會再觸發這個事件;event.target 是 ul 節點
ul.addEventListener('mouseenter', function (event) {
  event.target.style.color = 'purple';
  setTimeout(function () { event.target.style.color = ''; }, 500);
}, false);
// 進入 ul 節點以後,只要在子節點上移動,mouseover 事件會觸發多次;event.target 是 li 節點
ul.addEventListener('mouseover', function (event) {
  event.target.style.color = 'orange';
  setTimeout(function () { event.target.style.color = ''; }, 500);
}, false);

mouseout事件和mouseleave事件,都是鼠標離開一個節點時觸發。兩者的區別是,在父元素內部離開一個子元素時,mouseleave事件不會觸發,而mouseout事件會觸發

MouseEvent 接口概述

MouseEvent接口代表了鼠標相關的事件,單擊(click)、雙擊(dblclick)、鬆開鼠標鍵(mouseup)、按下鼠標鍵(mousedown)等動作,所產生的事件對象都是MouseEvent實例;此外,滾輪事件和拖拉事件也是MouseEvent實例。MouseEvent接口繼承了Event接口,所以擁有Event的所有屬性和方法;它還有自己的屬性和方法。瀏覽器原生提供一個MouseEvent構造函數,用於新建一個MouseEvent實例

var event = new MouseEvent(type, options);

MouseEvent構造函數接受兩個參數。第一個參數是字符串,表示事件名稱;第二個參數是一個事件配置對象,該參數可選。除了Event接口的實例配置屬性,該對象可以配置以下屬性,所有屬性都是可選的:

1.screenX:數值,鼠標相對於屏幕的水平位置(單位像素),默認值爲0,設置該屬性不會移動鼠標

2.screenY:數值,鼠標相對於屏幕的垂直位置(單位像素),其他與screenX相同

3.clientX:數值,鼠標相對於程序窗口的水平位置(單位像素),默認值爲0,設置該屬性不會移動鼠標

4.clientY:數值,鼠標相對於程序窗口的垂直位置(單位像素),其他與clientX相同

5.ctrlKey:布爾值,是否同時按下了 Ctrl 鍵,默認值爲false

6.shiftKey:布爾值,是否同時按下了 Shift 鍵,默認值爲false

7.altKey:布爾值,是否同時按下 Alt 鍵,默認值爲false

8.metaKey:布爾值,是否同時按下 Meta 鍵,默認值爲false

9.button:數值,表示按下了哪一個鼠標按鍵,默認值爲0,表示按下主鍵(通常是鼠標的左鍵)或者當前事件沒有定義這個屬性;1表示按下輔助鍵(通常是鼠標的中間鍵),2表示按下次要鍵(通常是鼠標的右鍵)

10.buttons:數值,表示按下了鼠標的哪些鍵,是一個三個比特位的二進制值,默認爲0(沒有按下任何鍵)。1(二進制001)表示按下主鍵(通常是左鍵),2(二進制010)表示按下次要鍵(通常是右鍵),4(二進制100)表示按下輔助鍵(通常是中間鍵)。因此,如果返回3(二進制011)就表示同時按下了左鍵和右鍵

11.relatedTarget:節點對象,表示事件的相關節點,默認爲null。mouseenter和mouseover事件時,表示鼠標剛剛離開的那個元素節點;mouseout和mouseleave事件時,表示鼠標正在進入的那個元素節點

function simulateClick() {
  var event = new MouseEvent('click', { 'bubbles': true, 'cancelable': true });
  var cb = document.getElementById('checkbox');
  cb.dispatchEvent(event);
}

MouseEvent 接口的實例屬性

MouseEvent.altKey,MouseEvent.ctrlKey,MouseEvent.metaKey,MouseEvent.shiftKey

MouseEvent.altKey、MouseEvent.ctrlKey、MouseEvent.metaKey、MouseEvent.shiftKey這四個屬性都返回一個布爾值,表示事件發生時,是否按下對應的鍵。它們都是隻讀屬性:

altKey屬性:Alt 鍵

ctrlKey屬性:Ctrl 鍵

metaKey屬性:Meta 鍵(Mac 鍵盤是一個四瓣的小花,Windows 鍵盤是 Windows 鍵)

shiftKey屬性:Shift 鍵

<body onclick="showKey(event)">
function showKey(e) { //點擊網頁會輸出是否同時按下對應的鍵
  console.log('ALT key pressed: ' + e.altKey);
  console.log('CTRL key pressed: ' + e.ctrlKey);
  console.log('META key pressed: ' + e.metaKey);
  console.log('SHIFT key pressed: ' + e.shiftKey);
}

MouseEvent.button,MouseEvent.buttons

MouseEvent.button屬性返回一個數值,表示事件發生時按下了鼠標的哪個鍵。該屬性只讀

0:按下主鍵(通常是左鍵),或者該事件沒有初始化這個屬性(比如mousemove事件)。

1:按下輔助鍵(通常是中鍵或者滾輪鍵)。

2:按下次鍵(通常是右鍵)

<button onmouseup="whichButton(event)">點擊</button>
var whichButton = function (e) {
  switch (e.button) {
    case 0:
      console.log('Left button clicked.');
      break;
    case 1:
      console.log('Middle button clicked.');
      break;
    case 2:
      console.log('Right button clicked.');
      break;
    default:
      console.log('Unexpected code: ' + e.button);
  }
}

MouseEvent.buttons屬性返回一個三個比特位的值,表示同時按下了哪些鍵。它用來處理同時按下多個鼠標鍵的情況。該屬性只讀。

1:二進制爲001(十進制的1),表示按下左鍵。

2:二進制爲010(十進制的2),表示按下右鍵。

4:二進制爲100(十進制的4),表示按下中鍵或滾輪鍵。

同時按下多個鍵的時候,每個按下的鍵對應的比特位都會有值。比如,同時按下左鍵和右鍵,會返回3(二進制爲011)

MouseEvent.clientX,MouseEvent.clientY

MouseEvent.clientX屬性返回鼠標位置相對於瀏覽器窗口左上角的水平座標(單位像素),MouseEvent.clientY屬性返回垂直座標。這兩個屬性都是隻讀屬性

<body onmousedown="showCoords(event)">
function showCoords(evt){
  console.log( 'clientX value: ' + evt.clientX + '\n' + 'clientY value: ' + evt.clientY + '\n' );
}

這兩個屬性還分別有一個別名MouseEvent.x和MouseEvent.y

MouseEvent.movementX,MouseEvent.movementY

MouseEvent.movementX屬性返回當前位置與上一個mousemove事件之間的水平距離(單位像素)。數值上,它等於下面的計算公式

currentEvent.movementX = currentEvent.screenX - previousEvent.screenX

MouseEvent.movementY屬性返回當前位置與上一個mousemove事件之間的垂直距離(單位像素)。數值上,它等於下面的計算公式

currentEvent.movementY = currentEvent.screenY - previousEvent.screenY

這兩個屬性都是隻讀屬性

MouseEvent.screenX,MouseEvent.screenY

MouseEvent.screenX屬性返回鼠標位置相對於屏幕左上角的水平座標(單位像素),MouseEvent.screenY屬性返回垂直座標。這兩個屬性都是隻讀屬性

<body onmousedown="showCoords(event)">
function showCoords(evt) {
  console.log( 'screenX value: ' + evt.screenX + '\n', 'screenY value: ' + evt.screenY + '\n' );
}

MouseEvent.offsetX,MouseEvent.offsetY

MouseEvent.offsetX屬性返回鼠標位置與目標節點左側的padding邊緣的水平距離(單位像素),MouseEvent.offsetY屬性返回與目標節點上方的padding邊緣的垂直距離。這兩個屬性都是隻讀屬性

<style>
    p { width: 100px; height: 100px; padding: 100px; }
</style>
<p>Hello</p>
var p = document.querySelector('p');
p.addEventListener( 'click', function (e) { console.log(e.offsetX); console.log(e.offsetY); }, false ); //鼠標如果在p元素的中心位置點擊,會返回150 150。因此中心位置距離左側和上方的padding邊緣,等於padding的寬度(100像素)加上元素內容區域一半的寬度(50像素)

MouseEvent.pageX,MouseEvent.pageY

MouseEvent.pageX屬性返回鼠標位置與文檔左側邊緣的距離(單位像素),MouseEvent.pageY屬性返回與文檔上側邊緣的距離(單位像素);它們的返回值都包括文檔不可見的部分。這兩個屬性都是隻讀

<style>
    body { height: 2000px; }
</style>
document.body.addEventListener( 'click', function (e) { console.log(e.pageX); console.log(e.pageY); }, false ); //頁面高度爲2000像素,會產生垂直滾動條。滾動到頁面底部,點擊鼠標輸出的pageY值會接近2000

MouseEvent.relatedTarget

MouseEvent.relatedTarget屬性返回事件的相關節點;對於那些沒有相關節點的事件,該屬性返回null;該屬性只讀。下表列出不同事件的target屬性值和relatedTarget屬性值義

事件名稱 target屬性 relatedTarget屬性
focusin 接受焦點的節點 喪失焦點的節點
focusout 喪失焦點的節點 接受焦點的節點
mouseenter 將要進入的節點 將要離開的節點
mouseleave 將要離開的節點 將要進入的節點
mouseout 將要離開的節點 將要進入的節點
mouseover 將要進入的節點 將要離開的節點
dragenter 將要進入的節點 將要離開的節點
dragexit 將要離開的節點 將要進入的節點
<div id="outer" style="height:50px;width:50px;border-width:1px solid black;">
    <div id="inner" style="height:25px;width:25px;border:1px solid black;"></div>
</div>
var inner = document.getElementById('inner');
inner.addEventListener('mouseover', function (event) { console.log('進入' + event.target.id + ' 離開' + event.relatedTarget.id); }, false);
inner.addEventListener('mouseenter', function (event) { console.log('進入' + event.target.id + ' 離開' + event.relatedTarget.id); });
inner.addEventListener('mouseout', function () { console.log('離開' + event.target.id + ' 進入' + event.relatedTarget.id); });
inner.addEventListener("mouseleave", function (){ console.log('離開' + event.target.id + ' 進入' + event.relatedTarget.id); });
// 鼠標從 outer 進入inner,輸出
// 進入inner 離開outer
// 進入inner 離開outer

// 鼠標從 inner進入 outer,輸出
// 離開inner 進入outer
// 離開inner 進入outer

MouseEvent 接口的實例方法

MouseEvent.getModifierState()

MouseEvent.getModifierState方法返回一個布爾值,表示有沒有按下特定的功能鍵。它的參數是一個表示功能鍵的字符串

document.addEventListener('click', function (e) { console.log(e.getModifierState('CapsLock')); }, false); //用戶是否按下了大寫鍵

WheelEvent 接口

概述

WheelEvent 接口繼承了 MouseEvent 實例,代表鼠標滾輪事件的實例對象。目前,鼠標滾輪相關的事件只有一個wheel事件,用戶滾動鼠標的滾輪,就生成這個事件的實例。瀏覽器原生提供WheelEvent()構造函數,用來生成WheelEvent實例

var wheelEvent = new WheelEvent(type, options);

WheelEvent()構造函數可以接受兩個參數,第一個是字符串,表示事件類型,對於滾輪事件來說,這個值目前只能是wheel。第二個參數是事件的配置對象。該對象的屬性除了Event、UIEvent的配置屬性以外,還可以接受以下幾個屬性,所有屬性都是可選的:

deltaX:數值,表示滾輪的水平滾動量,默認值是 0.0

deltaY:數值,表示滾輪的垂直滾動量,默認值是 0.0

deltaZ:數值,表示滾輪的 Z 軸滾動量,默認值是 0.0

deltaMode:數值,表示相關的滾動事件的單位,適用於上面三個屬性。0表示滾動單位爲像素,1表示單位爲行,2表示單位爲頁,默認爲0

實例屬性

WheelEvent事件實例除了具有Event和MouseEvent的實例屬性和實例方法,還有一些自己的實例屬性,但是沒有自己的實例方法;下面的屬性都是隻讀屬性:

WheelEvent.deltaX:數值,表示滾輪的水平滾動量

WheelEvent.deltaY:數值,表示滾輪的垂直滾動量

WheelEvent.deltaZ:數值,表示滾輪的 Z 軸滾動量

WheelEvent.deltaMode:數值,表示上面三個屬性的單位,0是像素,1是行,2是頁

鍵盤事件

鍵盤事件的種類

鍵盤事件由用戶擊打鍵盤觸發,主要有keydown、keypress、keyup三個事件,它們都繼承了KeyboardEvent接口:

keydown:按下鍵盤時觸發

keypress:按下有值的鍵時觸發,即按下 Ctrl、Alt、Shift、Meta 這樣無值的鍵,這個事件不會觸發。對於有值的鍵,按下時先觸發keydown事件,再觸發這個事件

keyup:鬆開鍵盤時觸發該事件

如果用戶一直按鍵不鬆開,就會連續觸發鍵盤事件,觸發的順序如下:

keydown -- keypress -- keydown -- keypress ...(重複以上過程)-- keyup

KeyboardEvent 接口概述

KeyboardEvent接口用來描述用戶與鍵盤的互動。這個接口繼承了Event接口,並且定義了自己的實例屬性和實例方法。瀏覽器原生提供KeyboardEvent構造函數,用來新建鍵盤事件的實例

new KeyboardEvent(type, options)

KeyboardEvent構造函數接受兩個參數。第一個參數是字符串,表示事件類型;第二個參數是一個事件配置對象,該參數可選。除了Event接口提供的屬性,還可以配置以下字段,它們都是可選:

key:字符串,當前按下的鍵,默認爲空字符串

code:字符串,表示當前按下的鍵的字符串形式,默認爲空字符串

location:整數,當前按下的鍵的位置,默認爲0

ctrlKey:布爾值,是否按下 Ctrl 鍵,默認爲false

shiftKey:布爾值,是否按下 Shift 鍵,默認爲false

altKey:布爾值,是否按下 Alt 鍵,默認爲false

metaKey:布爾值,是否按下 Meta 鍵,默認爲false

repeat:布爾值,是否重複按鍵,默認爲false

KeyboardEvent 的實例屬性

KeyboardEvent.altKey,KeyboardEvent.metaKey.ctrlKey,KeyboardEvent.metaKey,KeyboardEvent.shiftKey

以下屬性都是隻讀屬性,返回一個布爾值,表示是否按下對應的鍵:

KeyboardEvent.altKey:是否按下 Alt 鍵

KeyboardEvent.ctrlKey:是否按下 Ctrl 鍵

KeyboardEvent.metaKey:是否按下 meta 鍵(Mac 系統是一個四瓣的小花,Windows 系統是 windows 鍵)

KeyboardEvent.shiftKey:是否按下 Shift 鍵

function showChar(e) {
  console.log('ALT: ' + e.altKey);
  console.log('CTRL: ' + e.ctrlKey);
  console.log('Meta: ' + e.metaKey);
  console.log('Meta: ' + e.shiftKey);
}
document.body.addEventListener('keydown', showChar, false);

KeyboardEvent.code

KeyboardEvent.code屬性返回一個字符串,表示當前按下的鍵的字符串形式。該屬性只讀。下面是一些常用鍵的字符串形式:

數字鍵0 - 9:返回digital0 - digital9

字母鍵A - z:返回KeyA - KeyZ

功能鍵F1 - F12:返回 F1 - F12

方向鍵:返回ArrowDown、ArrowUp、ArrowLeft、ArrowRight

Alt 鍵:返回AltLeft或AltRight

Shift 鍵:返回ShiftLeft或ShiftRight

Ctrl 鍵:返回ControlLeft或ControlRight

KeyboardEvent.key

KeyboardEvent.key屬性返回一個字符串,表示按下的鍵名。該屬性只讀。如果按下的鍵代表可打印字符,則返回這個字符,比如數字、字母。如果按下的鍵代表不可打印的特殊字符,則返回預定義的鍵值,比如 Backspace,Tab,Enter,Shift,Control,Alt,CapsLock,Esc,Spacebar,PageUp,PageDown,End,Home,Left,Right,Up,Down,PrintScreen,Insert,Del,Win,F1~F12,NumLock,Scroll 等

如果同時按下一個控制鍵和一個符號鍵,則返回符號鍵的鍵名。比如,按下 Ctrl + a,則返回a;按下 Shift + a,則返回大寫的A。如果無法識別鍵名,返回字符串Unidentified

KeyboardEvent.location

KeyboardEvent.location屬性返回一個整數,表示按下的鍵在鍵盤的哪一個區域。它可能取以下值:

0:處在鍵盤的主區域,或者無法判斷處於哪一個區域

1:處在鍵盤的左側,只適用那些有兩個位置的鍵(比如 Ctrl 和 Shift 鍵)

2:處在鍵盤的右側,只適用那些有兩個位置的鍵(比如 Ctrl 和 Shift 鍵)

3:處在數字小鍵盤

KeyboardEvent.repeat

KeyboardEvent.repeat返回一個布爾值,代表該鍵是否被按着不放,以便判斷是否重複這個鍵,即瀏覽器會持續觸發keydown和keypress事件,直到用戶鬆開手爲止

KeyboardEvent 的實例方法

KeyboardEvent.getModifierState()

KeyboardEvent.getModifierState()方法返回一個布爾值,表示是否按下或激活指定的功能鍵。它的常用參數如下:

Alt:Alt 鍵

CapsLock:大寫鎖定鍵

Control:Ctrl 鍵

Meta:Meta 鍵

NumLock:數字鍵盤開關鍵

Shift:Shift 鍵

if ( event.getModifierState('Control') + event.getModifierState('Alt') + event.getModifierState('Meta') > 1 ) { return; }

進度事件

進度事件的種類

進度事件用來描述資源加載的進度,主要由 AJAX 請求、、、

abort:外部資源中止加載時(比如用戶取消)觸發。如果發生錯誤導致中止,不會觸發該事件

error:由於錯誤導致外部資源無法加載時觸發

load:外部資源加載成功時觸發

loadstart:外部資源開始加載時觸發

loadend:外部資源停止加載時觸發,發生順序排在error、abort、load等事件的後面

progress:外部資源加載過程中不斷觸發

timeout:加載超時時觸發

注意,除了資源下載,文件上傳也存在這些事件

image.addEventListener('load', function (event) { image.classList.add('finished'); });
image.addEventListener('error', function (event) { image.style.display = 'none'; });

上面代碼在圖片元素加載完成後,爲圖片元素添加一個finished的 Class。如果加載失敗,就把圖片元素的樣式設置爲不顯示

有時候,圖片加載會在腳本運行之前就完成,尤其是當腳本放置在網頁底部的時候,因此有可能load和error事件的監聽函數根本不會執行。所以,比較可靠的方式,是用complete屬性先判斷一下是否加載完成

function loaded() {
  // ...
}
if (image.complete) {
  loaded();
} else {
  image.addEventListener('load', loaded);
}

由於 DOM 的元素節點沒有提供是否加載錯誤的屬性,所以error事件的監聽函數最好放在元素的 HTML 代碼中,這樣才能保證發生加載錯誤時百分之百會執行

<img src="/wrong/url" onerror="this.style.display='none';" />

loadend事件的監聽函數,可以用來取代abort事件、load事件、error事件的監聽函數,因爲它總是在這些事件之後發生

req.addEventListener('loadend', loadEnd, false);
function loadEnd(e) { console.log('傳輸結束,成功失敗未知'); }

loadend事件本身不提供關於進度結束的原因,但可以用它來做所有加載結束場景都需要做的一些操作;另外,error事件有一個特殊的性質,就是不會冒泡;所以,子元素的error事件,不會觸發父元素的error事件監聽函數

ProgressEvent 接口

ProgressEvent接口主要用來描述外部資源加載的進度,比如 AJAX 加載、、

new ProgressEvent(type, options)

ProgressEvent()構造函數接受兩個參數。第一個參數是字符串,表示事件的類型,這個參數是必須的。第二個參數是一個配置對象,表示事件的屬性,該參數可選。配置對象除了可以使用Event接口的配置屬性,還可以使用下面的屬性,所有這些屬性都是可選的:

1.lengthComputable:布爾值,表示加載的總量是否可以計算,默認是false

2.loaded:整數,表示已經加載的量,默認是0

3.total:整數,表示需要加載的總量,默認是0

ProgressEvent具有對應的實例屬性:

ProgressEvent.lengthComputable / ProgressEvent.loaded / ProgressEvent.total

如果ProgressEvent.lengthComputable爲false,ProgressEvent.total實際上是沒有意義的

var p = new ProgressEvent('load', { lengthComputable: true, loaded: 30, total: 100, });
document.body.addEventListener('load', function (e) {
  console.log('已經加載:' + (e.loaded / e.total) * 100 + '%');
});
document.body.dispatchEvent(p); // 已經加載:30%

上面代碼先構造一個load事件,拋出後被監聽函數捕捉到

var xhr = new XMLHttpRequest();
xhr.addEventListener('progress', updateProgress, false);
xhr.addEventListener('load', transferComplete, false);
xhr.addEventListener('error', transferFailed, false);
xhr.addEventListener('abort', transferCanceled, false);
xhr.open();
function updateProgress(e) {
  if (e.lengthComputable) {
    var percentComplete = e.loaded / e.total;
  } else {
    console.log('不能計算進度');
  }
}
function transferComplete(e) { console.log('傳輸結束'); }
function transferFailed(evt) { console.log('傳輸過程中發生錯誤'); }
function transferCanceled(evt) { console.log('用戶取消了傳輸'); }

上面是下載過程的進度事件,還存在上傳過程的進度事件。這時所有監聽函數都要放在XMLHttpRequest.upload對象上面

var xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', updateProgress, false);
xhr.upload.addEventListener('load', transferComplete, false);
xhr.upload.addEventListener('error', transferFailed, false);
xhr.upload.addEventListener('abort', transferCanceled, false);
xhr.open();

表單事件

表單事件的種類

input 事件

input事件當、、的值發生變化時觸發;對於複選框()或單選框(),用戶改變選項時,也會觸發這個事件;另外,對於打開contenteditable屬性的元素,只要值發生變化,也會觸發input事件。input事件的一個特點,就是會連續觸發,比如用戶每按下一次按鍵,就會觸發一次input事件。input事件對象繼承了InputEvent接口;該事件跟change事件很像,不同之處在於input事件在元素的值發生變化後立即發生,而change在元素失去焦點時發生,而內容此時可能已經變化多次;也就是說,如果有連續變化,input事件會觸發多次,而change事件只在失去焦點時觸發一次

<select id="mySelect">
  <option value="1">1</option>
  <option value="2">2</option>
  <option value="3">3</option>
</select>
function inputHandler(e) {
  console.log(e.target.value)
}
var mySelect = document.querySelector('#mySelect');
mySelect.addEventListener('input', inputHandler);

上面代碼中,改變下拉框選項時,會觸發input事件,從而執行回調函數inputHandler

select 事件

select事件當在、裏面選中文本時觸發

<input id="test" type="text" value="Select me!" />
var elem = document.getElementById('test');
elem.addEventListener('select', function (e) {
  console.log(e.type); // "select"
}, false);

選中的文本可以通過event.target元素的selectionDirection、selectionEnd、selectionStart和value屬性拿到

change 事件

change事件當、、的值發生變化時觸發。它與input事件的最大不同,就是不會連續觸發,只有當全部修改完成時纔會觸發,另一方面input事件必然伴隨change事件。具體來說,分成以下幾種情況:

1.激活單選框(radio)或複選框(checkbox)時觸發

2.用戶提交時觸發。比如,從下列列表(select)完成選擇,在日期或文件輸入框完成選擇

3.當文本框或元素的值發生改變,並且喪失焦點時觸發

<select size="1" onchange="changeEventHandler(event);">
  <option>chocolate</option>
  <option>strawberry</option>
  <option>vanilla</option>
</select>
function changeEventHandler(event) {
  console.log(event.target.value);
}

如果比較一下上面input事件的例子,你會發現對於元素來說,input和change事件基本是等價的

invalid 事件

用戶提交表單時,如果表單元素的值不滿足校驗條件,就會觸發invalid事件

<form>
  <input type="text" required oninvalid="console.log('invalid input')" /> //輸入框是必填的。如果不填,用戶點擊按鈕提交時,就會觸發輸入框的invalid事件,導致提交被取消
  <button type="submit">提交</button>
</form>

reset 事件,submit 事件

這兩個事件發生在表單對象

上,而不是發生在表單的成員上:

reset事件當表單重置(所有表單成員變回默認值)時觸發

submit事件當表單數據向服務器提交時觸發。注意,submit事件的發生對象是

元素,而不是
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章