スクリプトをinnerHTMLで挿入できますか?


223

でを使用innerHTMLして、いくつかのスクリプトをページにロードしようとしました<div>。スクリプトはDOMに読み込まれるようですが、実行されません(少なくともFirefoxとChromeでは)。でスクリプトを挿入するときにスクリプトを実行する方法はありinnerHTMLますか?

サンプルコード:

<!DOCTYPE html>
<html>
  <body onload="document.getElementById('loader').innerHTML = '<script>alert(\'hi\')<\/script>'">
    Shouldn't an alert saying 'hi' appear?
    <div id="loader"></div>
  </body>
</html>

回答:


88

DOMテキストとして挿入したスクリプトコードを実行するには、eval()を使用する必要があります。

MooToolsが自動的にこれを行い、jQueryも同様に実行すると確信しています(バージョンによっては、jQueryバージョン1.6以降ではeval)。これにより、<script>タグを解析してコンテンツをエスケープする手間が大幅に削減されるだけでなく、他の多くの「落とし穴」も解消されます。

通常、自分でアクセスする場合はeval()、などのHTMLマークアップなしでスクリプトコードを作成/送信します。<script>これらはeval()適切に機能しないためです。


12
私が本当にやりたいことは、ローカルスクリプトを評価するだけでなく、外部スクリプトをロードすることです。innerHTMLを使用してスクリプトタグを追加することは、スクリプトDOM要素を作成して本文に追加するよりもはるかに短いため、コードをできるだけ短くしようとしています。domスクリプト要素を作成し、innerHTMLのようなものを使用するだけでなく、それらをdomに追加する必要がありますか?関数内からdocument.writeでこれを行う方法はありますか?
クレイグ

5
zombatが示唆しているように、Javascriptフレームワークを使用して外部スクリプトをロードし、ホイールを再発明しないでください。JQueryはこれを非常に簡単にします。JQueryをインクルードして、$。getScript(url)を呼び出します。スクリプトが読み込まれると実行されるコールバック関数を提供することもできます。
アリエルポポフスキー2009

2
アリエルは正しいです。私はあなたのコードを短く保つように努めることに感謝し、<script>タグを追加するinnerHTMLことは短いかもしれませんが、それは機能しません。実行されるまでは、すべてプレーンテキストeval()です。そして残念なことに、eval()HTMLタグを解析しないため、一連の問題が発生します。
zombat 2009

21
eval()はどんな問題に対しても優れた解決策ではありません。
buley

2
自分でeval()を試しました。それは恐ろしい考えです。あなたは全体のものにevalに持つたびに。変数の名前と値を宣言したとしても、それを機能させるには、毎回再宣言/再eval()する必要があります。エラーの悪夢です。
Youstay Igo 2015

88

ここにあなたの問題に対する非常に興味深い解決策があります:http : //24ways.org/2005/have-your-dom-and-script-it-too

したがって、スクリプトタグの代わりにこれを使用します。

<img src="empty.gif" onload="alert('test');this.parentNode.removeChild(this);" />


12
これは素晴らしいです!
confile 2013

ajaxリクエストの結果に挿入すると、機能しません。構文エラーが見つかりません。スクリプト文字列の先頭のbeforeステートメント
Oliver

どのようにして&lt; img src ...行をページコードに追加しますか?それをdocument.write()するか、それともdocument.body.innerHTML + =アプローチを使用しますか?私はどちらも失敗しています:(
Youstay Igo

onload属性内に多くのコードを記述するのはあまり実用的ではありません。また、これには追加のファイルが存在し、ロードされる必要があります。momoのソリューションは、妥協点が少ないです。
fregante 2016年

15
あなたはトリガー画像をBase64でエンコードすることができます<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">(これはネットワークリクエストを行いません)実際...画像は必要なく、存在しない画像を参照してonload使用する代わりにonerror(これはネットワークリクエストを行います)
Danny '365CSI'エンゲルマン

83

すべてのスクリプトを実行可能なスクリプトに再帰的に置き換える方法を次に示します。

function nodeScriptReplace(node) {
        if ( nodeScriptIs(node) === true ) {
                node.parentNode.replaceChild( nodeScriptClone(node) , node );
        }
        else {
                var i        = 0;
                var children = node.childNodes;
                while ( i < children.length ) {
                        nodeScriptReplace( children[i++] );
                }
        }

        return node;
}
function nodeScriptIs(node) {
        return node.tagName === 'SCRIPT';
}
function nodeScriptClone(node){
        var script  = document.createElement("script");
        script.text = node.innerHTML;
        for( var i = node.attributes.length-1; i >= 0; i-- ) {
                script.setAttribute( node.attributes[i].name, node.attributes[i].value );
        }
        return script;
}

呼び出し例:

nodeScriptReplace(document.getElementsByTagName("body")[0]);

9
あなたの答えがずっと下にあることを私は少し驚いています。私見、これが最善の解決策です。この方法では、特定のURLまたはコンテンツを含むスクリプトを制限することもできます。
davidmh 14

1
@ inf3rnoはしませんか?以前は機能していましたが、何か違うと主張した人はいますか?
mmm 2015年

[0]の目的は何ですか?nodeScriptReplace(document.getElementById()。html);を使用できますか。
バオタイの

@BaoThaiはい。あなたはできる。
mmm 2017

IWebBrowser2では役に立たないようです。スクリプトタグがcreateElementで再作成されることを確認できますが、それでもInvokeScript()を使用してそれらを呼び出すことができません。
デイブ

46

スクリプトを作成して、コンテンツを挿入できます。

var g = document.createElement('script');
var s = document.getElementsByTagName('script')[0];
g.text = "alert(\"hi\");"
s.parentNode.insertBefore(g, s);

これはすべてのブラウザで機能します:)


