188
votes

How can I control the background image and colour of a body element within an iframe? Note, the embedded body element has a class, and the iframe is of a page that is part of my site.

The reason I need this is that my site has a black background assigned to the body, and then a white background assigned to divs that contain text. A WYSIWYG editor uses an iframe to embed content when editing, but it doesn't include the div, so the text is very hard to read.

The body of the iframe when in the editor has a class that isn't used anywhere else, so I'm assuming this was put there so problems like this could be solved. However, when I apply styles to class.body they don't override the styles applied to body. The weird thing is that the styles do appear in Firebug, so I've no idea what's going on!

Thanks

UPDATE - I've tried @mikeq's solution of adding a style to the class that is the body's class. This doesn't work when added to the main page's stylesheet, but it does work when added with Firebug. I'm assuming this is because Firebug is applied to all elements on the page whereas the CSS is not applied within iframes. Does this mean that adding the css after window load with JavaScript would work?

11
Possible duplicate of How to apply CSS to iframe?2540625
While it's not possible to touch anything in an iframe, loading that URL per Ajax into a <div> can at times be a workaround (if given CORS-Header are allowing for that)... (and „sanitizing“ the loaded data by regexp on the way. Yes, all hacky...)Frank Nocke
You can use javascript if the page domains match, but why not just put a style block in your inner page's HTML to override the colors you want changed? Just add more selectors in your override or use important! if all else fails, to override any color styles you want only on that one page...OG Sean

11 Answers

121
votes

An iframe is a 'hole' in your page that displays another web page inside of it. The contents of the iframe is not in any shape or form part of your parent page.

As others have stated, your options are:

  • give the file that is being loaded in the iframe the necessary CSS
  • if the file in the iframe is from the same domain as your parent, then you can access the DOM of the document in the iframe from the parent.
265
votes

The below only works if the iframe content is from the same parent domain.

The following jquery script works for me. Tested on Chrome and IE8. The inner iframe references a page that is on the same domain as the parent page.

In this particular case, I am hiding an element with a specific class in the inner iframe.

Basically, you just append a style element to the head section of the document loaded in a frame:

frame.addEventListener("load", ev => {
    const new_style_element = document.createElement("style");
    new_style_element.textContent = ".my-class { display: none; }"
    ev.target.contentDocument.head.appendChild(new_style_element);
});

You can also instead of style use a link element, for referencing a stylesheet resource.

27
votes

You cannot change the style of a page displayed in an iframe unless you have direct access and therefore ownership of the source html and/or css files.

This is to stop XSS (Cross Site Scripting)

12
votes

This code uses vanilla JavaScript. It creates a new <style> element. It sets the text content of that element to be a string containing the new CSS. And it appends that element directly to the iframe document's head.

Keep in mind, however, that accessing elements of a document loaded from another origin is not permitted (for security reasons) -- contentDocument of the iframe element will evaluate to null when attempted from the browsing context of the page embedding the frame.

var iframe = document.getElementById('the-iframe');
var style = document.createElement('style');
style.textContent =
  'body {' +
  '  background-color: some-color;' +
  '  background-image: some-image;' +
  '}' 
;
iframe.contentDocument.head.appendChild(style);
9
votes

An iframe has another scope, so you can't access it to style or to change its content with javascript.

It's basically "another page".

The only thing you can do is to edit its own CSS, because with your global CSS you can't do anything.

9
votes

Override another domain iframe CSS

By using part of SimpleSam5's answer, I achieved this with a few of Tawk's chat iframes (their customization interface is fine but I needed further customizations).

In this particular iframe that shows up on mobile devices, I needed to hide the default icon and place one of my background images. I did the following:

Tawk_API.onLoad = function() {
// without a specific API, you may try a similar load function
// perhaps with a setTimeout to ensure the iframe's content is fully loaded
  $('#mtawkchat-minified-iframe-element').
    contents().find("head").append(
     $("<style type='text/css'>"+
       "#tawkchat-status-text-container {"+
         "background: url(https://example.net/img/my_mobile_bg.png) no-repeat center center blue;"+
         "background-size: 100%;"+
       "} "+
       "#tawkchat-status-icon {display:none} </style>")
   );
};

I do not own any Tawk's domain and this worked for me, thus you may do this even if it's not from the same parent domain (despite Jeremy Becker's comment on Sam's answer).

1
votes

If you have control of the page hosting the iframe and the page of the iframe, you can pass a query parameter to the iframe...

Here's an example to add a class to the iframe based on whether or not the hosting site is mobile...

Adding iFrame:

