window.matchMedia not works in iframe - javascript

I'm detecting device orientation using window.matchMedia. The following code works as intended - every orientation change is logged to console with correct value:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Test App</title>
</head>
<body>
<script type="application/javascript">
let isIframe= () => {
try {
return window.self !== window.top;
} catch (e) {
return true;
}
}
let onOrientationChange = () => {
const isLandscape = window.matchMedia("(orientation: landscape)").matches;
console.log("Event: " + (isIframe() ? "Iframe " : "") + "landscape:" + isLandscape);
}
let mediaQueryList = window.matchMedia("(orientation: landscape)");
console.log("Onload: " + (isIframe() ? "Iframe " : "") + "landscape:" + mediaQueryList.matches);
mediaQueryList.addListener(onOrientationChange);
</script>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">Hello World in Iframe</div>
</body>
</html>
But when I run that page in iframe, callback registered using addListener is not fired. In iframe, I only get singular log line - Onload: Iframe landscape:true, regardless of the device orientation.
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">Hello World</div>
<iframe id="game" src="iframeContent.html" frameborder="0" style="width: 960px; height: 600px;"></iframe>
</body>
I'm using addListener instead of addEventListener, because the second one function is not working on all Safari versions.
Tested on Safari 14 and on Dev Tools of Chrome and Firefox.
My question is - why addListener callback is not invoked in iframe.
Thank you.

If the iframe does not get it's size to change because it has fixed width and height, thus resize related events cannot be triggered inside it including MediaQueryList events regarding orientation.
You can do two things to get this working; you can make your iFrame width and height to be 100%, or you can let the media query detection code inside the main window and pass the orientation state using postMessage when it triggers a change event.
1) Changing iFrame size to 100% so it resizes when landscape/portrait orientation event triggers
In the main page, make the body full height and the iframe full width/height (using CSS).
body {
height: 100vh;
margin: 0;
}
iframe {
width: 100%;
height: 100%;
}
Live example that you can test: https://zikro.gr/dbg/so/65704468/
2) Media query detection on the main page and use postMessage to send a message to iFrame when orientation event triggers
index.html:
<iframe src="iframe.html"></iframe>
<script>
let iframe = document.querySelector('iframe');
let onOrientationChange = () => {
iframe.contentWindow.postMessage({
isLandscape: window.matchMedia("(orientation: landscape)").matches
}, '*');
}
iframe.addEventListener('load', onOrientationChange);
const mediaQueryList = window.matchMedia("(orientation: landscape)");
mediaQueryList.addListener(onOrientationChange);
</script>
iframe.html:
<script>
window.addEventListener("message", (event) => {
if (event.data.isLandscape) {
console.log('iFrame Landscape');
} else {
console.log('iFrame Portrait');
}
});
</script>
Live example that you can test: https://zikro.gr/dbg/so/65704468/pm/

Related

How to disable CSS media queries when in desktop mode

The following media query isn't working. I want it to only affect devices less than 415px, but it's affecting all of my code, i.e. devices >415px
The Code:
#media only screen and (max-width: 415px) {
.anchor-offset {
height: 80px;
margin-top: -80px;
}
}
At the moment this is applied to all of my HTML elements which have the class .anchor-offset.
Thank you in advance.
If you want that this media queries will be available only in mobile devices, i would use JS to identify the device and load the css queries file. For example, something like this:
const ifIsMobile = { // detect the mobile devices
Android: function() {
return navigator.userAgent.match(/Android/i);
},
BlackBerry: function() {
return navigator.userAgent.match(/BlackBerry/i);
},
iOS: function() {
return navigator.userAgent.match(/iPhone|iPad|iPod/i);
},
Opera: function() {
return navigator.userAgent.match(/Opera Mini/i);
},
Windows: function() {
return navigator.userAgent.match(/IEMobile/i);
},
any: function() {
return (ifIsMobile.Android() || ifIsMobile.BlackBerry() || ifIsMobile.iOS() || ifIsMobile.Opera() || ifIsMobile.Windows());
}};
const loadMobileCss = () => { // add the link tag to load mobilestyles.css
const linke = document.createElement("link");
linke.rel = "stylesheet";
linke.href = "mobilestyles.css";
document.getElementsByTagName("head")[0].appendChild(linke);
}
if (ifIsMobile.any()) loadMobileCss(); // if the device is mobile, load mobilestyles.css with the function loadMobileCss()
You have to put this code in the head of your web to make this work.
You can try to use a container with a fixed size if you would like to use it for coding purposes. personally i would use a mobile environment or flex/responsive coding.
<!DOCTYPE html>
<html>
<title>W3.CSS</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<body>
<div style="width: 415px"class="w3-container w3-red">
<p>This is a container with max output of 415px. this size can not be exeeded.</p>
</div>
</body>
</html>

