prevent iframe from scrolling parent document when calling focus - javascript

I have an iframe that calls someElement.focus. When this happens the parent scrolls to show the iframe. How can I prevent the parent from scrolling? Effectively I want the iframe itself to scroll so within the iframe the correct element is scrolled into space of the iframe itself but the parent page should be unaffected.
const lines = new Array(100).fill(0).map((e,i) => i).join('\n')
const script = 'script';
const frameContent = `
<style>
pre { font-size: 60pt; }
</style>
<body>
<pre>${lines}</pre>
<input type="text" id="target">
</body>
<${script}>
document.querySelector('#target').focus();
</${script}>
`;
document.querySelector('pre').textContent = lines;
const iframe = document.querySelector('iframe');
iframe.src = URL.createObjectURL(new Blob([frameContent], {type: 'text/html'}));
<style>
pre { font-size: 60pt; }
}
</style>
<div>you should see this element</div>
<pre></pre>
<iframe></iframe>
<div>you should NOT be scrolled to see this element</div>
It makes sense to me that calling focus would move the input area on to the screen, but at the same time, it makes no sense to me that any random iframe can get the parent page to jump to it on demand by calling focus.
Is it possible to prevent the iframe from affecting the parent?

You can try passing options when setting focus:
document.querySelector('#target').focus({preventScroll: true});
The preventScroll option is described in the MDN docs on HTMLElement.focus().
Note that this will likely only work for same-origin iframes.
Browsers apply restrictions that might prevent the preventScroll option being respected inside cross-origin/third-party iframes.

Related

How to create click event across MULTIPLE iframes (same domain)?

I have a main (parent) page that hosts 3 iframes that are under the same domain. The iframe's src attribute is added on a click event, so the iframe's aren't loaded until needed.
What I want is when I click a certain div on my main page, it will trigger a click on a specific div that's located inside all 3 iframes at the same time.
I have tried the following code in my parent page:
function myFunction() {
var iframe = document.getElementById("iframe1");
var elmnt = iframe.contentWindow.document.getElementById("btn").click()
}
and repeated it twice more for iframe's #iframe2 and #iframe3. However, it didn't work unless I loaded the iframes in order that they're written on my page. (i.e the click event on #iframe2 didn't execute until after #iframe1's event). This is a problem as I'm unable to control what iframe visitors will load first. I tried giving them separate function() names, but the same thing occurred - only one iframe was effected at a time. Note, this worked fine when I tested it out with the iframe's already having their src's like normal, just not when they're added manually.
I also tried adding this in the iframe pages:
parent.document.getElementById('btn').onclick = function(){
document.getElementById('btn').click();
}
However, this only works on the first iframe that is loaded (i.e if #iframe2 is loaded first, then the iframe1's and iframe3's event wont execute)
Is there any other solutions I could try? My purpose is creating a day and night toggle with classList.toggle, and I want all the iframes css to be toggled at the same time, with the toggle button
being on the main page.
(I prefer vanilla javascript for my main page, but am fine with jQuery on the iframe pages)
You are dynamically loading iframe's src suppose user loads <iframe3> but your code is written such a way that it tries to do something with <div> inside <iframe1> (which isn't loaded yet) what will happen? It will throw error and break the execution that's why it's not working for you what you need is to first look whether they are currently there
Also you are using var iframe = document.getElementById("iframe1"); but I don't see any id on the fiddle you posted
Try this
<body>
<style>
body {
background:#fff;
color:#000
}
body.toggled
{
background:#000;
color:#fff
}
</style>
<div id="btn">click here to toggle css</div>
<p>page content</p>
add src to iframe one<br>
add src to iframe two<br>
add src to iframe three
<p>iframe 1:</p>
<iframe src="about:blank" id="iframe1" name="iframe1"></iframe>
<p>iframe 2:</p>
<iframe src="about:blank" id="iframe2" name="iframe2"></iframe>
<p>iframe 3:</p>
<iframe src="about:blank" id="iframe3" name="iframe3"></iframe>
</body>
<script>
document.querySelector('#btn').addEventListener('click', function(e) {
[].map.call(document.querySelectorAll('body,a'), function(el) {
el.classList.toggle('toggled');
});
var iframe1 = document.getElementById("iframe1");
var iframe1btn = iframe1.contentWindow.document.getElementById("btn");
if(iframe1btn) //<-- check if that element really exists within <iframe>
iframe1.contentWindow.document.getElementById("btn").click()
// you could've also used <iframe>'s loaded event but this is better way
var iframe2 = document.getElementById("iframe2");
var iframe2btn = iframe2.contentWindow.document.getElementById("btn");
if(iframe2btn)
iframe2.contentWindow.document.getElementById("btn").click()
var iframe3 = document.getElementById("iframe3");
var iframe3btn = iframe3.contentWindow.document.getElementById("btn");
if(iframe3btn)
iframe3.contentWindow.document.getElementById("btn").click()
});
</script>
I am not particularly sure what you got wrong on, but I think I understand what you want to do. To reproduce this example, please create a HTML file (with name index.html) in your local PC and run this file on your browser:
<!DOCTYPE html>
<html>
<head>
<style>
body {
background: #fff;
color: #000;
}
body.toggled {
background: #000;
color: #fff;
}
</style>
</head>
<body>
<div id="btn">click here to toggle css</div>
<p>page content</p>
add src to iframe one<br>
add src to iframe two<br>
add src to iframe three
<p>iframe 1:</p>
<iframe src="about:blank" name="iframe1"></iframe>
<p>iframe 2:</p>
<iframe src="about:blank" name="iframe2"></iframe>
<p>iframe 3:</p>
<iframe src="about:blank" name="iframe3"></iframe>
<script>
let outerBody = document.querySelector('body')
let toggleButton = document.querySelector('#btn')
let iframes = document.querySelectorAll('iframe')
let anchors = document.querySelectorAll('a')
toggleButton.addEventListener('click', () => {
let body = document.querySelector('body')
body.classList.toggle('toggled')
Array.from(iframes).forEach(iframe => {
toggleIframe(iframe)
})
})
Array.from(iframes).forEach(iframe => {
iframe.addEventListener('load', e => {
toggleIframe(iframe)
})
})
Array.from(anchors).forEach(anchor => {
anchor.addEventListener('click', e => {
let targetedIframe = document.querySelector(`iframe[name=${anchor.target}]`)
targetedIframe.src = './index.html'
})
})
function toggleIframe(iframe) {
let iframeBody = iframe.contentWindow.document.querySelector('body')
let iframeToggleButton = iframe.contentWindow.document.querySelector('#btn')
let isOuterBodyToggled = outerBody.classList.contains('toggled')
let isIframeBodyToggled = iframeBody.classList.contains('toggled')
if (isOuterBodyToggled && iframeToggleButton && !isIframeBodyToggled)
iframeToggleButton.click()
else if (!isOuterBodyToggled && iframeToggleButton && isIframeBodyToggled)
iframeToggleButton.click()
}
</script>
</body>
</html>
I changed your a's href to empty (#). I also changed your JS file. Basically, I specified the as to change the source of its associated iframe when it's clicked with addEventListener('click'). Also, each time I click on the outerBody's (parent browsing context) button, I will also toggle its containing iframes' body by performing a click on the iframes' buttons. That already works. However, your problem was that you don't know when the iframes will be loaded. There's a function for that, addEventListener('load') on your iframes. When an iframe is loaded (an iframe will be loaded again when its source is changed), I will use that event listener to check if its parent browsing context's body is toggled. If it is, then simply toggle the iframe's body too by performing click.
Voila, it works. As mentioned, please try pasting the code above to your local file in your PC and run it in a browser.