1
ドキュメントに他のスクリプト要素がない場合を除きます。document.documentElement代わりに使用してください。
Eli Grey

4
別のスクリプトからスクリプトを記述しているため、必要ありません。 <script> var g = document.createElement('script'); var s = document.getElementsByTagName('script')[0]; //reference this script g.text = "alert(\"hi\");" s.parentNode.insertBefore(g, s); </script>
パブロモレッティ2011

3
別のスクリプトからのものだと誰が言ったのですか?<script>要素なしでJavaScriptを実行できます。例えば<img onerror="..." src="#"><body onload="...">。技術的になりたい場合、これはHTML / SVG以外のドキュメントでも機能しません。これは、名前空間が不明確であるためです。
Eli Grey

2
FacebookはSDKでPabloの回答を使用しています。developers.facebook.com/docs/javascript/quickstart/v2.2#loading
geoyws

30

私はこのコードを使用しました、それはうまくいきます

var arr = MyDiv.getElementsByTagName('script')
for (var n = 0; n < arr.length; n++)
    eval(arr[n].innerHTML)//run script inside div

1
ありがとう。これは、TinyBox2 Jqueryプラグインを使用して作成されたモーダルポップアップにDisqus Universalコードを追加するという私の問題を修正しました。
gsinha 2014

3
残念ながら、このソリューションは、スクリプトに後で呼び出される関数が含まれている場合は機能しません。
ホセ・ゴメス

7

innerHTMLでこの問題が発生しました。Reactjsアプリケーションの「head」タグにHotjarスクリプトを追加する必要があり、追加した直後に実行する必要がありました。

「ヘッド」タグへの動的ノードインポートの優れたソリューションの1つは、React-helmentモジュールです。


また、提案された問題には便利な解決策があります:

innerHTMLにはスクリプトタグがありません!

HTML5では、innerHTMLプロパティを使用してスクリプトタグを動的に追加できないことがわかりました。したがって、以下は実行されず、Hello World!という警告は表示されません。

element.innerHTML = "<script>alert('Hello World!')</script>";

これはHTML5仕様で文書化されています:

注:innerHTMLを使用して挿入されたスクリプト要素は、挿入時に実行されません。

ただし、これはinnerHTMLがクロスサイトスクリプティングから安全であることを意味するわけではありません。MDNのinnerHTMLページに示されているように、タグを使用せずにinnerHTMLを介してJavaScriptを実行することが可能です

解決策:スクリプトを動的に追加する

スクリプトタグを動的に追加するには、新しいスクリプト要素を作成し、それをターゲット要素に追加する必要があります。

これは、外部スクリプトに対して行うことができます。

var newScript = document.createElement("script");
newScript.src = "http://www.example.com/my-script.js";
target.appendChild(newScript);

そしてインラインスクリプト:

var newScript = document.createElement("script");
var inlineScript = document.createTextNode("alert('Hello World!');");
newScript.appendChild(inlineScript); 
target.appendChild(newScript);

5

まだこれを実行しようとしている人にとっては、いいえ、を使用してスクリプトを挿入することはできませんinnerHTMLが、BlobおよびURL.createObjectURLです。

文字列をスクリプトとして実行し、スクリプトの「エクスポート」をpromiseから返すことができる例を作成しました。

