使用JavaScript操纵DOM,必须等待DOM加载完毕才可以执行代码,但window.onload有个坏处,它非要等到页面中的所有图片及视频加载完毕才会触发load事件。结果就是一些本来应该在打开时隐藏起来的元素,由于网络延迟,在页面打开时仍然会出现,然后又会突然消失,让用户觉得莫名其妙。我们想做的就是寻找一种方法来确定DOM被完全的加载时不用等待所有那些讨厌的图片加载完毕。必须与这种丑陋的闪烁告别!
我这里整理出针对onload事件的七种方案。
第七种方案是我们最终的解决方案,也是完美的解决方案。
三 ~ 六 的解决方案只解决了window.onload加载多个方法,但是还没有解决图片的等待加载问题,还有它们只兼容IE和FF。
最成熟的解决:http://javascript.nwbox.com/ContentLoaded/
定义和用法
onload 事件会在页面或图像加载完成后立即发生。
语法
onload=”SomeJavaScriptCode”
支持该事件的 HTML 标签:
<body>, <frame>, <frameset>, <iframe>, <img>, <link>, <script>
支持该事件的 JavaScript 对象:
image, layer, window实例
第一种:
function loadFunction(){ alert("hello!"); } <body onload="loadFunction()">
第二种:
window.onload = loadFunction; function loadFunction(){ alert("hello!"); }
第三种:
function firstFunction(){ alert("hello firstFun !"); } function secondFunction(){ alert("hello secondFun !"); } window.onload = function(){ firstFunction(); secondFunction(); }
第四种:通用的做法
function firstFunction() { alert("hello firstFun !"); } function secondFunction() { alert("hello secondFun !"); } function addLoadEvent(func) { var oldonload = window.onload; if (typeof window.onload != 'function') { window.onload = func; } else { window.onload = function () { oldonload(); func(); } } } //测试 addLoadEvent(firstFunction); addLoadEvent(secondFunction);
addLoadEvent工作流程:
把现有的window.onload事件处理函数的值存入变量oldonload。
如果在这个处理函数上还没有绑定任何函数,就像平时那样把新函数添加给它;
如果在这个处理函数已经绑定了一些函数,就把函数追回到现有指令未尾。
浏览器加载html内容是自上而下的(默认),而JS一般是在哪里引入——想想如果JS里面包含了一些即时执行指令,
它会操作根本不存在元素节点(因为还没有加载完)会有什么后果?结果就是出错。
addLoadEvent可以实现无论有多少个函数,都能让它们同时和window.onload事件绑定。
第五种; 推荐
function a(){ alert("a"); } function b(){ alert("b"); } function addEvent(obj,EventName,callBack){//给对象添加事件 if(obj.addEventListener){ //FF obj.addEventListener(EventName,callBack,false); }else if(obj.attachEvent){//IE obj.attachEvent('on'+EventName,callBack); }else{ obj["on"+EventName]=callBack; } } //测试 addEvent(window,"load",a); addEvent(window,"load",b);
第六种:推荐
function a(){ alert("a"); } function b(){ alert("b"); } // Please note: this file contains snippets for comparison // it is not self-contained or ready-to-use code as such function addLoadListener(fn) { if (typeof window.addEventListener != 'undefined') { window.addEventListener('load', fn, false); } else if (typeof document.addEventListener != 'undefined') { document.addEventListener('load', fn, false); } else if (typeof window.attachEvent != 'undefined') { window.attachEvent('onload', fn); } else { var oldfn = window.onload; if (typeof window.onload != 'function') { window.onload = fn; } else { window.onload = function() { oldfn(); fn(); }; } } } //测试 addLoadListener(a); addLoadListener(b);
第七种:最完美的解决方案
建立一个独立的通用解决方案,兼容各种浏览器,任何人都可以使用,而无需一个具体的框架。
最初的完整解决方案:http://dean.edwards.name/weblog/2006/06/again/
一个独立的通用解决方案 :http://www.thefutureoftheweb.com/blog/adddomloadevent.
/* * (c)2006 Jesse Skinner/Dean Edwards/Matthias Miller/John Resig * Special thanks to Dan Webb's domready.js Prototype extension * and Simon Willison's addLoadEvent * * For more info, see: * http://www.thefutureoftheweb.com/blog/adddomloadevent * http://dean.edwards.name/weblog/2006/06/again/ * http://www.vivabit.com/bollocks/2006/06/21/a-dom-ready-extension-for-prototype * http://simon.incutio.com/archive/2004/05/26/addLoadEvent * * * To use: call addDOMLoadEvent one or more times with functions, ie: * addDOMLoadEvent的调用方法,如下: * function something() { * // do something * } * addDOMLoadEvent(something); * * addDOMLoadEvent(function() { * // do other stuff * }); * */ addDOMLoadEvent = (function(){ // create event function stack var load_events = [], load_timer, script, done, exec, old_onload, init = function () { done = true; /*//停止调用计时器*/ // kill the timer clearInterval(load_timer); // execute each function in the stack in the order they were added while (exec = load_events.shift()) exec(); if (script) script.onreadystatechange = ''; }; return function (func) { // if the init function was already ran, just run this function now and stop if (done) return func(); if (!load_events[0]) { // for Mozilla/Opera9 /* DOMContentLoaded是firefox下特有的Event, 当所有DOM解析完以后会触发这个事件。 注册DOMContentLoaded事件,如果支持的话 */ if (document.addEventListener) document.addEventListener("DOMContentLoaded", init, false); // for Internet Explorer /* 对于IE则使用条件注释,并使用script标签的defer属性 IE中可以给script标签添加一个defer(延迟)属性,这样,标签中的脚本只有当DOM加载完毕后才执行*/ /*@cc_on @*/ /*@if (@_win32) document.write("<script id=__ie_onload defer src=//0><\/scr"+"ipt>"); script = document.getElementById("__ie_onload"); script.onreadystatechange = function() { if (this.readyState == "complete") init(); // call the onload handler }; /*@end @*/ // for Safari /* 但对于Safari,我们需要使用setInterval方法不断检测document.readyState 当为loaded或complete的时候表明DOM已经加载完毕 */ if (/WebKit/i.test(navigator.userAgent)) { // sniff load_timer = setInterval(function() { if (/loaded|complete/.test(document.readyState)) init(); // call the onload handler }, 10); } // for other browsers set the window.onload, but also execute the old window.onload old_onload = window.onload; window.onload = function() { init(); if (old_onload) old_onload(); }; } load_events.push(func); } })();
方案七的演示:
http://blog.moocss.com/Project/JavaScript/demo4/addDOMLoadEvent-Demo-Page.htm
addDOMLoadEvent 原始文件:http://blog.moocss.com/Project/JavaScript/demo4/adddomloadevent-compressed.js
addDOMLoadEvent 压缩版文件:http://blog.moocss.com/Project/JavaScript/demo4/adddomloadevent-compressed.js
最成熟的解决方案:
第七种方案回头看,源码中的 hacks 用的太多,也并不完美,接下来,就看一个大家都在用的:
/*! * contentloaded.js * * Author: Diego Perini (diego.perini at gmail.com) * Summary: cross-browser wrapper for DOMContentLoaded * Updated: 20101020 * License: MIT * Version: 1.2 * * URL: * http://javascript.nwbox.com/ContentLoaded/ * http://javascript.nwbox.com/ContentLoaded/MIT-LICENSE * */ // @win window reference // @fn function reference (function(win, doc) { contentLoaded=function(fn) { var done = false, top = true, doc = win.document, root = doc.documentElement, add = doc.addEventListener ? 'addEventListener' : 'attachEvent', rem = doc.addEventListener ? 'removeEventListener' : 'detachEvent', pre = doc.addEventListener ? '' : 'on', init = function(e) { if (e.type == 'readystatechange' && doc.readyState != 'complete') return; (e.type == 'load' ? win : doc)[rem](pre + e.type, init, false); if (!done && (done = true)) fn.call(win, e.type || e); }, poll = function() { try { root.doScroll('left'); } catch(e) { setTimeout(poll, 50); return; } init('poll'); }; if (doc.readyState == 'complete') fn.call(win, 'lazy'); else { if (doc.createEventObject && root.doScroll) { try { top = !win.frameElement; } catch(e) { } if (top) poll(); } doc[add](pre + 'DOMContentLoaded', init, false); doc[add](pre + 'readystatechange', init, false); win[add](pre + 'load', init, false); } } })(window, document); <pre> <h3>使用方法:</h3> <pre lang="javascript" line="1"> //测试 var $tt = (new Date).getTime(); function timeElapsed(t) { return ((new Date()).getTime() - t); } contentLoaded( function (e) { document.body.style.backgroundColor = 'green'; window.status = window.defaultStatus = ' * ' + (e.type || e) + ' ' + ' - ' + (e.eventType ? e.eventType : 'native') + ' in ' + timeElapsed($tt) + ' ms.'; } ); //测试 contentLoaded( function(){ var el=document.getElementById('logo'); console.log(el); } );