Call a parent function from inside an iFrame to change Element height

I'm building my first website and I have a main element which is supposed to stretch to the height of the content of the iFrame. I need a click in NavBox.htm (also iFrame'd into the Index page) to display SubFolder/SubPage.htm (mostly plaintext) inside the main iFrame of Index.htm and call a function in Index.htm that changes the element's height depending on the length of the sub-page.
It works when a longer page is called, but if a shorter page is called, it actually grows by the padding size (20px) I added (if I take off the padding it just stays the same size). I'm pretty sure it's a simple mistake in my syntax, but I can't get the height to recalculate with a new page inside the element.
On Index.htm I have the following function that changes the element height:
function frm_onload(frmname) {
frmname.frameElement.height = frmname.document.body.scrollHeight+20;
}
Further down is the actual iFrame call:
<iframe id="Center" width=98%; LANGUAGE=javascript ONLOAD="return frm_onload(Center)" marginwidth="0" marginheight="0" frameborder="0" ></iframe>
On the the NavBox.htm child page I have a link that changes the content of the element:
SubPage
So if I'm thinking straight there's either something I'm not understanding from the function or I need the link in NavBox to do more. Thanks very much for any help you guys can provide!
I answered a question very similar to this one here. You can use this function:
function addCSSToParent(url) {
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = url;
link.type = 'text/css';
window.parent.document.getElementsByTagName('head')[0].appendChild(link);
}
// Add some CSS
addCSSToParent('some.css');
It will allow you to attach a .css file for styling the parent. You can access the parent document from inside an iframe it in jQuery with $(window.parent.document). So you would just need to either add a css file to the parent, or define the style of the element height using jQuery.

How to move an iFrame in the DOM without losing its state?