Pausing a custom iframe video with Javascript

So I am embedding a video into a custom iframe, and I'm not using youtube, vimeo or any of those so I can't use their APIs. I am making an idle-timer for it, so when the user hasnt acted in X amount of time, it will bring up a confirm window asking if they want to keep watching or restart. However, while this window is up, I want the video to pause, which is proving surprisingly difficult. It also pretty much needs to be cross-domain as I will be serving the videos with an s3 bucket.
I have seen many threads saying this is basically not possible, but I find that hard to believe. Is it true?
Here's my code (the main part I need help with is pauseVideo() near the bottom):
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>HRMSC</title>
</head>
<body>
<iframe class="iframe" id="primaryVideo" src="amazon-s3-video-link.mp4"
width="1000"
height="562.5">
<p> Your browser does not support iframes. </p>
</iframe>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js">
</script>
<script type="text/javascript" src="./IdleScript.js">
</script>
</body>
</html>
IdleScript.js :
var idleTime = 0;
var clickIframe = window.setInterval(checkFocus, 100);
var idleInterval = setInterval(timerIncrement, 600); // 1 second
var i = 0;
function checkFocus() {
if(document.activeElement == document.getElementById("primaryVideo")) {
idleTime = 0;
console.log("clicked "+(i++));
$('#primaryVideo').blur();
}
}
function timerIncrement() {
idleTime = idleTime + 1;
if (idleTime > 5) { // seconds
console.log("restart?");
if (this.resetInterstitial()){
idleTime = 0;
window.location.reload();
}
else{
idleTime = 0;
console.log("keep watching");
}
}
}
var pauseVideo = function ( element ) {
// WHAT CAN I DO HERE?
console.log("pause!");
// WHAT CAN I DO HERE?
};
function resetInterstitial(){
pauseVideo(primaryVideo);
return confirm("You haven't tapped anything in a while. Do you want to keep watching or start from the beginning?");
}
Use a <video>-tag: https://www.w3schools.com/tags/tag_video.asp
and use the build-in javascript functions https://www.w3schools.com/tags/av_met_pause.asp

Is there a way to get drag and drop data in safari 11 or is that broken?

I created an webpage that lets users drag and drop images from a website into a div.
If I drag an image of a potato from google images into the box of this webpage , the image's url should get logged to the jconsole.
This works perfectly in windows but not on mac.
Using safari 11 on mac, getData never returns anything
Using Chrome on mac, getData only returns with text/html
using Firefox on mac, getData returns both text/plain and text/html
I'm fine with chrome and firefox.
My problem is that getData never returns anything no matter what I have tried so far.
Code:
function dragenterhandle(elem, event) {
event.preventDefault();
console.log("DragEnter Called\n");
return true;
}
function dragoverhandle(elem, event) {
event.preventDefault();
//console.log("Dragover Called\n");
return true;
}
function ondrophandle(elem, event) {
event.preventDefault();
console.log("ondrop Called\n");
var a = event.dataTransfer.getData("text");
console.log("Value of a:\n" + a);
}
function dragstarthandler(event) {
console.log("dragstart Called\n");
event.dataTransfer.effectAllowed = "all";
return true;
}
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<style type="text/css">
.img-bx {
border-style: dashed;
word-wrap: break-word;
width: 100%;
min-height: 500px;
}
</style>
</head>
<body>
<div class='img-bx' ondragenter="dragenterhandle(this, event);" ondragover="dragoverhandle(this, event);" ondrop="ondrophandle(this, event);" ondragstart="dragstarthandler(event);">
</div>
<script src="eng.js"></script>
</body>
</html>

