How can I make an iframe click-through, but make that iframe's body still be clickable?
I tried:
iframe.style.width = '100%'
iframe.style.height = '100%'
iframe.style.display = 'block'
iframe.style.position = 'fixed'
iframe.style.backgroundColor = 'transparent'
iframe.style.pointerEvents = 'none'
iframe.style.border = '0'
iframe.frameborder = '0'
iframe.scrolling = 'no'
iframe.allowTransparency = 'true'
and inside of my I frame I'm using the following css:
html, body {
/* background:none transparent; */
pointer-events:auto;
}
This results in body being visible (which is what I want), but it is click-through like the rest of the iframe. I want the body of the iframe to be clickable, but all the rest of the actual iframe element should be click-through.
The iframe is always bigger than the body inside of it.
Unfortunately I cannot access the the iframe content from the main site (so accessing the scrollHeight etc isn't possible), I can only change its actual source code.
DISCLAIMER:
OP created this question almost two years ago, my answer follows Ian Wise's bumping the question and elaborating on it (see comments).
What you are describing here involves logic between a document and a child document: "If a click event did nothing inside child document, apply that click event to parent document", and therefore cannot be approached using HTML/CSS.
Iframes are different documents. They do have a child-parent relationship with their containers, but an event that occurs within the iframe will be handled by the iframe.
An idea that requires some code but will work:
Place a transparent div above all the stacked iframes, and catch the
click event pos.
Parent logic ->
Iterate through array of existing iframe elements.
Send click pos until one of the iframes returns a positive response.
function clickOnCover(e, i) {
if(e && e.preventDefaule) e.preventDefault();
if(i && i >= iframes.length) {
console.log("No action.");
return;
}
var iframe = iframes[i || 0];
if(iframe.contentWindow && iframe.contentWindow.postMessage) {
iframe.contentWindow.postMessage({ x: e.clientX, y: e.clientY, i: i });
}
}
function iframeResponse(e) {
var response = e.data, iframeIndex = response.i;
if(response.success)
console.log("Action done on iframe index -> " + iframeIndex);
else
clickOnCover({ clientX: response.x, clientY: response.y }, iframeIndex+1);
}
iFrames logic ->
Have a function that accepts the clientX, clientY and checks for possible activies in that position (might be tricky!).
Will respond positively if an action occurred, and the opposite.
window.addEventListener("message", function(e) {
// Logic for checking e.x & e.y
e.success = actionExists; // Some indicator if an action occurred.
if(window.parent && window.parent.postMessage) {
window.parent.postMessage(e);
}
});
This solution keeps managing the event within the parent document and only requires iterating through whatever amount of stacked iframes.
Found a relevant SO question to further support my claim: Detect Click in Iframe
This is not possible with CSS.
The easiest way is to resize the iframe properly. Assuming you have access to iframe content, the following solution is possible:
You might add a little JS to allow the parent page to know the iframe height
mainpage.js
var iframe = getIframe();
setIntervalMaybe(() => {
// ask for size update
iframe.contentWindow.postMessage({action: "getSize"}, document.location.origin);
}, 100)
window.addEventListener("message", function receiveMessage(event) {
switch(event.data.action) {
case "returnSize":
// updateIFrameSize(event.data.dimensions);
console.log(event.data.dimensions);
break;
}
}, false);
iframe.js
window.addEventListener("message", function receiveMessage(event) {
switch(event.data.action) {
case "getSize":
event.source.postMessage({action: "returnSize", dimensions: {
width: document.body.offsetWidth,
height: document.body.offsetHeight
}}, event.origin);
break;
}
}, false);
Related
I've been trying to figure out this issue for quite a long time.
I'm creating VPAID script to support basic VPAID functionality according to the specification.
To display ads I'm taking the slot provided in initAd params and append an iFrame with my video player inside (PlayerJS) to show ads (it's mandatory to use own player for third-party events tracking etc.).
Everything is working well if slot isn't hidden by display CSS property or if its container is not. When it's hidden, the browser won't let the iframe dom to render with js and so player is not starting.
initAd(width, height, viewMode, desiredBitrate, creativeData, environmentVars) {
this._attributes.width = width
this._attributes.height = height
this._attributes.viewMode = viewMode
this._attributes.desiredBitrate = desiredBitrate
this._slot = environmentVars.slot || this.this.emit('AdError', 'Slot is invalid')
this._slotWnd = function(a) {
a = a.ownerDocument
return a.defaultView || a.parentWindow
}(this._slot)
this._videoSlot = environmentVars.videoSlot || this.this.emit('AdError', 'Video slot is invalid')
try {
this.adParameters = JSON.parse(creativeData.AdParameters)
} catch (e) {
console.error('Error parsing AdParameters')
console.log(e)
}
this._slotWnd.addEventListener('message', (event) => { this.eventHandler(event, this) })
this._player = new PlayerFrameIniter(this._slot, this._slotWnd, false, true)
this._player.init( () => this.emit('AdLoaded') )
}
And in PlayerFrameIniter I'm creating an iframe like this:
createIframe(container, url, onFrameLoaded) {
this._frame = this._context.document.createElement('iframe')
const style = {
width: '100%',
height: '100%',
border: 0,
position: 'absolute',
overflow: 'hidden'
}
Object.assign(this._frame.style, style)
this._frame.src = url
this._frame.onload = onFrameLoaded
container.appendChild(this._frame)
}
How can I make frame inside slot render correctly or maybe there's a different approach for this task?
It appeared to be a PlayerJS issue, so the question is no longer actual
I have a problem with scroll to element on mobile Safari in iframe (it works on other browsers, including Safari on mac).
I use scrollIntoView. I want to scroll when all content has been rendered. Here is my code:
var readyStateCheckInterval = setInterval(function () {
if (document.readyState === "complete") {
clearInterval(readyStateCheckInterval);
$browser.notifyWhenNoOutstandingRequests(function () {
if (cinemaName != null && eventId == null) {
scrollToCinema();
} else {
scrollToEvent();
}
});
}
}, 10);
function scrollToEvent() {
var id = eventId;
var delay = 100;
if (cinemaName != null) {
id = cinemaName + "#" + eventId;
}
if ($rootScope.eventId != null) {
id = $rootScope.cinemaId + "#" + $rootScope.eventId;
}
$timeout(function () {
var el = document.getElementById(id);
if (el != null)
el.scrollIntoView(true);
$rootScope.eventId = null;
}, delay);
}
ScrollIntoView does not work (currently). But you can manually calculate the position of the element and scroll to it. Here is my solution
const element = document.getElementById('myId')
Pass the element to this function
/** Scrolls the element into view
* Manually created since Safari does not support the native one inside an iframe
*/
export const scrollElementIntoView = (element: HTMLElement, behavior?: 'smooth' | 'instant' | 'auto') => {
let scrollTop = window.pageYOffset || element.scrollTop
// Furthermore, if you have for example a header outside the iframe
// you need to factor in its dimensions when calculating the position to scroll to
const headerOutsideIframe = window.parent.document.getElementsByClassName('myHeader')[0].clientHeight
const finalOffset = element.getBoundingClientRect().top + scrollTop + headerOutsideIframe
window.parent.scrollTo({
top: finalOffset,
behavior: behavior || 'auto'
})
}
Pitfalls: Smooth scroll also does not work for ios mobile, but you can complement this code with this polyfill
In my experience scrollIntoView() fails sometimes on my iphone and my ipad and sometimes it works (on my own web sites). I'm not using iframes. This is true both with safari and firefox on the above devices.
The solution that works for me is to pop the element you need to scroll to inside a DIV eg. as the first element in that DIV. Hey presto it then works fine!
Seems like a dodgy implementation by Apple.
Your most likely having the exact same issue I just debugged. Safari automatically resizes the frame to fit it's contents. Therefore, the parent of the Iframe will have the scrollbars in Safari. So calling scrollintoview from within the Iframe itself 'fails'.
If Iframe is cross domain accessing the parent document via window.parent.document will be denied.
If you need a cross domain solution check my answer here.
Basically I use post message to tell the parent page to do the scrolling itself when inside Mobile Safari cross domain.
Is there a way to get elements which is:
Inside a div with overflow: scroll
Is in viewport
Just like the following picture, where active div (5,6,7,8,9) is orange, and the others is green (1-4 and >10) :
I just want the mousewheel event to add "active" class to div 5,6,7,8,9 (currently in viewport). View my JSFiddle
$('.wrapper').bind('mousewheel', function (e) {
//addClass 'active' here
});
You could do something like this. I would have re-factored it, but only to show the concept.
Firstly I would attach this to scroll event and not mousewheel. There are those among us that likes to use keyboard for scrolling, and you also have the case of dragging the scrollbar. ;) You also have the case of touch devices.
Note that with this I have set overflow:auto; on wrapper, thus no bottom scroll-bar.
With bottom scrollbar you would either have to live with it becoming tagged as in-view a tad to early, or tumble into the world of doing a cross-browser calculating of IE's clientHeight. But the code should hopefully be OK as a starter.
»»Fiddle««
function isView(wrp, elm)
{
var wrpH = $(wrp).height(),
elmH = $(elm).height(),
elmT = $(elm).offset().top;
return elmT >= 0 &&
elmT + elmH < wrpH;
}
$('.wrapper').bind('scroll', function (e) {
$('div.box').each(function(i, e) {
if (isView(".wrapper", this)) {
$(this).addClass('active');
} else {
$(this).removeClass('active');
}
});
});
Note that you should likely refactor in such a way that .wrapper height is only retrieved once per invocation, or if it is static, at page load etc.
Update; a modified version of isView(). Taking position of container into account. This time looking at dolphins in the pool.
»»Fiddle««
function isView(pool, dolphin) {
var poolT = pool.offset().top,
poolH = pool.height(),
dolpH = dolphin.height(),
dolpT = dolphin.offset().top - poolT;
return dolpT >= 0 && dolpT + dolpH <= poolH;
}
I have a strange issue that might have to do with jQuery document ready. Below is an html and script block that contains the usual social networking scripts. The Javascript block below displays the dd_outer div on the left edge of the body div, and when the browser window is shrunk, the div is faded out and the dd_footer div is faded in. The fadein and fadeout between the two divs works OK.
The problem is two fold: one issue is when the browser window is full width (1200px+), the Facebook script will not load and display consistently; it sometimes appears and sometimes doesn't, sometimes after a page reload and sometimes doesn't. (No browser or .htaccess caching is involved). Only the Facebook share fails to show consistently; all other services show OK.
The second problem that when the browser window is narrow - 650 px or so, when the dd_outer div is not displayed and the dd_footer div is - the footer div will not show on a page reload until the browser window is moved the smallest amount. Then the the div will display, Facebook share and all. For a mobile device, this is a problem because the browser window will be narrow to begin with and shouldn't need to be "nudged" to make the dd_footer div display.
This problem may have come into play because I have adapted this code from a WordPress plugin that used options to set the position of the dd_outer div and scroll height. That's the reason for the variables above the document ready call.
Is this the issue with what seems to be a document ready issue?
How can the variables be integrated into the script itself? It doesn't matter if they are hardcoded; I can change them when needed.
I'd throw this in a jsfiddle to demo but the divs won't realistically float with the window resizing.
I haven't included the CSS for clarity.
This is the html and social script block:
<div class='dd_outer'><div class='dd_inner'><div id='dd_ajax_float'>
<div class="sbutton"><script src="http://connect.facebook.net/en_US/all.js#xfbml=1"></script><fb:like layout="box_count" show_faces="false" font=""></fb:like></div>
<div class="sbutton">
Tweet<script type="text/javascript" src="http://platform.twitter.com/widgets.js"></script></div>
<div class="sbutton"><script type="text/javascript" src="https://apis.google.com/js/plusone.js"></script><g:plusone size="tall"></g:plusone></div>
<div class="sbutton"><script src="http://platform.linkedin.com/in.js" type="text/javascript"></script>
<script type="IN/Share" data-counter="top"></script></div>
</div></div></div>
In the footer is <div id="dd_footer">that contains the same social scripts as above</div> and are faded in and out by the script below:
This is the jQuery that positions the dd_outer social services to the left and fades it out and fades in the dd_footer div.
<script type="text/javascript">
var dd_top = 0;
var dd_left = 0;
var dd_offset_from_content = 70; var dd_top_offset_from_content = 10;
jQuery(document).ready(function(){
var $floating_bar = jQuery('#dd_ajax_float');
var $dd_start = jQuery('#dd_start');
var $dd_end = jQuery('#dd_end');
var $dd_outer = jQuery('.dd_outer');
// first, move the floating bar out of the content to avoid position: relative issues
$dd_outer.appendTo('body');
dd_top = parseInt($dd_start.offset().top) + dd_top_offset_from_content;
if($dd_end.length){
dd_end = parseInt($dd_end.offset().top);
}
dd_left = -(dd_offset_from_content + 55);
dd_adjust_inner_width();
dd_position_floating_bar(dd_top, dd_left);
$floating_bar.fadeIn('slow');
if($floating_bar.length > 0){
var pullX = $floating_bar.css('margin-left');
jQuery(window).scroll(function () {
var scroll_from_top = jQuery(window).scrollTop() + 30;
var is_fixed = $dd_outer.css('position') == 'fixed';
if($dd_end.length){
var dd_ajax_float_bottom = dd_end - ($floating_bar.height() + 30);
}
if($floating_bar.length > 0)
{
if(scroll_from_top > dd_ajax_float_bottom && $dd_end.length){
dd_position_floating_bar(dd_ajax_float_bottom, dd_left);
$dd_outer.css('position', 'absolute');
}
else if ( scroll_from_top > dd_top && !is_fixed )
{
dd_position_floating_bar(30, dd_left);
$dd_outer.css('position', 'fixed');
}
else if ( scroll_from_top < dd_top && is_fixed )
{
dd_position_floating_bar(dd_top, dd_left);
$dd_outer.css('position', 'absolute');
}
}
});
}
});
jQuery(window).resize(function() {
dd_adjust_inner_width();
});
var dd_is_hidden = false;
var dd_resize_timer;
function dd_adjust_inner_width() {
var $dd_inner = jQuery('.dd_inner');
var $dd_floating_bar = jQuery('#dd_ajax_float')
var width = parseInt(jQuery(window).width() - (jQuery('#dd_start').offset().left * 2));
$dd_inner.width(width);
var dd_should_be_hidden = (((jQuery(window).width() - width)/2) < -dd_left);
var dd_is_hidden = $dd_floating_bar.is(':hidden');
if(dd_should_be_hidden && !dd_is_hidden)
{
clearTimeout(dd_resize_timer);
dd_resize_timer = setTimeout(function(){ jQuery('#dd_ajax_float').fadeOut(); }, -dd_left);
jQuery('#dd_footer').fadeIn();
}
else if(!dd_should_be_hidden && dd_is_hidden)
{
clearTimeout(dd_resize_timer);
dd_resize_timer = setTimeout(function(){ jQuery('#dd_ajax_float').fadeIn(); }, -dd_left);
jQuery('#dd_footer').fadeOut();
}
}
function dd_position_floating_bar(top, left, position) {
var $floating_bar = jQuery('#dd_ajax_float');
if(top == undefined) top = 0 + dd_top_offset_from_content;;
if(left == undefined) left = 0;
if(position == undefined) position = 'absolute';
$floating_bar.css({
position: position,
top: top + 'px',
left: left + 'px'
});
}
</script>
jQuery .ready() does not wait for iframes and other external media to load. These social buttons tend to work by inserting an iframe. The load event does wait for iframes etc, so you could try using that event instead, i.e.
jQuery(window).load(function () {
/* put the code you had inside .ready() here */
});
The problem comes with your idea: $(document).ready() fires when the DOM is ready, not when all scripts are ready!
an idea would be to search for trigger of that social-APIs you are using or just delay your calculations (e.g. via setTimeout).
Keep in mind that they are asyncron, even if you specify "async" on the script-tag to be false, you still dont know when they will activate or are finished.
I suggest to use the standard DOM event window.onload if you want to make sure that all the external assets, scripts, images, etc. are loaded first before you do something:
window.onload = function () {
// your script that needs to run after all the external assets are loaded
}
Reference: https://developer.mozilla.org/en-US/docs/DOM/window.onload
I just ran into similar problems with the facebook script... I just used the integration in the HEAD-section with javascript and added an "asynchronous"-attribute to the javascript-embedding script which then fires an asynchronous "heeey, facebook is ready now, too"-event to my jQuery-eventqueue...
I can't help you in detail, because I don't totally understand what you WANT to do and would reorganize the whole code A LOT... so - contact me private (email/skype) or try figuring out... I used that lines of code: pastie.org/private/9m4b9eet1dzzkl6duqpkrg
I have a vertically-scrolling div within a page that also scrolls vertically.
When the child div is scrolled with the mouse wheel and reaches the top or bottom of the scroll bar, the page (body) begins to scroll. While the mouse is over the child div, I'd like the page (body) scroll to be locked.
This SO post (scroll down to the selected answer) demonstrates the problem well.
This SO question is essentially the same as mine, but the selected answer causes my page contents to noticeably shift horizontally as the scrollbar disappears and reappears.
I thought there might be a solution that leverages event.stopPropagation(), but couldn't get anything to work. In ActionScript, this kind of thing would be solved by placing a mousewheel handler on the child div that calls stopPropagation() on the event before it reaches the body element. Since JS and AS are both ECMAScript languages, I thought the concept might translate, but it didn't seem to work.
Is there a solution that keeps my page contents from shifting around? Most likely using stopPropagation rather than a CSS fix? JQuery answers are welcome as is pure JS.
here's what i ended up with. very similar to #mrtsherman's answer here, only pure JS events instead of jQuery. i still used jQuery for selecting and moving the child div around, though.
// earlier, i have code that references my child div, as childDiv
function disableWindowScroll () {
if (window.addEventListener) {
window.addEventListener("DOMMouseScroll", onChildMouseWheel, false);
}
window.onmousewheel = document.onmousewheel = onChildMouseWheel;
}
function enableWindowScroll () {
if (window.removeEventListener) {
window.removeEventListener("DOMMouseScroll", onArticleMouseWheel, false);
}
window.onmousewheel = document.onmousewheel = null;
}
function onChildMouseWheel (event) {
var scrollTgt = 0;
event = window.event || event;
if (event.detail) {
scrollTgt = -40 * event.detail;
} else {
scrollTgt = event.wheelDeltaY;
}
if (scrollTgt) {
preventDefault(event);
$(childDiv).scrollTop($(childDiv).scrollTop() - scrollTgt);
}
}
function preventDefault (event) {
event = event || window.event;
if (event.preventDefault) {
event.preventDefault();
}
event.returnValue = false;
}
i've noticed the scrolling doesn't match normal scrolling exactly; it seems to scroll a bit faster than without this code. i assume i can fix by knocking down wheelDeltaY a bit, but it's odd that it would be reported differently by javascript than it's actually implemented by the browser...
I usually do it with a small hack listening to the scroll event on the document: it resets the scroll height back to the original one - effectively freezing the document from scrolling but any inner element with overflow: auto will still scroll nicely:
var scrollTop = $(document).scrollTop();
$(document).on('scroll.scrollLock', function() {
$(document).scrollTop(scrollTop);
});
and then when I'm done with the inner scroll lock:
$(document).off('scroll.scrollLock');
the .scrollLock event namespace makes sure I'm not messing with any other event listeners on scroll.
Although this is an old question, here is how I do it with jQuery. This allows you to scroll a list within an outer list, or you can change the outer list to the document to do what the OP asked.
window.scrollLockHolder = null;
function lockScroll(id){
if (window.scrollLockHolder == null){
window.scrollLockHolder = $('#' + id).scrollTop();
}
$('#' + id).on('scroll', function(){
$('#' + id).scrollTop(window.scrollLockHolder);
});
}
function unlockScroll(id){
$('#' + id).off('scroll');
window.scrollLockHolder = null;
}
And you can use it like this:
<ul onmousemove="lockScroll('outer-scroller-id')" onmouseout="unlockScroll('outer-scroller-id')">
<li>...</li>
<li>...</li>
</ul>
what about this:
div.onmousemove = function() { // may be onmouseover also works fine
document.body.style.overflow = "hidden";
document.documentElement.style.overflow = "hidden";
};
div.onmouseout = function() {
document.body.style.overflow = "auto";
document.documentElement.style.overflow = "auto";
};