js
在操作DOM
中存在著許多跨瀏覽器方面的坑,本文花了我將近一週的時間整理,我將根據例項整理那些大大小小的“坑”。
DOM
的工作模式是:先載入文件的靜態內容、再以動態方式對它們進行重新整理,動態重新整理不影響文件的靜態內容。
PS:IE
中的所有 DOM
物件都是以 COM
物件的形式實現的,這意味著 IE
中的 DOM
可能會和其他瀏覽器有一定的差異。
Node 介面
特性/方法 | 型別/返回型別 | 說 明 |
---|---|---|
nodeName | String | 節點的名字;根據節點的型別而定義 |
nodeValue | String | 節點的值;根據節點的型別而定義 |
nodeType | Number | 節點的型別常量值之一 |
ownerDocument | Document | 返回某元素的根元素 |
firstChild | Node | 指向在childNodes列表中的第一個節點 |
lastChild | Node | 指向在childNodes列表中的最後一個節點 |
childNodes | NodeList | 所有子節點的列表 |
previousSibling | Node | 返回選定節點的上一個同級節點,若不存在,則返回null |
nextSibling | Node | 返回被選節點的下一個同級節點,若不存在,則返回null |
hasChildNodes() | Boolean | 如果當前元素節點擁有子節點,返回true,否則返回false |
attributes | NamedNodeMap | 返回包含被選節點屬性的 NamedNodeMap |
appendChild(node) | node | 將node新增到childNodes的末尾 |
removeChild(node) | node | 從childNodes中刪除node |
replaceChild(newnode, oldnode) | Node | 將childNodes中的oldnode替換成newnode |
insertBefore | Node | 在已有子節點之前插入新的子節點 |
firstChild
相當於 childNodes[0]
;lastChild
相當於childNodes[box.childNodes.length - 1]
。
nodeType返回結點的型別
1 2 3 |
--元素結點返回1 --屬性結點返回2 --文字結點返回3 |
innerHTML 和 nodeValue
1 2 3 4 5 |
對於文字節點,nodeValue 屬性包含文字。 對於屬性節點,nodeValue 屬性包含屬性值。 nodeValue 屬性對於文件節點和元素節點是不可用的。 |
兩者區別
1 2 |
box.childNodes[0].nodeValue = '<strong>abc</strong>';//結果為:<strong>abc</strong> abcbox.innerHTML = '<strong>abc</strong>';//結果為:abc |
nodeName屬性獲得結點名稱
1 2 3 |
--對於元素結點返回的是標記名稱,如:<a herf><a>返回的是"a" --對於屬性結點返回的是屬性名稱,如:class="test" 返回的是test --對於文字結點返回的是文字的內容 |
tagName
1 |
document.getElementByTagName(tagName):返回一個陣列,包含對這些結點的引用 |
getElementsByTagName()
方法將返回一個物件陣列 HTMLCollection(NodeList)
,這個陣列儲存著所有相同元素名的節點列表。
1 |
document.getElementsByTagName('*');//獲取所有元素 |
PS:IE
瀏覽器在使用萬用字元的時候,會把文件最開始的 html
的規範宣告當作第一個元素節點。
1 2 3 4 |
document.getElementsByTagName('li');//獲取所有 li 元素,返回陣列 document.getElementsByTagName('li')[0];//獲取第一個 li 元素,HTMLLIElement document.getElementsByTagName('li').item(0);//獲取第一個 li 元素,HTMLLIElement document.getElementsByTagName('li').length;//獲取所有 li 元素的數目 |
節點的絕對引用:
1 2 3 4 5 |
返回文件的根節點:document.documentElement 返回當前文件中被擊活的標籤節點:document.activeElement 返回滑鼠移出的源節點:event.fromElement 返回滑鼠移入的源節點:event.toElement 返回啟用事件的源節點:event.srcElement |
節點的相對引用:(設當前對節點為node)
1 2 3 4 5 6 7 8 |
返回父節點:node.parentNode || node.parentElement(IE) 返回子節點集合(包含文字節點及標籤節點):node.childNodes 返回子標籤節點集合:node.children 返回子文字節點集合:node.textNodes 返回第一個子節點:node.firstChild 返回最後一個子節點:node.lastChild 返回同屬下一個節點:node.nextSibling 返回同屬上一個節點:node.previousSibling |
節點資訊
1 2 3 |
是否包含某節點:node.contains() 是否有子節點node.hasChildNodes() |
建立新節點
1 2 3 |
createDocumentFragment()--建立文件碎片節點 createElement(tagname)--建立標籤名為tagname的元素 createTextNode(text)--建立包含文字text的文字節點 |
獲取滑鼠點選事件的位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
document.onclick = mouseClick; function mouseClick(ev){ ev = ev || window.event;//window.event用來相容IE var x = 0; var y = 0; if(ev.pageX){ x = ev.pageX; y = ev.pageY; }else if(ev.clientX){ var offsetX = 0 , offsetY = 0; if(document.documentElement.scrollLeft){ offsetX = document.documentElement.scrollLeft; offsetY = document.documentElement.scrollTop; }else if(document.body){ offsetX = document.body.scrollLeft; offsetY = document.body.scrollTop; } x = ev.clientX + offsetX; y = ev.clientY + offsetY; } alert("你點選的位置是 x="+ x + " y=" + y); } |
以下所描述的屬性在chrome
和Safari
都很給力的支援了。
問題一:Firefox
,Chrome
、Safari
和IE9
都是通過非標準事件的pageX
和pageY
屬性來獲取web頁面的滑鼠位置的。pageX/Y
獲取到的是觸發點相對文件區域左上角距離,以頁面為參考點,不隨滑動條移動而變化
問題二:在IE 中,event
物件有 x
, y
屬性(事件發生的位置的 x
座標和 y
座標)火狐中沒有。在火狐中,與event.x
等效的是event.pageX
。event.clientX
與 event.pageX
有微妙的差別(當整個頁面有滾動條的時候),不過大多數時候是等效的。
offsetX
:IE
特有,chrome
也支援。滑鼠相比較於觸發事件的元素的位置,以元素盒子模型的內容區域的左上角為參考點,如果有boder
,可能出現負值
問題三:
scrollTop
為滾動條向下移動的距離,所有瀏覽器都支援document.documentElement
。
其餘參照:http://segmentfault.com/a/1190000002559158#articleHeader11
參照表
(+
為支援,-
為不支援):
1 2 3 4 5 6 7 8 9 10 11 |
offsetX/offsetY:W3C- IE+ Firefox- Opera+ Safari+ chrome+ x/y:W3C- IE+ Firefox- Opera+ Safari+ chrome+ layerX/layerY:W3C- IE- Firefox+ Opera- Safari+ chrome+ pageX/pageY:W3C- IE- Firefox+ Opera+ Safari+ chrome+ clientX/clientY:W3C+ IE+ Firefox+ Opera+ Safari+ chrome+ screenX/screenY:W3C+ IE+ Firefox+ Opera+ Safari+ chrome+ |
檢視下方DEMO
:
你會發現offsetX
在Firefox
下是undefined
,在chrome
和IE
則會正常顯示。
https://jsfiddle.net/f4am208m/embedded/result/
offsetLeft和style.left區別
1 2 3 4 5 |
1.style.left返回的是字串,比如10px。而offsetLeft返回的是數值,比如數值10 2.style.left是可讀寫的,offsetLeft是隻讀的 3.style.left的值需要事先定義(在樣式表中定義無效,只能取到在html中定義的值),否則取到的值是空的 |
getComputedStyle與currentStyle
getComputedStyle()
接受兩個引數:要取得計算樣式的元素和一個偽元素,如果不需要偽元素,則可以是null
。然而,在IE中,並不支援getComputedStyle
,IE提供了currentStyle
屬性。
getComputedStyle(obj , false )
是支援 w3c (FF12、chrome 14、safari):在FF新版本中只需要第一個引數,即操作物件,第二個引數寫“false”也是大家通用的寫法,目的是為了相容老版本的火狐瀏覽器。
缺點:在標準瀏覽器中正常,但在IE6/7/8中不支援
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
window.onload=function(){ var oBtn=document.getElementById('btn'); var oDiv=document.getElementById('div1'); oBtn.onclick=function(){ //alert(oDiv.style.width); //寫在樣式表裡無法讀取,只能得到寫在行內的 //alert(getComputedStyle(oDiv).width); //適用於標準瀏覽器 IE6、7、8不識別 //alert(oDiv.currentStyle.width); //適用於IE瀏覽器,標準瀏覽器不識別 if(oDiv.currentStyle){ alert(oDiv.currentStyle.width); }else{ alert(getComputedStyle(oDiv).width); } }; }; |
取消表單提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<script type="text/javascript"> function listenEvent(eventObj,event,eventHandler){ if(eventObj.addEventListener){ eventObj.addEventListener(event,eventHandler,false); }else if(eventObj.attachEvent){ event = "on" + event; eventObj.attachEvent(event,eventHandler); }else{ eventObj["on" + event] = eventHandler; } } function cancelEvent(event){ if(event.preventDefault){ event.preventDefault();//w3c }else{ event.returnValue = true;//IE } } window.onload = function () { var form = document.forms["picker"]; listenEvent(form,"submit",validateFields); }; function validateFields(evt){ evt = evt ? evt : window.event; ... if(invalid){ cancelEvent(evt); } } </script> |
確定瀏覽器視窗的尺寸
對於主流瀏覽器來說,比如IE9
、Firefox
,Chrome
和Safari
,支援名為innerWidth
和 innerHeight
的視窗物件屬性,它返回視窗的視口區域,減去任何滾動條的大小。IE
不支援innerWidth
和 innerHeight
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<script type="text/javascript"> function size(){ var w = 0, h=0; if(!window.innerWidth){ w = (document.documentElement.clientWidth ? document.documentElement.clientWidth : document.body.clientWidth); h = (document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight); }else{ w = window.innerWidth; h = window.innerHeight; } return {width:w,height:h}; } console.log(size());//Object { width: 1366, height: 633 } </script> |
實用的 JavaScript
方案(涵蓋所有瀏覽器):
1 2 3 |
var w=window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; var h=window.innerHeight || document.documentElement.clientHeight|| document.body.clientHeight; |
對於 IE 6、7、8的方案如下:
1 2 |
document.documentElement.clientHeight document.documentElement.clientWidth |
或者
1 2 |
document.body.clientHeight document.body.clientWidth |
Document
物件的body
屬性對應HTML
文件的<body>
標籤。Document
物件的documentElement
屬性則表示 HTML
文件的根節點。
attributes 屬性
attributes
屬性返回該節點的屬性節點集合。
1 2 3 4 5 6 7 |
document.getElementById('box').attributes//NamedNodeMap document.getElementById('box').attributes.length;//返回屬性節點個數 document.getElementById('box').attributes[0]; //Attr,返回最後一個屬性節點 document.getElementById('box').attributes[0].nodeType; //2,節點型別 document.getElementById('box').attributes[0].nodeValue; //屬性值 document.getElementById('box').attributes['id']; //Attr,返回屬性為 id 的節點 document.getElementById('box').attributes.getNamedItem('id'); //Attr |
setAttribute 和 getAttribute
在IE
中是不認識class
屬性的,需改為className
屬性,同樣,在Firefox
中,也是不認識className
屬性的,Firefox
只認識class
屬性,所以通常做法如下:
1 2 |
element.setAttribute(class, value); //for firefox element.setAttribute(className, value); //for IE |
IE:
可以使用獲取常規屬性的方法來獲取自定義屬性,也可以使用getAttribute()
獲取自定義屬性
Firefox
:只能使用getAttribute()
獲取自定義屬性.
解決方法:統一通過getAttribute()
獲取自定義屬性
1 2 3 4 5 6 |
document.getElementById('box').getAttribute('id');//獲取元素的 id 值 document.getElementById('box').id;//獲取元素的 id 值 document.getElementById('box').getAttribute('mydiv');//獲取元素的自定義屬性值 document.getElementById('box').mydiv//獲取元素的自定義屬性值, IE 不支援非 document.getElementById('box').getAttribute('class');//獲取元素的 class 值,IE 不支援 document.getElementById('box').getAttribute('className');//非 IE 不支援 |
PS:在 IE7
及更低版本的IE瀏覽器中,使用 setAttribute()
方法設定 class
和 style
屬性是沒有效果的,雖然 IE8
解決了這個bug
,但還是不建議使用。
removeAttribute()方法
1 2 |
removeAttribute()可以移除 HTML 屬性。 document.getElementById('box').removeAttribute('style');//移除屬性 |
PS:IE6
及更低版本不支援 removeAttribute()
方法。
跨瀏覽器事件Event物件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style type="text/css"> #drop{ width: 300px; height: 200px; background-color: #ff0000; padding: 5px; border: 2px solid #000000; } #item{ width: 100px; height: 100px; background-color: #ffff00; padding: 5px; margin: 20px; border: 1px dashed black; } *[draggable = true]{ -moz-user-select: none; -webkit-user-select: none; cursor: move; } </style> </head> <body> <div> <p>將金黃色的小方塊拖到紅色的大方塊中,不相容IE7及以下瀏覽器,相容主流瀏覽器!</p> </div> <div id="item" draggable="true"></div> <div id="drop"></div> <script type="text/javascript"> function listenEvent(target,type,handler){ if(target.addEventListener){//w3c target.addEventListener(type,handler,false); }else if(target.attachEvent){//IE type = "on" + type; target.attachEvent(type,handler);//IE }else{ target["on" + type] = handler; } } //取消事件 function cancelEvent(e){ if(e.preventDefault){ e.preventDefault();//w3c }else{ e.returnValue = false;//IE } } //取消傳遞 function cancelPropagation(e){ if(e.stopPropagation){ e.stopPropagation();//w3c }else{ e.cancelBubble = true;//IE } } window.onload = function () { var target = document.getElementById('drop'); listenEvent(target,'dragenter',cancelEvent); listenEvent(target,"dragover",dragOver); listenEvent(target,'drop', function (evt) { cancelPropagation(evt); evt = evt || window.event; evt.dataTransfer.dropEffect = 'copy'; var id = evt.dataTransfer.getData('Text'); target.appendChild(document.getElementById(id)); }); var item = document.getElementById('item'); item.setAttribute("draggable",'true'); listenEvent(item,'dragstart', function (evt) { evt = evt || window.event; evt.dataTransfer.effectAllowed = 'copy'; evt.dataTransfer.setData('Text',item.id); }); }; function dragOver(evt){ if(evt.preventDefault) evt.preventDefault(); evt = evt || window.event; evt.dataTransfer.dropEffect = 'copy'; return false; } </script> </body> </html> |
dataTransfer 物件
| 屬性 | 描述 |
| ————- |:————-:|
| dropEffect | 設定或獲取拖曳操作的型別和要顯示的游標型別 |
| effectAllowed | 設定或獲取資料傳送操作可應用於該物件的源元素 |
| 方法 | 描述 |
| ————- |:————-:|
| clearData | 通過 dataTransfer 或 clipboardData 物件從剪貼簿刪除一種或多種資料格式 |
| getData | 通過 dataTransfer 或 clipboardData 物件從剪貼簿獲取指定格式的資料
| setData | 以指定格式給 dataTransfer 或 clipboardData 物件賦予資料
HTML5拖拽的瀏覽器支援
Internet Explorer 9、Firefox、Opera 12、Chrome
以及 Safari 5
支援拖放
為了使元素可拖動,需把 draggable
屬性設定為 true
:
1 |
<img draggable="true" /> |
| 事件 | 描述 |
| ————- |:————-:|
| dragstart | 拖拽事件開始 |
| drag | 在拖動操作上 |
| dragenter | 拖動到目標上,用來決定目標是否接受放置
|dragover | 拖動到目標上,用來決定給使用者的反饋
|drop | 放置發生
| dragleave| 拖動離開目標
|dragend | 拖動操作結束
上述程式碼的一些瀏覽器相容性:
1 2 3 4 5 6 7 8 9 10 11 |
1.為了相容IE,我們將`window.event`賦給 `evt`,其他瀏覽器則會正確將接收到的`event`物件賦給`evt`。 2.w3c使用addEventListener來為事件元素新增事件監聽器,而IE則使用attachEvent。addEventListener為事件冒泡到的當前物件,而attachEvent是window 3.對於事件型別,IE需要加`on + type`屬性,而其他瀏覽器則不用 4.對於阻止元素的預設事件行為,下面是w3c和IE的做法: e.preventDefault();//w3c e.returnValue = false;//IE 5.對於取消事件傳播,w3c和IE也有不同的處理機制: e.stopPropagation();//w3c e.cancelBubble = true;//IE |
跨瀏覽器獲取目標物件
1 2 3 4 5 6 7 8 |
//跨瀏覽器獲取目標物件 function getTarget(ev){ if(ev.target){//w3c return ev.target; }else if(window.event.srcElement){//IE return window.event.srcElement; } } |
對於獲取觸發事件的物件,w3c
和IE
也有不同的做法:
1 2 |
event.target;//w3c event.srcElement;//IE |
我們可以使用三目運算子來相容他們:
1 |
obj = event.srcElement ? event.srcElement : event.target; |
innerText的問題
innerText
在IE
中能正常工作,但是innerText
在FireFox
中卻不行。
1 2 3 4 5 6 7 8 |
<p id="element"></p> <script type="text/javascript"> if(navigator.appName.indexOf("Explorer") >-1){ document.getElementById('element').innerText = "my text"; } else{ document.getElementById('element').textContent = "my text"; } </script> |
跨瀏覽器獲取和設定innerText
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//跨瀏覽器獲取innerText function getInnerText(element){ return (typeof element.textContent == 'string') ? element.textContent : element.innerText; } //跨瀏覽器設定innerText function setInnerText(element,text){ if(typeof element.textContent == 'string'){ element.textContent = text; }else{ element.innerText = text; } } |
oninput,onpropertychange,onchange的用法
onchange
觸發事件必須滿足兩個條件:
1 2 3 |
a)當前物件屬性改變,並且是由鍵盤或滑鼠事件激發的(指令碼觸發無效) b)當前物件失去焦點(onblur); |
onpropertychange
的話,只要當前物件屬性發生改變,都會觸發事件,但是它是IE專屬的;
1 |
oninput是onpropertychange的非IE瀏覽器版本,支援firefox和opera等瀏覽器,但有一點不同,它繫結於物件時,並非該物件所有屬性改變都能觸發事件,它只在物件value值發生改變時奏效。 |
訪問XMLHTTPRequest物件
1 2 3 4 5 6 7 |
<script type="text/javascript"> if(window.XMLHttpRequest){ xhr = new XMLHttpRequest();//非IE }else if(window.ActiveXObject){ xhr = new ActiveXObject("Microsoft.XMLHttp");//IE } </script> |
禁止選取網頁內容
1 2 3 4 5 6 |
問題: FF需要用CSS禁止,IE用JS禁止 解決方法: IE: obj.onselectstart = function() {return false;} FF: -moz-user-select:none; |
三大不冒泡事件
所有瀏覽器的focus/blur
事件都不冒泡,萬幸的是大部分瀏覽器支援focusin/focusout
事件,不過可惡的firefox
連這個都不支援。
1 2 |
IE6、7、8下 submit事件不冒泡。 IE6、7、8下 change事件要等到blur時才觸發。 |
萬惡的滾輪事件
滾輪事件的支援可謂是亂七八糟,規律如下:
1 2 3 4 5 6 7 8 9 |
IE6-11 chrome mousewheel wheelDetla 下 -120 上 120 firefox DOMMouseScroll detail 下3 上-3 firefox wheel detlaY 下3 上-3 IE9-11 wheel deltaY 下40 上-40 chrome wheel deltaY 下100 上-100 |
關於滑鼠滾輪事件,IE支援mousewheel
,火狐支援DOMMouseScroll
。
判斷滑鼠滾輪是向上還是向下,IE
是通過wheelDelta
屬性,而火狐是通過detail
屬性
事件委託方法
1 2 3 |
//事件委託方法 IE:document.body.onload = inject; //Function inject()在這之前已被實現 FF:document.body.onload = inject(); |
HTML5 的瀏覽器支援情況
查詢操作
查詢通過指的是通過一些特徵字串來找到一組元素,或者判斷元素是不是滿足字串。
- IE6/7不區分id和nam在IE6/7下使用getElementById和getElementsByName時會同時返回id或name與給定值相同的元素。由於name通常由後端約定,因此我們在寫JS時,應保證id不與name重複。
- IE6/7不支援getElementsByClassName和querySelectorAll 這兩個函式從IE8開始支援的,因此在IE6/7下,我們實際可以用的只有getElementByTagName。
- IE6/7不支援getElementsByTagName(‘*’)會返回非元素節點 要麼不用*,要麼自己寫個函式過濾一下。
- IE8下querySelectorAll對屬性選擇器不友好 幾乎所有瀏覽器預定義的屬性都有了問題,儘量使用自定義屬性或者不用屬性選擇器。
- IE8下querySelectorAll不支援偽類 有時候偽類是很好用,IE8並不支援,jquery提供的:first、:last、:even、:odd、:eq、:nth、:lt、:gt並不是偽類,我們在任何時間都不要使用它們。
- IE9的matches函式不能處理不在DOM樹上的元素只要元素不在dom樹上,一定會返回false,實在不行把元素丟在body裡面匹配完了再刪掉吧,當然了我們也可以自己寫匹配函式以避免迴流。
資料參考:
http://w3help.org/zh-cn/kb/,
http://www.zhihu.com/question/29072028