JavaScript操作DOM的那些坑
js在操作DOM中存在著許多跨瀏覽器方面的坑,本文花了我將近一周的時間整理,我將根據(jù)實例整理那些大大小小的“坑”。
DOM的工作模式是:先加載文檔的靜態(tài)內(nèi)容、再以動態(tài)方式對它們進行刷新,動態(tài)刷新不影響文檔的靜態(tài)內(nèi)容。
PS:IE 中的所有 DOM 對象都是以 COM 對象的形式實現(xiàn)的,這意味著 IE 中的 DOM可能會和其他瀏覽器有一定的差異。
Node 接口
firstChild 相當(dāng)于 childNodes[0];lastChild 相當(dāng)于childNodes[box.childNodes.length - 1]。
nodeType返回結(jié)點的類型
--元素結(jié)點返回1
--屬性結(jié)點返回2
--文本結(jié)點返回3
innerHTML 和 nodeValue
對于文本節(jié)點,nodeValue 屬性包含文本。
對于屬性節(jié)點,nodeValue 屬性包含屬性值。
nodeValue 屬性對于文檔節(jié)點和元素節(jié)點是不可用的。
兩者區(qū)別
- box.childNodes[0].nodeValue = '<strong>abc</strong>';//結(jié)果為:<strong>abc</strong>
- abcbox.innerHTML = '<strong>abc</strong>';//結(jié)果為:abc
nodeName屬性獲得結(jié)點名稱
--對于元素結(jié)點返回的是標記名稱,如:<a herf><a>返回的是"a"
--對于屬性結(jié)點返回的是屬性名稱,如:class="test" 返回的是test
--對于文本結(jié)點返回的是文本的內(nèi)容
tagName
document.getElementByTagName(tagName):返回一個數(shù)組,包含對這些結(jié)點的引用
getElementsByTagName()方法將返回一個對象數(shù)組 HTMLCollection(NodeList),這個數(shù)組保存著所有相同元素名的節(jié)點列表。
- document.getElementsByTagName('*');//獲取所有元素
PS:IE 瀏覽器在使用通配符的時候,會把文檔最開始的 html 的規(guī)范聲明當(dāng)作第一個元素節(jié)點。
- document.getElementsByTagName('li');//獲取所有 li 元素,返回數(shù)組
- document.getElementsByTagName('li')[0];//獲取第一個 li 元素,HTMLLIElement
- document.getElementsByTagName('li').item(0);//獲取第一個 li 元素,HTMLLIElement
- document.getElementsByTagName('li').length;//獲取所有 li 元素的數(shù)目
節(jié)點的絕對引用:
返回文檔的根節(jié)點:document.documentElement
返回當(dāng)前文檔中被擊活的標簽節(jié)點:document.activeElement
返回鼠標移出的源節(jié)點:event.fromElement
返回鼠標移入的源節(jié)點:event.toElement
返回激活事件的源節(jié)點:event.srcElement
節(jié)點的相對引用:(設(shè)當(dāng)前對節(jié)點為node)
返回父節(jié)點:node.parentNode || node.parentElement(IE)
返回子節(jié)點集合(包含文本節(jié)點及標簽節(jié)點):node.childNodes
返回子標簽節(jié)點集合:node.children
返回子文本節(jié)點集合:node.textNodes
返回第一個子節(jié)點:node.firstChild
返回最后一個子節(jié)點:node.lastChild
返回同屬下一個節(jié)點:node.nextSibling
返回同屬上一個節(jié)點:node.previousSibling
節(jié)點信息
是否包含某節(jié)點:node.contains()
是否有子節(jié)點node.hasChildNodes()
創(chuàng)建新節(jié)點
createDocumentFragment()--創(chuàng)建文檔碎片節(jié)點
createElement(tagname)--創(chuàng)建標簽名為tagname的元素
createTextNode(text)--創(chuàng)建包含文本text的文本節(jié)點
獲取鼠標點擊事件的位置
以下所描述的屬性在chrome和Safari 都很給力的支持了。
問題一:Firefox,Chrome、Safari和IE9都是通過非標準事件的pageX和pageY屬性來獲取web頁面的鼠標位置的。pageX/Y獲取到的是觸發(fā)點相對文檔區(qū)域左上角距離,以頁面為參考點,不隨滑動條移動而變化
問題二:在IE 中,event 對象有 x, y 屬性(事件發(fā)生的位置的 x 坐標和 y 坐標)火狐中沒有。在火狐中,與event.x 等效的是event.pageX。event.clientX 與 event.pageX 有微妙的差別(當(dāng)整個頁面有滾動條的時候),不過大多數(shù)時候是等效的。
offsetX:IE特有,chrome也支持。鼠標相比較于觸發(fā)事件的元素的位置,以元素盒子模型的內(nèi)容區(qū)域的左上角為參考點,如果有boder,可能出現(xiàn)負值
問題三:
scrollTop為滾動條向下移動的距離,所有瀏覽器都支持document.documentElement。
其余參照:http://segmentfault.com/a/1190000002559158#articleHeader11
參照表
(+為支持,-為不支持):
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:
你會發(fā)現(xiàn)offsetX在Firefox下是undefined,在chrome和IE則會正常顯示。
https://jsfiddle.net/f4am208m/embedded/result/
offsetLeft和style.left區(qū)別
1.style.left返回的是字符串,比如10px。而offsetLeft返回的是數(shù)值,比如數(shù)值10
2.style.left是可讀寫的,offsetLeft是只讀的
3.style.left的值需要事先定義(在樣式表中定義無效,只能取到在html中定義的值),否則取到的值是空的
getComputedStyle與currentStyle
getComputedStyle()接受兩個參數(shù):要取得計算樣式的元素和一個偽元素,如果不需要偽元素,則可以是null。然而,在IE中,并不支持getComputedStyle,IE提供了currentStyle屬性。
getComputedStyle(obj , false ) 是支持 w3c (FF12、chrome 14、safari):在FF新版本中只需要第一個參數(shù),即操作對象,第二個參數(shù)寫“false”也是大家通用的寫法,目的是為了兼容老版本的火狐瀏覽器。
缺點:在標準瀏覽器中正常,但在IE6/7/8中不支持
- window.onload=function(){
- var oBtn=document.getElementById('btn');
- var oDiv=document.getElementById('div1');
- oBtn.onclick=function(){
- //alert(oDiv.style.width); //寫在樣式表里無法讀取,只能得到寫在行內(nèi)的
- //alert(getComputedStyle(oDiv).width); //適用于標準瀏覽器 IE6、7、8不識別
- //alert(oDiv.currentStyle.width); //適用于IE瀏覽器,標準瀏覽器不識別
- if(oDiv.currentStyle){
- alert(oDiv.currentStyle.width);
- }else{
- alert(getComputedStyle(oDiv).width);
- }
- };
- };
取消表單提交
- <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的窗口對象屬性,它返回窗口的視口區(qū)域,減去任何滾動條的大小。IE不支持innerWidth 和 innerHeight
- <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 方案(涵蓋所有瀏覽器):
- var w=window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
- var h=window.innerHeight || document.documentElement.clientHeight|| document.body.clientHeight;
對于 IE 6、7、8的方案如下:
- document.documentElement.clientHeight
- document.documentElement.clientWidth
或者
- document.body.clientHeight
- document.body.clientWidth
Document對象的body屬性對應(yīng)HTML文檔的<body>標簽。Document對象的documentElement屬性則表示 HTML文檔的根節(jié)點。
attributes 屬性
attributes 屬性返回該節(jié)點的屬性節(jié)點集合。
- document.getElementById('box').attributes//NamedNodeMap
- document.getElementById('box').attributes.length;//返回屬性節(jié)點個數(shù)
- document.getElementById('box').attributes[0]; //Attr,返回最后一個屬性節(jié)點
- document.getElementById('box').attributes[0].nodeType; //2,節(jié)點類型
- document.getElementById('box').attributes[0].nodeValue; //屬性值
- document.getElementById('box').attributes['id']; //Attr,返回屬性為 id 的節(jié)點
- document.getElementById('box').attributes.getNamedItem('id'); //Attr
setAttribute 和 getAttribute
在IE中是不認識class屬性的,需改為className屬性,同樣,在Firefox中,也是不認識className屬性的,F(xiàn)irefox只認識class屬性,所以通常做法如下:
- element.setAttribute(class, value); //for firefox
- element.setAttribute(className, value); //for IE
IE:可以使用獲取常規(guī)屬性的方法來獲取自定義屬性,也可以使用getAttribute()獲取自定義屬性
Firefox:只能使用getAttribute()獲取自定義屬性.
解決方法:統(tǒng)一通過getAttribute()獲取自定義屬性
- 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()方法設(shè)置 class 和 style 屬性是沒有效果的,雖然 IE8 解決了這個bug,但還是不建議使用。
removeAttribute()方法
removeAttribute()可以移除 HTML 屬性。
document.getElementById('box').removeAttribute('style');//移除屬性
PS:IE6 及更低版本不支持 removeAttribute()方法。
跨瀏覽器事件Event對象
- <!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 | 設(shè)置或獲取拖曳操作的類型和要顯示的光標類型 |
| effectAllowed | 設(shè)置或獲取數(shù)據(jù)傳送操作可應(yīng)用于該對象的源元素 |
| 方法 | 描述 |
| ————- |:————-:|
| clearData | 通過 dataTransfer 或 clipboardData 對象從剪貼板刪除一種或多種數(shù)據(jù)格式 |
| getData | 通過 dataTransfer 或 clipboardData 對象從剪貼板獲取指定格式的數(shù)據(jù)
| setData | 以指定格式給 dataTransfer 或 clipboardData 對象賦予數(shù)據(jù)
HTML5拖拽的瀏覽器支持
Internet Explorer 9、Firefox、Opera 12、Chrome 以及 Safari 5 支持拖放
為了使元素可拖動,需把 draggable 屬性設(shè)置為 true :
- <img draggable="true" />
| 事件 | 描述 |
| ————- |:————-:|
| dragstart | 拖拽事件開始 |
| drag | 在拖動操作上 |
| dragenter | 拖動到目標上,用來決定目標是否接受放置
|dragover | 拖動到目標上,用來決定給用戶的反饋
|drop | 放置發(fā)生
| dragleave| 拖動離開目標
|dragend | 拖動操作結(jié)束
上述代碼的一些瀏覽器兼容性:
1.為了兼容IE,我們將`window.event`賦給 `evt`,其他瀏覽器則會正確將接收到的`event`對象賦給`evt`。
2.w3c使用addEventListener來為事件元素添加事件監(jiān)聽器,而IE則使用attachEvent。addEventListener為事件冒泡到的當(dāng)前對象,而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
跨瀏覽器獲取目標對象
- //跨瀏覽器獲取目標對象
- function getTarget(ev){
- if(ev.target){//w3c
- return ev.target;
- }else if(window.event.srcElement){//IE
- return window.event.srcElement;
- }
- }
對于獲取觸發(fā)事件的對象,w3c和IE也有不同的做法:
- event.target;//w3c
- event.srcElement;//IE
我們可以使用三目運算符來兼容他們:
- obj = event.srcElement ? event.srcElement : event.target;
innerText的問題
innerText在IE中能正常工作,但是innerText在FireFox中卻不行。
- <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>
跨瀏覽器獲取和設(shè)置innerText
- //跨瀏覽器獲取innerText
- function getInnerText(element){
- return (typeof element.textContent == 'string') ? element.textContent : element.innerText;
- }
- //跨瀏覽器設(shè)置innerText
- function setInnerText(element,text){
- if(typeof element.textContent == 'string'){
- element.textContent = text;
- }else{
- element.innerText = text;
- }
- }
oninput,onpropertychange,onchange的用法
onchange觸發(fā)事件必須滿足兩個條件:
a)當(dāng)前對象屬性改變,并且是由鍵盤或鼠標事件激發(fā)的(腳本觸發(fā)無效)
b)當(dāng)前對象失去焦點(onblur);
onpropertychange的話,只要當(dāng)前對象屬性發(fā)生改變,都會觸發(fā)事件,但是它是IE專屬的;
oninput是onpropertychange的非IE瀏覽器版本,支持firefox和opera等瀏覽器,但有一點不同,它綁定于對象時,并非該對象所有屬性改變都能觸發(fā)事件,它只在對象value值發(fā)生改變時奏效。
訪問XMLHTTPRequest對象
- <script type="text/javascript">
- if(window.XMLHttpRequest){
- xhr = new XMLHttpRequest();//非IE
- }else if(window.ActiveXObject){
- xhr = new ActiveXObject("Microsoft.XMLHttp");//IE
- }
- </script>
禁止選取網(wǎng)頁內(nèi)容
問題:
FF需要用CSS禁止,IE用JS禁止
解決方法:
IE: obj.onselectstart = function() {return false;}
FF: -moz-user-select:none;
三大不冒泡事件
所有瀏覽器的focus/blur事件都不冒泡,萬幸的是大部分瀏覽器支持focusin/focusout事件,不過可惡的firefox連這個都不支持。
IE6、7、8下 submit事件不冒泡。
IE6、7、8下 change事件要等到blur時才觸發(fā)。
萬惡的滾輪事件
滾輪事件的支持可謂是亂七八糟,規(guī)律如下:
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
關(guān)于鼠標滾輪事件,IE支持mousewheel,火狐支持DOMMouseScroll。
判斷鼠標滾輪是向上還是向下,IE是通過wheelDelta屬性,而火狐是通過detail屬性
事件委托方法
- //事件委托方法
- IE:document.body.onload = inject; //Function inject()在這之前已被實現(xiàn)
- FF:document.body.onload = inject();
HTML5 的瀏覽器支持情況
來源地址:http://fmbip.com/litmus/
查詢操作
查詢通過指的是通過一些特征字符串來找到一組元素,或者判斷元素是不是滿足字符串。
- IE6/7不區(qū)分id和nam在IE6/7下使用getElementById和getElementsByName時會同時返回id或name與給定值相同的元素。由于name通常由后端約定,因此我們在寫JS時,應(yīng)保證id不與name重復(fù)。
- IE6/7不支持getElementsByClassName和querySelectorAll 這兩個函數(shù)從IE8開始支持的,因此在IE6/7下,我們實際可以用的只有g(shù)etElementByTagName。
- IE6/7不支持getElementsByTagName(‘*’)會返回非元素節(jié)點 要么不用*,要么自己寫個函數(shù)過濾一下。
- IE8下querySelectorAll對屬性選擇器不友好 幾乎所有瀏覽器預(yù)定義的屬性都有了問題,盡量使用自定義屬性或者不用屬性選擇器。
- IE8下querySelectorAll不支持偽類 有時候偽類是很好用,IE8并不支持,jquery提供的:first、:last、:even、:odd、:eq、:nth、:lt、:gt并不是偽類,我們在任何時間都不要使用它們。
- IE9的matches函數(shù)不能處理不在DOM樹上的元素只要元素不在dom樹上,一定會返回false,實在不行把元素丟在body里面匹配完了再刪掉吧,當(dāng)然了我們也可以自己寫匹配函數(shù)以避免回流。
資料參考:
http://w3help.org/zh-cn/kb/,
http://www.zhihu.com/question/29072028