Take a look at this simple HTML:
<div id="wrap1">
<iframe id="iframe1"></iframe>
</div>
<div id="warp2">
<iframe id="iframe2"></iframe>
</div>
Let's say I wanted to move the wraps so that the #wrap2 would be before the #wrap1. The iframes are polluted by JavaScript. I am aware of jQuery's .insertAfter() and .insertBefore(). However, when I use those, the iFrame loses all of its HTML, and JavaScript variables and events.
Lets say the following was the iFrame's 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>
In the above code, the variable variableThatChanges would...change if the user clicked on the body. This variable, and the click event, should remain after the iFrame is moved (along with any other variables/events that have been started)
My question is the following: with JavaScript (with or without jQuery), how can I move the wrap nodes in the DOM (and their iframe childs) so that the iFrame's window stays the same, and the iFrame's events/variables/etc stay the same?
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);
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:
According to Ryosuke Niwa - "That's the expected behavior".
There was a "magic iframe" (webkit, 2010), but it was removed in 2012.
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).
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).
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 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!
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.
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).
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.
At least in some circumstances a shadow dom with slotting might be an option.
<template>
<style>div {outline:1px solid black; height:45px}</style>
<div><slot name="a" /></div>
<div><slot name="b" /></div>
</template>
<div id="shadowhost">
<iframe src="data:text/html,<button onclick='this.innerText+=`!`'>!</button>"
slot="a" height=40px ></iframe>
</div>
<button onclick="ifr.slot= (ifr.slot=='a') ? 'b' : 'a';">swap</button>
<script>
document.querySelector('#shadowhost').attachShadow({mode: 'open'}).appendChild(
document.querySelector('template').content
);
ifr=document.querySelector('iframe');
</script>
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);
}

Show part of page in new window

I'd like to open the page in the image below, but only showing the green part in the new window. Hiding the menu and the header to the user.
function openNewWindow() {
var pr = window.open("Page.aspx", "page", "width=700, height=400");
pr.onload() = function() {
pr.document.getElementById("header").style.display = 'none';
}
}
Is it possible to set some kind of offset for the page in the new window? Like left:-40px and top:-20px or something similar? I know top and left positions the new window rather than its content, but is there something I can do to change the position of the actual content?
Is there a work-around or another solution with the same result?
EDIT
When I click Click I want Page.aspx (image above) to open in a new window, but without menu and header showing.
how about you open a page that shows an iframe which loads your page -- and then you can set your iframe width/height to what you need and whether to provide scrolling or not?
something like this:
<html>
<!-- this is page2.aspx -->
<body>
<!-- header -->
<!-- menu -->
<iframe id="abc"...></iframe>
<script type="text/javascript">
var page = ... //retrieve the value of the parameter "url" passed to us (you can find how to do this by googling)
document.getElementById( "abc" ).src = page; //set the iframe url to the parameter passed
</script>
</body>
</html>
then your function becomes:
function openNewWindow() {
window.open("Page.aspx?url=http://page/to/load", "page", "width=700, height=400");
}
Load the whole page, but hide the header and menu using Javascript:
newwindow.onload = function() {
newwindow.document.getElemementById('header').style.display = 'none';
newwindow.document.getElemementById('menu').style.display = 'none';
}
(or use JQuery's .hide() method)
Load the whole page, but add an extra stylesheet which sets the header and menu to hidden:
#header, #menu {display:none !important;}
when you serve the page, use a different template which doesn't include the header and menu, etc. All things being equal, this would probably be the best option, but I can't really give any advice on this without knowing a whole load more about your code.
(all of the above assumes that you have the IDs in your header and menu that I've specified; change as appropriate)
Agree with Spudley, but if that's not possible you might be able to get by with negative margins. Like this:
body { margin: -50px 0 0 -50px }

Methods for breaking up content client-side into multiple "pages"

I have a long HTML text-only article formatted with paragraph tags. What I'd like to do is break this content into N number of divs so that I can create individual pages. So, for instance, on an iPad/iPhone, instead of reading one long page the user could swipe right/left to navigate to pages.
My initial javascript attempts have been somewhat convoluted: creating an array of the text, measuring line-heights, device window heights, adding closing/opening paragraph tags and the end/beginning of pages.
Thoughts on a good way to approach this? I will not have access to server-side processing, this has to be a client-side solution.
How about a div that scrolls a fixed amount on the click of a button?
I've used YUI here, but it's easily translatable. Basically you just create a fixed size div and change it's scrollTop value on click.
<html><head><script type="text/javascript" src="http://yui.yahooapis.com/combo?2.8.1/build/yahoo-dom-event/yahoo-dom-event.js"></script>
<style>
#page {
border: inset;
height:400px;
width:400px;
overflow:hidden;
}
</style>
<script>
(function() {
var Dom = YAHOO.util.Dom,
Evt = YAHOO.util.Event;
var getPageSize = function(){
return Dom.get('size').value;
};
Evt.addListener('Next', "click", function() {
var page = document.getElementById('page');
Dom.get('page').scrollTop += getPageSize();
});
Evt.addListener('Previous', "click", function() {
var page = document.getElementById('page');
Dom.get('page').scrollTop -= getPageSize();
});
})();
</script></head>
<body><input type='text' id='size' value='50'>
<div id='Next'>Next</div>
<div id='Previous'>Previous</div>
<div id='page'> INSERT LOTS OF TEXT HERE </div></body></html>

Categories