首页WEB开发Ajax教程 → 跨浏览器的设置innerHTML方法

跨浏览器的设置innerHTML方法

日期:2007-4-15 15:30:42 出处:Ajax Wing 作者:kenxu 人气:

定义

  • 代码:这里说的代码是指要插入到 innerHTML 中的 html 代码,不是 JavaScript 代码
  • 脚本:代码中包含的 JavaScript 脚本,也就是 html 代码中的 <script> 元素
  • 本地脚本:直接写在 html 代码中的 JavaScript 脚本元素,例如:
    <script language="javascript">alert("Hello World");<script>
  • 外部脚本:连接到其它 JavaScript 文件的脚本元素
    <script language="javascript" src="helloworld.js"><script>
  • 样式:代码中包含的 CSS 样式,也就是 html 代码中的 <style> 元素
  • 问题

    很多人都可能遇到过这种情况:使用 innerHTML 来插入 html 代码,如果代码中包含脚本或者样式,这些脚本和样式就会不生效,或者在 IE 上生效在其它浏览器上不生效。原因很简单:不同浏览器对插入 innerHTML 中的脚本和样式有不同的处理方法。

    分析

  • 对于 IE,当用 innerHTML 的方式加载的时候,它会立刻执行所有本地脚本,而外部脚本会在后台分别加载。还有,如果 style 或者 script 元素之前没有可显示元素,IE 会滤掉这些元素。
  • 对于 Firefox,所有脚本都放在后台执行,html 代码加载过程不会等待脚本的加载,立刻返回。
  • 对于 Opera,所有脚本按顺序,并且在 html 代码加载时执行。
  • 其次,如果加载的脚本中包含 document.write,会破坏原页面。
  • 针对上面分析,这里给出两个版本的解决方法,它们各有优缺点。

    复杂版

    复杂版会严格控制代码中各个脚本的加载顺序,并且处理了脚本中包含 document.write 的问题。代码如下:

    /* ------------------------------------------------------------------

        Cross-browser set innerHTML method.
        The HTML code to be inserted can contains script and style tag.

        Tested browsers:
            IE 5.5+
            Firefox 1.0+
            Opera 8.5+

        Author: kenxu <
    ken@ajaxwing.com>
        Version: 0.1.5
        Date: 2006-08-04

        Usage:
            setInnerHTML(element, htmlCode);

        For more informations, visit:
           
    http://www.ajaxwing.com/index.php?id=3

       ------------------------------------------------------------------ */


    var setInnerHTML = (function () {
    var element_stack = [];
    var input_stack = [];
    var html_stack = [];
    var timer = null;
    var ua = navigator.userAgent.toLowerCase();
    var isIE = (ua.indexOf('msie') >= 0 && ua.indexOf('opera') < 0);
    var old_document_write = document.write;
    var old_document_writeln = document.writeln;
    var loding_script = false;

    var callback = function () {
       
    if (loding_script) {
           
    return;
       
    }
       
    if (element_stack.length == 0) {
           
    clearInterval(timer);
           
    timer = null;
           
    document.write = old_document_write;
           
    document.writeln = old_document_writeln;
           
    return;
       
    }
       
    var index = element_stack.length - 1;
       
    var input = input_stack[index];
       
    if (input.length == 0) {
           
    input_stack.pop();
           
    var element = element_stack.pop();
           
    var html = html_stack.pop();
           
    element.innerHTML = '';
           
    if (typeof beforeInsert == 'function') {
               
    html = beforeInsert(html);
           
    }
           
    if (html.match(/<script([^>]*>)((.|r|n)*?)</script>/i) != null) {
                setInnerHTML(element, html);
                return;
            }
            if (isIE) {
                html = '<div style="display:none">for IE<
    /div>' + html;
                element.innerHTML = html;
                element.removeChild(element.firstChild);
            } else {
                element.innerHTML = html;
            }
            return;
        }
        var item = input[input.length - 1];
        if (typeof item ==
    'string') {
            html_stack[index] += item;
            input.pop();
        } else if (typeof item ==
    'object') {
            if (item.src) {
                loding_script = true;
                var script = document.createElement(
    'script');
                script.src = item.src;
                script.__index = index;
                if (isIE) {
                    script.onreadystatechange = script_loaded;
                } else {
                    script.onload = script_loaded;
                }
                var head = document.getElementsByTagName(
    'head')[0];
                head.appendChild(script);
            }
            if (item.text) {
                var script = document.createElement(
    'script');
                script.text = item.text;
                var head = document.getElementsByTagName(
    'head')[0];
                head.appendChild(script);
                input.pop();
            }
        } else {
            input.pop();
        }
    }

    var script_loaded = function () {
        if (isIE && this.readyState.toLowerCase() != "loaded" && this.readyState.toLowerCase() != "complete") {
            return;
        }
        var index = this.__index;
        input_stack[index].pop();
        loding_script = false;
    }

    var new_document_write = function() {
        for (var i = 0; i < arguments.length; i++) {
            html_stack[element_stack.length - 1] += arguments[i];
        }
    }

    var new_document_writeln = function () {
        for (var i = 0; i < arguments.length; i++) {
            new_document_write(arguments[i] + "n");
        }
    }

    return function (element, htmlCode) {
        element_stack.push(element);
        html_stack.push(
    '');
        var input = [];
        while (true) {
            if ((m = htmlCode.match(/<script([^>]*>)((.|r|n)*?)</script>/i)) == null) {
                break;
            }
            input.unshift(htmlCode.substr(0, m.index));
            htmlCode = htmlCode.substr(m.index + m[0].length);
            if ((m2 = m[1].match(/srcs*=s*([
    '"]?)([^'">s]*)1/i)) != null) {
                input.unshift({src:m2[2]});
            } else {
                input.unshift({text:m[2]});
            }
        }
        input.unshift(htmlCode);
        input_stack.push(input);
        if (timer == null) {
            document.write = new_document_write;
            document.writeln = new_document_writeln;
            timer = setInterval(callback, 10);
        }
    }})();

    如果你想在最终插入之前修改一下代码,可以定义一个名为 beforeInsert 的函数,例如:

    function beforeInsert (html) {
       
    // 在这里修改 html
       
    return html;
    }

    setInnerHTML 会先执行 beforeInsert,然后才插入代码。在你并不知道最终要插入的代码是什么的情况下,beforeInsert 函数是非常有用的。

    复杂版的缺点有两个:第一,代码中的脚本不能访问到代码中的其它 html 元素;第二,复杂版的执行速度和兼容性不如简单版。

    简单版

    如果脚本中不包含 document.write,则不用保证脚本加载顺序的问题,因为各个浏览器有其自身的加载逻辑,只需确保脚本和样式都生效就可以了。代码如下:

    /*
    * 描述:跨浏览器的设置 innerHTML 方法
    *       允许插入的 HTML 代码中包含 script 和 style
    * 作者:kenxu <
    ken@ajaxwing.com>
    * 日期:2006-03-23
    * 参数:
    *    el: 合法的 DOM 树中的节点
    *    htmlCode: 合法的 HTML 代码
    * 经测试的浏览器:ie5+, firefox1.5+, opera8.5+
    */

    var setInnerHTML = function (el, htmlCode) {
       
    var ua = navigator.userAgent.toLowerCase();
       
    if (ua.indexOf('msie') >= 0 && ua.indexOf('opera') < 0) {
           
    htmlCode = '<div style="display:none">for IE</div>' + htmlCode;
           
    htmlCode = htmlCode.replace(/<script([^>]*)>/gi,
                                       
    '<script$1 defer>');
           
    el.innerHTML = htmlCode;
           
    el.removeChild(el.firstChild);
       
    } else {
           
    var el_next = el.nextSibling;
           
    var el_parent = el.parentNode;
           
    el_parent.removeChild(el);
           
    el.innerHTML = htmlCode;
           
    if (el_next) {
               
    el_parent.insertBefore(el, el_next)
           
    } else {
               
    el_parent.appendChild(el);
           
    }
       
    }
    }

    简单版充分利用了浏览器自身的特性,执行效率高,兼容性好。唯一的缺点就是脚本中不能包含 document.write。

    应用场合

    复杂版非常适合用来插入广告代码。广告代码一般使用 document.write 来直接在页面中插入 html,而 document.write 是阻塞的,也就是说浏览器会等待广告代码执行完毕才显示下面的内容,如果广告的执行速度比较慢或者你的页面中放置了多个广告,那么整个页面的显示时间就会变得很慢,但实际上浏览器很早就已经拿到整个页面了。使用 setInnerHTML 就可以加快整个页面的显示速度,因为浏览器不必等待每一个广告代码执行完毕。

    别看简单版只有 20 行代码,但她是非常强大的,保证代码按照各个浏览器自身的逻辑加载。也就是说你也可以插入任意 html 代码,只要脚本中不包含 document.write。

    其它解决方法

    在这篇文章编写之前,已经有一个比较完善的解决方法《让插入到 innerHTML 中的 script 跑起来》。如果你发现这里提供的解决方法不能满足你的要求,可以先试试它。

    下载和测试

    下载地址(复杂版)

    测试地址(复杂版)

    测试地址(简单版)

    关于本站 | 帮 助 | 广告服务 | 版权声明 | 业务合作 | 捐助本站 | 软件发布 | 联系我们
    77资源下载 www.77zy.com ©2007-2008 版权所有
    备案编号:赣ICP备07002641号  QQ:674648476