`base` tag causes iframe to open as new window in Internet Explorer

I have a problem with the base tag which only affects Internet Explorer (versions 8, 9 and 10).
The following code is used to open dynamic content in an iframe and it functions correctly in Chrome and Firefox. It also functions correctly in Internet Explorer, but only without the <base target="_blank"/> tag. The inclusion of this tag causes the iframe to open as a new window (which makes sense, however this is not what I am trying to do.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<base target="_blank"/>
</head>
<body>
<div id="main"></div>
<script type="text/javascript">
function load_iframe(name, height, width) {
var div = document.getElementById(name);
var ifrm = document.createElement('iframe');
ifrm.id = 'iframe_' + name;
ifrm.frameBorder = 0;
ifrm.scrolling = 'no';
ifrm.noresize = 'noresize';
ifrm.marginheight = 0;
ifrm.marginwidth = 0;
if (height !== 0) {
ifrm.height = height;
}
if (width !== 0) {
ifrm.width = width;
}
div.appendChild(ifrm);
content = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"><html><head></head><body></body></html>';
if (/msie/.test(navigator.userAgent.toLowerCase()) || window.opera) {
ifrm.contentWindow.contents = content;
return ifrm.src = 'javascript:window["contents"]';
} else {
doc = ifrm.contentDocument;
doc.open();
doc.write(content);
doc.close();
return ifrm;
}
}
load_iframe('main', 250, 300);
</script>
</body>
</html>
How can I fix this issue? Unfortunately, I couldn't get the code to work in a fiddle, perhaps because it relies on <base/> being in the <head>.
I just removed the /msie/.test(navigator.userAgent.toLowerCase()) || part, and works fine on IE8. Are you sure that you need this piece of code?
However, whether you don't want remove this piece, you can remove the base tag and add it after you create the iframe:
load_iframe('main', 250, 300);
//and then:
var hd = document.getElementsByTagName("head")[0];
var bs = document.createElement("base");
bs.target= "_blank";
hd.appendChild(bs);
I tested on chrome, IE8, IE11 and works perfectly!
As said in comments target="_blank" makes the browser open a new window or tab (depends on your configuration).
Just remove it or replace by self (tested on IE8):
<base target="_self"/>

Internet Explorer 8 Messing Up My Bulletgraphs

I've created HTML and JavaScript files to display bulletgraphs, using the 'canvas' HTML5 tag. I've tried it in Chrome and it works nicely and changes width along with the size of the browser. I have to have this working in IE8, too, so I've used Excanvas, which is working in all except one way: when I resize the browser I get remnants of the valueIndicator. This only happens on IE8.
I've tried looking round for information on redrawing the canvas but I don't think this is the issue. Can anyone see where I'm going wrong, please?
EDIT
I'm keeping the complete code at the bottom, however, following advice I've cut my code down somewhat.
In IE8 it looks like this:
In Chrome it looks like this:
When I refresh the IE8 page it looks OK again.
Cut-down Bulletgraph.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Bulletgraph</title>
<!--[if IE]><script src="excanvas.js"></script><![endif]-->
</head>
<body>
<canvas id="graph1"></canvas>
<script src="Scripts.js"></script>
<script>
drawGraphs();
window.onresize=function() { drawGraphs() };
function drawGraphs() {
drawBulletGraph(getScreenWidth(),300,1000,350,"graph1");
}
</script>
</body>
</html>
Complete code:
Cut-down Scripts.js:
function drawBulletGraph (cwidth, left, right, loValue, id) {
var canvas=document.getElementById(id);
var cheight=30;
var multiplier=cwidth/(right-left);
canvas.width=cwidth;
canvas.height=cheight;
var valueIndicator=canvas.getContext("2d");
valueIndicator.lineWidth="1";
valueIndicator.moveTo((loValue-left)*multiplier,0);
valueIndicator.lineTo((loValue-left)*multiplier,cheight);
valueIndicator.fill();
valueIndicator.stroke();
}
function getScreenWidth () {
return (window.innerWidth || document.documentElement.clientWidth)/7;
}
Bulletgraph.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Bulletgraph</title>
<!--[if IE]><script src="excanvas.js"></script><![endif]-->
</head>
<body>
<canvas id="graph1"></canvas><br>
<canvas id="graph2"></canvas>
<script src="Scripts.js"></script>
<script>
drawGraphs();
window.onresize=function() { drawGraphs() };
function drawGraphs() {
drawBulletGraph(bgWidth(getScreenWidth()),300,400,450,600,700,1000,800,350,850,"graph1");
drawBulletGraph(bgWidth(getScreenWidth()),250,450,500,650,700,1200,600,350,850,"graph2");
}
</script>
</body>
</html>
Scripts.js:
function drawBulletGraph (cwidth, left, loRed, loAmber, hiAmber, hiRed, right, value, loValue, hiValue, id) {
var canvas=document.getElementById(id);
var cheight=16;
var colour="#008000";
if (value <= loRed || value >= hiRed)
{
colour="#FF0000";
}
else if (value <= loAmber || value >= hiAmber)
{
colour="#FFA500";
}
var multiplier=cwidth/(right-left);
canvas.width=cwidth;
canvas.height=cheight;
var red=canvas.getContext("2d");
red.fillStyle="#F4C3C6";
red.fillRect(0,0,cwidth,cheight);
var amber=canvas.getContext("2d");
amber.fillStyle="#F4F6C6";
amber.fillRect((loRed-left)*multiplier,0,(hiRed-loRed)*multiplier,cheight);
var green=canvas.getContext("2d");
green.fillStyle="#CCE5CC";
green.fillRect((loAmber-left)*multiplier,0,(hiAmber-loAmber)*multiplier,cheight);
var valueIndicator=canvas.getContext("2d");
valueIndicator.fillStyle=colour;
valueIndicator.strokeStyle=colour;
valueIndicator.lineWidth="2";
valueIndicator.moveTo((loValue-left)*multiplier,0);
valueIndicator.lineTo((loValue-left)*multiplier,cheight);
valueIndicator.moveTo((loValue-left)*multiplier,cheight/2);
valueIndicator.lineTo((hiValue-left)*multiplier,cheight/2);
valueIndicator.moveTo((hiValue-left)*multiplier,0);
valueIndicator.lineTo((hiValue-left)*multiplier,cheight);
valueIndicator.moveTo(((value-left)*multiplier)-(cheight/2),cheight/2);
valueIndicator.stroke();
valueIndicator.lineWidth="1";
valueIndicator.lineTo((value-left)*multiplier,cheight);
valueIndicator.lineTo(((value-left)*multiplier)+(cheight/2),cheight/2);
valueIndicator.lineTo((value-left)*multiplier,0);
valueIndicator.lineTo(((value-left)*multiplier)-(cheight/2),cheight/2);
valueIndicator.fill();
valueIndicator.stroke();
}
function getScreenWidth () {
return window.innerWidth || document.documentElement.clientWidth;
}
function bgWidth (screenWidth) {
var graphWidth=screenWidth/7;
if (graphWidth<70) {graphWidth=70;}
if (graphWidth>260) {graphWidth=260;}
return graphWidth;
}
I've managed to get a solution. It feels like a bit of a hack but it does the trick. Basically it involves drawing a white line round the canvas and filling it each time it's drawn again. The following code goes between the var valueIndicator=canvas.getContext("2d"); and the valueIndicator.lineWidth="1"; lines:
valueIndicator.fillStyle="#FFFFFF";
valueIndicator.strokeStyle="#FFFFFF";
valueIndicator.moveTo(0,0);
valueIndicator.beginPath();
valueIndicator.lineTo(0,cheight);
valueIndicator.lineTo(cwidth,cheight);
valueIndicator.lineTo(cwidth,0);
valueIndicator.closePath();
valueIndicator.fill();
valueIndicator.stroke();
valueIndicator.strokeStyle="#000000";
I've tried it in the full code and it works. If anyone has a more elegant solution, and I'm sure there must be many, I would still love to see them.

Categories

Resources