function loadScript(scriptContent, moduleId) {
    // create the script tag
    var scriptElement = document.createElement('SCRIPT');

    // create a promise which will resolve to the script's 'exports'
    // (i.e., the value returned by the script)
    var promise = new Promise(function(resolve) {
        scriptElement.onload = function() {
            var exports = window["__loadScript_exports_" + moduleId];
            delete window["__loadScript_exports_" + moduleId];
            resolve(exports);
        }
    });

    // wrap the script contents to expose exports through a special property
    // the promise will access the exports this way
    var wrappedScriptContent =
        "(function() { window['__loadScript_exports_" + moduleId + "'] = " + 
        scriptContent + "})()";

    // create a blob from the wrapped script content
    var scriptBlob = new Blob([wrappedScriptContent], {type: 'text/javascript'});

    // set the id attribute
    scriptElement.id = "__loadScript_module_" + moduleId;

    // set the src attribute to the blob's object url 
    // (this is the part that makes it work)
    scriptElement.src = URL.createObjectURL(scriptBlob);

    // append the script element
    document.body.appendChild(scriptElement);

    // return the promise, which will resolve to the script's exports
    return promise;
}

...

function doTheThing() {
    // no evals
    loadScript('5 + 5').then(function(exports) {
         // should log 10
        console.log(exports)
    });
}

実際の実装からこれを簡略化したので、バグがないという約束はありません。しかし、原理は機能します。

スクリプトの実行後に値を取得する必要がない場合は、さらに簡単です。ちょうどままにPromiseしてonloadビットを。スクリプトをラップしたり、グローバルwindow.__load_script_exports_プロパティを作成したりする必要さえありません。


1
試してみたところ、Chrome 57で動作します。scriptタグのinnerHTMLがテキストを実行します。
iPherian 2017

興味深いことに、以前は機能しませんでした。この動作は、クロスブラウザのみクローム57にあるのだろうか
JayArby

4

これは、広告サーバーで使用する要素のinnerHTMLを設定するための再帰関数です。

// o: container to set the innerHTML
// html: html text to set.
// clear: if true, the container is cleared first (children removed)
function setHTML(o, html, clear) {
    if (clear) o.innerHTML = "";

    // Generate a parseable object with the html:
    var dv = document.createElement("div");
    dv.innerHTML = html;

    // Handle edge case where innerHTML contains no tags, just text:
    if (dv.children.length===0){ o.innerHTML = html; return; }

    for (var i = 0; i < dv.children.length; i++) {
        var c = dv.children[i];

        // n: new node with the same type as c
        var n = document.createElement(c.nodeName);

        // copy all attributes from c to n
        for (var j = 0; j < c.attributes.length; j++)
            n.setAttribute(c.attributes[j].nodeName, c.attributes[j].nodeValue);

        // If current node is a leaf, just copy the appropriate property (text or innerHTML)
        if (c.children.length == 0)
        {
            switch (c.nodeName)
            {
                case "SCRIPT":
                    if (c.text) n.text = c.text;
                    break;
                default:
                    if (c.innerHTML) n.innerHTML = c.innerHTML;
                    break;
            }
        }
        // If current node has sub nodes, call itself recursively:
        else setHTML(n, c.innerHTML, false);
        o.appendChild(n);
    }
}

ここでデモを見ることができます


3

あなたはこのようにすることができます:

var mydiv = document.getElementById("mydiv");
var content = "<script>alert(\"hi\");<\/script>";

mydiv.innerHTML = content;
var scripts = mydiv.getElementsByTagName("script");
for (var i = 0; i < scripts.length; i++) {
    eval(scripts[i].innerText);
}

3

ここでは使用しないソリューションevalであり、作品のスクリプトリンクされたスクリプトと同様にして、モジュール

この関数は3つのパラメーターを受け入れます。

  • html:挿入するHTMLコードを含む文字列
  • dest:ターゲット要素への参照
  • append:ターゲット要素htmlの最後に追加できるようにするブールフラグ
function insertHTML(html, dest, append=false){
    // if no append is requested, clear the target element
    if(!append) dest.innerHTML = '';
    // create a temporary container and insert provided HTML code
    let container = document.createElement('div');
    container.innerHTML = html;
    // cache a reference to all the scripts in the container
    let scripts = container.querySelectorAll('script');
    // get all child elements and clone them in the target element
    let nodes = container.childNodes;
    for( let i=0; i< nodes.length; i++) dest.appendChild( nodes[i].cloneNode(true) );
    // force the found scripts to execute...
    for( let i=0; i< scripts.length; i++){
        let script = document.createElement('script');
        script.type = scripts[i].type || 'text/javascript';
        if( scripts[i].hasAttribute('src') ) script.src = scripts[i].src;
        script.innerHTML = scripts[i].innerHTML;
        document.head.appendChild(script);
        document.head.removeChild(script);
    }
    // done!
    return true;
}

