如何在 DOM 中移动 iFrame 而不丢失其状态?

看看这个简单的 HTML:

<div id="wrap1">
<iframe id="iframe1"></iframe>
</div>
<div id="warp2">
<iframe id="iframe2"></iframe>
</div>

让我们说,我想移动的包装,使 #wrap2会在 #wrap1之前。Iframe 被 JavaScript 污染了。我知道 jQuery 的 .insertAfter().insertBefore()。但是,当我使用它们时,iFrame 会丢失所有的 HTML、 JavaScript 变量和事件。

假设下面是 iFrame 的 HTML:

<html>
<head>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript">
// The variable below would change on click
// This represents changes on variables after the code is loaded
// These changes should remain after the iFrame is moved
variableThatChanges = false;
$(function(){
$("body").click(function(){
variableThatChanges = true;
});
});
</script>
</head>
<body>
<div id='anything'>Illustrative Example</div>
</body>
</html>

在上面的代码中,如果用户单击主体,变量 variableThatChanges将... 更改。这个变量和 click 事件应该在移动 iFrame 之后保留(同时保留其他已启动的变量/事件)

我的问题是: 使用 JavaScript (不管有没有 jQuery) ,我如何移动 DOM 中的换行节点(以及它们的 iFrame 子节点) ,使 iFrame 的窗口保持不变,而 iFrame 的事件/变量/等保持不变?

43879 次浏览

It isn't possible to move an iframe from one place in the dom to another without it reloading.

Here is an example to show that even using native JavaScript the iFrames still reload: http://jsfiddle.net/pZ23B/

var wrap1 = document.getElementById('wrap1');
var wrap2 = document.getElementById('wrap2');
setTimeout(function(){
document.getElementsByTagName('body')[0].appendChild(wrap1);
},10000);

Unfortunately, the parentNode property of an HTML DOM element is read-only. You can adjust the positions of the iframes, of course, but you can't change their location in the DOM and preserve their states.

See this jsfiddle I created that provides a good test bed. http://jsfiddle.net/RpHTj/1/

Click on the box to toggle the value. Click on the "move" to run the javascript.

If you have created the iFrame on the page and simply need to move it's position later try this approach:

Append the iFrame to the body and use a high z-index and top,left,width,height to put the iFrame where you want.

Even CSS zoom works on the body without reloading which is awesome!

I maintain two states for my "widget" and it is either injected in place in the DOM or to the body using this method.

This is useful when other content or libraries will squish or squash your iFrame.

BOOM!

This question is pretty old... but I did find a way to move an iframe without it reloading. CSS only. I have multiple iframes with camera streams, I dont like when they reload when i swap them. So i used a combination of float, position:absolute, and some dummy blocks to move them around without reloading them and having the desired layout on demand (resizing and all).

This answer is related to the bounty by @djechlin

A lot of search on the w3/dom specs and didn't find anything final that specifically says that iframe should be reloaded while moving in the DOM tree, however I did find lots of references and comments in the webkit's trac/bugzilla/microsoft regarding different behavior changes over the years.

I hope someone will find anything specific regarding this issue, but for now here are my findings:

  1. According to Ryosuke Niwa - "That's the expected behavior".
  2. There was a "magic iframe" (webkit, 2010), but it was removed in 2012.
  3. According to MS - "iframe resources are freed when removed from the DOM". When you appendChild(node) of existing node - that node is first removed from the dom.
    Interesting thing here - IE<=8 didn't reload the iframe - this behavior is (somewhat) new (since IE>=9).
  4. According to Hallvord R. M. Steen comment, this is a quote from the iframe specs

    When an iframe element is inserted into a document that has a browsing context, the user agent must create a new browsing context, set the element's nested browsing context to the newly-created browsing context, and then process the iframe attributes for the "first time".

    This is the most close thing I found in the specs, however it's still require some interpretation (since when we move the iframe element in the DOM we don't really do a full remove, even if the browsers uses the node.removeChild method).

In response to the bounty @djechlin placed on this question, I have forked the jsfiddle posted by @matt-h and have come to the conclusion that this is still not possible.

http://jsfiddle.net/gr3wo9u6/

//this does not work, the frames reload when appended back to the DOM
function swapFrames() {
var w1 = document.getElementById('wrap1');
var w2 = document.getElementById('wrap2');
var f1 = w1.querySelector('iframe');
var f2 = w2.querySelector('iframe');


w1.removeChild(f1);
w2.removeChild(f2);
w1.appendChild(f2);
w2.appendChild(f1);
//f1.parentNode = w2;
//f2.parentNode = w1;


//alert(f1.parentNode.id);
}

Whenever an iframe is appended and has a src attribute applied it fires a load action similarly to when creating an Image tag via JS. So when you remove and then append them they are completely new entities and they refresh. Its kind of how window.location = window.location will reload a page.

The only way I know to reposition iframes is via CSS. Here is an example I put together showing one way to handle this with flex-box: https://jsfiddle.net/3g73sz3k/15/

The basic idea is to create a flex-box wrapper and then define an specific order for the iframes using the order attribute on each iframe wrapper.

<style>
.container{
display: flex;
flex-direction: column;
}
</style>
<div class="container">
<div id="wrap1" style="order: 0" class="iframe-wrapper">
<iframe id="iframe1" src="https://google.com"></iframe>
</div>
<div id="warp2" style="order: 1" class="iframe-wrapper">
<iframe id="iframe2" src="https://bing.com"></iframe>
</div>
</div>

As you can see in the JS fiddle these order styles are inline to simplify the flip button so rotate the iframes.

I sourced the solution from this StackOverflow question: Swap DIV position with CSS only

Hope that helps.

If you are using the iframe to access pages you control, you could create some javascript to allow your parent to communicate with the iframe via postMessage

From there, you could build login inside the iframe to record state changes, and before moving dom, request that as a json object.

Once moved, the iframe will reload, you can pass the state data into the iframe and the iframe listening can parse the data back into the previous state.

PaulSCoder has the right solution. Never manipulate the DOM for this purpose. The classic approach for this is to have a relative position and "flip" the positions in the click event. It's only not wise to put the click event on the body, because it bubbles from other elements too.

$("body").click(function () {
var frame1Height = $(frame1).outerHeight(true);
var frame2Height = $(frame2).outerHeight(true);
var pos = $(frame1).css("top");
if (pos === "0px") {
$(frame1).css("top", frame2Height);
$(frame2).css("top", -frame1Height);
} else {
$(frame1).css("top", 0);
$(frame2).css("top", 0);
}
});

If you only have content that is not cross-domain you could save and restore the HTML:

var htmlContent = $(frame).contents().find("html").children();
// do something
$(frame).contents().find("html").html(htmlContent);

The advantage of the first method is, that the frame keeps on doing what it was doing. With the second method, the frame gets reloaded and starts it's code again.