var isMobile=$("mobile").length; //detect if page is mobile
var iFrameUrl ="https://myiframesite/?isMobile=" + isMobile; 

$(body).append("<div id='wrapper'><iframe src=''></iframe></div>");
$("#wrapper iframe").attr("src", iFrameUrl ); 

Inside iFrame:

//add mobile class if needed
var url = new URL(window.location.href);
var isMobile = url.searchParams.get("isMobile");
if(isMobile == "1") {
    $("body").addClass("mobile");
}
0
votes

I have a blog and I had a lot of trouble finding out how to resize my embedded gist. Post manager only allows you to write text, place images and embed HTML code. Blog layout is responsive itself. It's built with Wix. However, embedded HTML is not. I read a lot about how it's impossible to resize components inside body of generated iFrames. So, here is my suggestion:

If you only have one component inside your iFrame, i.e. your gist, you can resize only the gist. Forget about the iFrame.

I had problems with viewport, specific layouts to different user agents and this is what solved my problem:

<script language="javascript" type="text/javascript" src="https://gist.github.com/roliveiravictor/447f994a82238247f83919e75e391c6f.js"></script>

<script language="javascript" type="text/javascript">

function windowSize() {
  let gist = document.querySelector('#gist92442763');

  let isMobile = {
    Android: function() {
        return /Android/i.test(navigator.userAgent)
    },
    BlackBerry: function() {
        return /BlackBerry/i.test(navigator.userAgent)
    },
    iOS: function() {
        return /iPhone|iPod/i.test(navigator.userAgent)
    },
    Opera: function() {
        return /Opera Mini/i.test(navigator.userAgent)
    },
    Windows: function() {
        return /IEMobile/i.test(navigator.userAgent) || /WPDesktop/i.test(navigator.userAgent)
    },
    any: function() {
        return (isMobile.Android() || isMobile.BlackBerry() || isMobile.iOS() || isMobile.Opera() || isMobile.Windows());
    }
};

  if(isMobile.any()) {
    gist.style.width = "36%";
    gist.style.WebkitOverflowScrolling = "touch"
    gist.style.position = "absolute"
  } else {
    gist.style.width = "auto !important";
  }
}

windowSize();

window.addEventListener('onresize', function() {
    windowSize();
});

</script>

<style type="text/css">

.gist-data {
  max-height: 300px;
}

.gist-meta {
  display: none;
}

</style>

The logic is to set gist (or your component) css based on user agent. Make sure to identify your component first, before applying to query selector. Feel free to take a look how responsiveness is working.

0
votes

This should work with cross domain if you're the owner of the both

The trick here is to assign a global css variable to your body, to listen message with the new color, and then to change the global css variable once receive a message.

I'm using angular, but it should work with pure javascript

My use case was to show to the user what he how the color change would impact his website in the iframe before saving it

Domain A

@ViewChildren('iframeContainer') iframeContainer: QueryList<ElementRef>

sendDataToIframe(
  data = {
      type: 'colorChange',
      colors: {primary: '#000', secondary: '#fff'},
  },
): void {
  if (this.targetUrl)
    this.iframeContainer.first.nativeElement.contentWindow.postMessage(data) // You may use document.getElementById('iframeContainer') instead
}

Domain B

acceptedEditOrigins = [
  'https://my.origine.ccom', // Be sur to have a correct origin, to avoid xss injecto: https://en.wikipedia.org/wiki/Cross-site_scripting
]

constructor() {
// Listen to message
window.addEventListener('message', (event) => this.receiveMessage(event), false)
}

receiveMessage(event: MessageEvent) {
  if (this.acceptedEditOrigins.includes(event.origin))
    switch (event.data.type) {
      case 'colorChange': {
        this.setWebsiteConfigColor(event.data.colors)
      }
    }
}

setWebsiteConfigColor(colors: WebsiteConfigColors) {
  if (colors) {
    const root = document.documentElement
    for (const [key, value] of Object.entries(colors)) {
       root.style.setProperty(`--${key}`, value) // --primary: #000, --secondary: #fff
    }
  }
}

body {
  background-color: var(--primary);
}
-5
votes

Perhaps it's changed now, but I have used a separate stylesheet with this element:

.feedEkList iframe
{
max-width: 435px!important;
width: 435px!important;
height: 320px!important;
}

to successfully style embedded youtube iframes...see the blog posts on this page.

-28
votes

give the body of your iframe page an ID (or class if you wish)

<html>
<head></head>
<body id="myId">
</body>
</html>

then, also within the iframe's page, assign a background to that in CSS

#myId {
    background-color: white;
}