つまり、スクリプトコンテンツにコードコンテンツを追加することは評価ですよね。
Kevin B

@KevinB悪名高い違いがあります...試してみるeval('console.log(this)')と、最も明白な違いが見られます
colxi

だから文脈は違う、そして?それはまだ単なる評価です。
Kevin B

@KevinBいいえ、評価ではありません。これを試してみて、evalの外eval('let b=100')からアクセスbしてみてください ....頑張ってください。これが必要になります
colxi

私のために働く。乾杯
Bezzzo

1

Krasimir Tsonevは、すべての問題を解決する優れたソリューションを備えています。彼の方法ではevalを使用する必要がないため、パフォーマンスやセキュリティの問題はありません。これにより、innerHTML文字列にjsを含むhtmlを設定し、それをすぐにDOM要素に変換すると同時に、コードに存在するjs部分を実行することができます。短く、シンプルで、思い通りに動作します。

彼の解決策をお楽しみください:

http://krasimirtsonev.com/blog/article/Convert-HTML-string-to-DOM-element

重要なメモ:

  1. ターゲット要素をdivタグでラップする必要があります
  2. src文字列をdivタグでラップする必要があります。
  3. src文字列を直接記述し、それがjsパーツを含む場合、これは文字列であるため、終了スクリプトタグを正しく(/の前に\を使用して)記述するように注意してください。

1

$(parent).html(code)代わりに使用parent.innerHTML = code

以下は、使用するスクリプトと属性document.writeを介して読み込まれるスクリプトも修正しsrcます。残念ながら、これもGoogle AdSenseスクリプトでは機能しません。

var oldDocumentWrite = document.write;
var oldDocumentWriteln = document.writeln;
try {
    document.write = function(code) {
        $(parent).append(code);
    }
    document.writeln = function(code) {
        document.write(code + "<br/>");
    }
    $(parent).html(html); 
} finally {
    $(window).load(function() {
        document.write = oldDocumentWrite
        document.writeln = oldDocumentWriteln
    })
}

ソース


1
ここで少し遅れますが、このメソッドを使用している可能性のある人は、JQueryで<script src = "url> </ script>ではなく$ .loadScript(url)を使用してスクリプトをロードする必要があることに注意してください。後者の場合、ブラウザでの非推奨の同期XMLHttpRequestエラー
Stavm

1

テンプレートとdocument.importNodeを使用してみてください。次に例を示します。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Sample</title>
</head>
<body>
<h1 id="hello_world">Sample</h1>
<script type="text/javascript">
 var div = document.createElement("div");
  var t = document.createElement('template');
  t.innerHTML =  "Check Console tab for javascript output: Hello world!!!<br/><script type='text/javascript' >console.log('Hello world!!!');<\/script>";
  
  for (var i=0; i < t.content.childNodes.length; i++){
    var node = document.importNode(t.content.childNodes[i], true);
    div.appendChild(node);
  }
 document.body.appendChild(div);
</script>
 
</body>
</html>


1
これはMicrosoft Edgeでは機能しませんが、他の回避策はありますか?
Soul

1

<script>このようにラップすることもでき、実行されます:

<your target node>.innerHTML = '<iframe srcdoc="<script>alert(top.document.title);</script>"></iframe>';

注:内部のスコープsrcdocはiframeを参照するためtop、親ドキュメントにアクセスするには、上記の例のように使用する必要があります。


0

はい、できますが、DOMの外で行う必要があり、順序が正しい必要があります。

var scr = '<scr'+'ipt>alert("foo")</scr'+'ipt>';
window.onload = function(){
    var n = document.createElement("div");
    n.innerHTML = scr;
    document.body.appendChild(n);
}

... 'foo'に警告します。これは動作しません:

document.getElementById("myDiv").innerHTML = scr;

そして、ノードが最初に挿入されるため、これでも機能しません:

var scr = '<scr'+'ipt>alert("foo")</scr'+'ipt>';
window.onload = function(){
    var n = document.createElement("div");
    document.body.appendChild(n);
    n.innerHTML = scr;  
}

16
それは価値があることのために:これは現在のブラウザでは動作しないようです。
Wichert Akkerman 2012

0

この問題に対する私の解決策は、Mutation Observerを設定して<script></script>ノードを検出し、それを<script></script>同じsrc を持つ新しいノードに置き換えることです。例えば:

let parentNode = /* node to observe */ void 0
let observer = new MutationObserver(mutations=>{
    mutations.map(mutation=>{
        Array.from(mutation.addedNodes).map(node=>{
            if ( node.parentNode == parentNode ) {
                let scripts = node.getElementsByTagName('script')
                Array.from(scripts).map(script=>{
                    let src = script.src
                    script = document.createElement('script')
                    script.src = src
                    return script
                })
            }
        })
    })
})
observer.observe(document.body, {childList: true, subtree: true});

1
理由を言わずに私に反対票を投じてくれてありがとう。すべてが大好きです。
ガブリエルガルシア

0

ガブリエルガルシアがMutationObserversについて言及したことは正しい方向に進んでいますが、私にとってはうまくいきませんでした。それがブラウザの癖によるものなのか、それとも自分のミスによるものなのかはわかりませんが、私のために機能するようになったバージョンは次のとおりです。

document.addEventListener("DOMContentLoaded", function(event) {
    var observer = new MutationObserver(mutations=>{
        mutations.map(mutation=>{
            Array.from(mutation.addedNodes).map(node=>{
                if (node.tagName === "SCRIPT") {
                    var s = document.createElement("script");
                    s.text=node.text;
                    if (typeof(node.parentElement.added) === 'undefined')
                        node.parentElement.added = [];
                    node.parentElement.added[node.parentElement.added.length] = s;
                    node.parentElement.removeChild(node);
                    document.head.appendChild(s);
                }
            })
        })
    })
    observer.observe(document.getElementById("element_to_watch"), {childList: true, subtree: true,attributes: false});
};

もちろん、element_to_watch変更する要素の名前に置き換える必要があります。

node.parentElement.addedに追加されるスクリプトタグを格納するために使用されdocument.headます。外部ページの読み込みに使用される関数では、次のようなものを使用して、関連のなくなったスクリプトタグを削除できます。

function freeScripts(node){
    if (node === null)
        return;
    if (typeof(node.added) === 'object') {
        for (var script in node.added) {
            document.head.removeChild(node.added[script]);
        }
        node.added = {};
    }
    for (var child in node.children) {
        freeScripts(node.children[child]);
    }
}

そして、ロード関数の始まりの例:

function load(url, id, replace) {
    if (document.getElementById(id) === null) {
        console.error("Element of ID "+id + " does not exist!");
        return;
    }
    freeScripts(document.getElementById(id));
    var xhttp = new XMLHttpRequest();
    // proceed to load in the page and modify innerHTML
}

MutationObserver要素がドキュメントに追加されるたびに新しいものが追加されていることに気づきましたか?ところで、私のコードが機能しないのはなぜですか。
ガブリエルガルシア

@gabrielgarcia試してみたが機能しなかったため、コードは機能しないと言った。今それを見ると、あなたではなく、私にあったことは完全に可能です。今それを修正しています。
pixelherodev

re:要素がドキュメントに追加されるたびにMutationObserverを追加することについて、何を話しているのですか?DOMContentLoadedとここでMDNから引用します。「スタイルシート、画像、サブフレームの読み込みが完了するのを待たずに、最初のHTMLドキュメントが完全に読み込まれて解析されたときに発生します。」それは、かつて一度だけです。さらに、このスクリプトは私のサイトで問題なく動作しており、デバッグでは1回しか発生しないことが示されているため、理論上だけでなく実際にも1回だけです。
pixelherodev

1
うんうん…見落としました お詫び申し上げます。
ガブリエルガルシア

@gabrielgarcia問題ありません:)
pixelherodev

0

私にとって最良の方法は、innerHtmlを介して新しいHTMLコンテンツを挿入し、次に使用することです

setTimeout(() => {
        var script_el = document.createElement("script")
        script_el.src = 'script-to-add.js'
        document.body.appendChild(script_el)
    }, 500)

setTimeoutは必須ではありませんが、うまく機能します。これでうまくいきました。


-1

innerHTMLからの(Javaスクリプト)タグの実行

スクリプト要素を、クラス属性class = "javascript"を持つdivに置き換えて、次のように閉じます </div>

実行するコンテンツを変更しないでください(以前はスクリプトタグ内でしたが、現在はdivタグ内にあります)

ページにスタイルを追加...

<style type="text/css"> .javascript { display: none; } </style>

jqueryを使用してevalを実行します(jquery jsはすでに含まれているはずです)。

   $('.javascript').each(function() {
      eval($(this).text());

    });`

あなたは私のブログでここでもっと探検することができます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.