How to automatically adjust iframe height for embedded wordpress posts

I am currently working on a custom embed template for wordpress posts so I can embed them via wp discourse plugin in an iframe

Currently you can only add a fixed iframe height into the post. The post looks like this:

The HTML used:

<iframe src="https://wordpress-92041-921046.cloudwaysapps.com/growbox-dimensionieren/" width="1200" height="2000" "frameborder="0"></iframe>
  1. Is there any way to set up the iframe height in a variable manner to adjust to the embedded content size?
  2. Since it will be put into a wp discourse template (like @simon) did here , is there any way to set the iframe height in a dynamic way based on some parameters of the wp post? (character count or such?)
1 Like

I am trying to do this as well, as I realized if I embed my WordPress posts on Discourse, then I can just drive my users to Discourse and not to two different sites.

Has anyone figured out how to do this? I’ve been struggling for a while with this.

Are you using something like this for adding the iframe:

function your_namespace_publish_format_html( $output ) {
    global $post;

    if ( 'my_iframe_post_type' === $post->post_type) {
	ob_start();

	?>
    <iframe width="690" height="600" src="<?php echo esc_url( the_permalink() ); ?>" frameborder="0"></iframe>
	<?php
	$output = ob_get_clean();

	// Return an iframe for this post type.
	return $output;
    }

    // Return the default output, or do something else with it here.
    return $output;
}
add_filter( 'discourse_publish_format_html', 'your_namespace_publish_format_html' );

but wanting to set the height property based on the height of the post?

If so, how exact does it need to be? I’m wondering if you could just count the number of characters in the post, then add the height of any images or other elements that have a set height, then add a bit more for good measure. I might have some suggestions for how to do that.

It might also be possible to use Javascript to get the exact height of the post when rendered at a given width.

1 Like

Ah, I haven’t even got that far in terms of integrating it into the discourse_publish_format_html filter. I was just adding it manually to a post in Discourse and trying to figure out the javascript to read the height of the content inside the iframe and then resize the iframe.

I’ve seen quite a few tutorials for how to do that online but for some reason I’m unable to get that js to work on Discourse. I tried with the page change and decorateCookedElement in the api, and yet still can’t seem to make it work.

If possible, I’d prefer to do it in Discourse itself, so that I can resize iframes to full height if I embed from a non-WordPress site as well.

It’s an interesting problem. What happens if you manually edit the height attribute of an iframe element in a Discourse post? When you view the post after editing the height, does the new height get used, or do you have to rebake the Discourse post for the edited height to take effect?

Edit: it’s interesting enough that I’ll test this later on today.

1 Like

If I add it to the iframe tag with height="400px" then it will resize after I refresh the page. If I add it as height="100%" it doesn’t seem to do anything.

If I add CSS properties about the iframe, it seems to change the height properly as well, at least to what I manually enter.

Ugh, I got it to work. The problem was that I was using a custom class on the iframe tag and Discourse strips all those tags. I should have learned my lesson from a few days ago (Formatting posts to look more like Wordpress blog - #6 by jimkleiber). I’ve read that it’s a security reason for why custom HTML classes don’t work here but I’m not sure why :confused:

However, here’s the code for adjusting the height of an iframe with the .topic-body iframe selector (not sure if this code works for others, seems to work for me):

<script type="text/discourse-plugin" version="0.8.18">
    api.decorateCookedElement(
      element => {
        setTimeout(function() {
          let iframes = element.querySelectorAll('.topic-body iframe');
          if (iframes) {
            iframes.forEach(function(iframe) {
              iframe.onload = function() {
                let iframeDocument = this.contentDocument || this.contentWindow.document;
                let contentHeight = Math.max(
                  iframeDocument.body.scrollHeight,
                  iframeDocument.documentElement.scrollHeight
                ) + 'px';
                this.style.height = contentHeight;
              };
            });
          }
        }, 5000); // Adjust the delay as needed                  
      },
      { id: "component-id", onlyStream: true}
    );
</script>

EDIT: actually, it doesn’t work. I added height="4000px" in the iframe tag and that’s why it was working, I think.

1 Like

It’s a tricky problem. I don’t think it’s easy to access the iframe’s content from inside of Discourse. It might be possible to get it to work if the iframe’s source and your Discourse site are on different subdomains of the same domain though.

If I’m understanding the problem correctly, you’d need to set the document.domain on both the subdomain you’re pulling the content from, and in the script you’re running on Discourse to the root domain.

If the iframe’s source is actually your root domain, maybe try adjusting the script to:

<script type="text/discourse-plugin" version="0.8.18">
   document.domain = "your_root_domain.com"; // edit that to your domain
    api.decorateCookedElement(
      element => {

If the iframe’s domain is also a subdomain of your root domain, you’d need to set document.domain to the root domain on it as well.

For styling iframes (or anything) on Discourse, you can wrap the content in a div that has a data attribute:

<div data-full-height>
<iframe src="http://wp-discourse.test/zalg_iframe/this-is-a-test-this-is-only-a-test/" height="600" width="690"></iframe>
</div>

Theme CSS:

[data-full-height] > iframe {
      // optionally style outer iframe here: height, width, etc
     // unfortunately height: 100% won't work - the iframe's containing element doesn't have a set height.
}

If the document.domain approach doesn’t work, there may be other solutions - using window.postMessage to communicate between the iframe’s parent document and Discourse.

I don’t think my initial idea of calculating the iframe’s height on its source site will work - the width of the rendered iframe will vary depending on the device Discourse is viewed on.

1 Like

Hmm, my forum is a subdomain of my root domain, so I added the root domain as you suggested and got this error:

Uncaught DOMException: Failed to read a named property 'document' from 'Window': Blocked a frame with origin 

Now I’m trying to do the postMessage thing but failing. I think I need to better understand how Discourse javascript works, I think I assume it works like other sites and maybe it doesn’t, or maybe it’s just me not knowing how to do javascript that well, especially for cross-origin stuff lol.

Thank you for taking a look at this!

I’ve been wondering about this for a while. Here’s a proof of concept (note that it doesn’t solve the issue of removing scrollbars from iframes).

Add a script tag to the post that you’re embedding:

<script>
    function sendHeight() {
        const body = document.body,
            html = document.documentElement;

        const height = Math.max(body.scrollHeight, body.offsetHeight,
            html.clientHeight, html.scrollHeight, html.offsetHeight);

        window.parent.postMessage({
            'iframeHeight': height,
            'iframeId': 'zalgFrame' // Use a unique identifier if you have multiple iframes
        }, '*'); // Consider specifying the parent domain for security
    }

    // Send initial height
    window.onload = sendHeight;

    // Optional: Update height on resize or other events
    window.onresize = sendHeight;
</script>

I’m using the identifier "zalgFrame" in the script.

In your Discourse theme:

<script type="text/discourse-plugin" version="1.29.0">
let iframeHeight, iframeId;
window.addEventListener('message', (event) => {
  if (event.origin !== "http://wp-discourse.test") return; // my test domain, update it to your domain or comment out
  // gets the iframe height that's passed from `wp-discourse.test` and confirms that the iframeId matches the iframeID I set there    
  if (event.data.iframeHeight && event.data.iframeId === 'zalgFrame') {
      // visit the Discourse page with the iframe with your console open
      // you should see updated heights being sent from the parent site as you resize the window
      console.log("we got an event:" + event.data.iframeHeight); 
      iframeHeight = event.data.iframeHeight;
      iframeId = event.data.iframeId;
  }
  }, false);
</script>

In a Discourse post:

<div data-iframe-test-one>
<iframe src="http://wp-discourse.test/zalg_iframe/this-is-a-test-this-is-only-a-test/" width="100%" height="1659"></iframe>
</div>

So it’s possible to get the actual height of the rendered iframe from the parent window.

I don’t know how to get the height from the data from the event listener into a call to api.decorateCookedElement though. I’m not certain that would even work to remove the vertical scroll bar from long iframes. If I try hard coding a large height (1600px) into the iframe element, I’m still ending up with a scroll bar.

Edit: for the sake of completeness:

<script type="text/discourse-plugin" version="1.29.0">
api.decorateCookedElement(
  (e) => {
    let iframeHeight, iframeId;

    function handleMessage(event) {
      if (event.origin !== "http://wp-discourse.test") return;
      if (event.data.iframeHeight && event.data.iframeId === "zalgFrame") {
        iframeHeight = event.data.iframeHeight;
        iframeId = event.data.iframeId;
        // based on the assumption that there will only be 1 iframe wrapped in the data-zalgFram div
        let iframe = e.querySelector("[data-zalgFrame] iframe");
        if (iframe) {
          iframe.style.height = `${iframeHeight}px`;
        }
        // after setting the actual rendered height of the iframe
        // remove the event listener
        window.removeEventListener("message", handleMessage, false);
      }
    }
    window.addEventListener("message", handleMessage, false);
  },
  { id: "component-id", onlyStream: true }
);
</script>

For anything over ~1000px in height, there doesn’t seem to be any way of avoiding a scroll bar being added by Discourse, so I’m not recommending the approach.

I think the answer to the OP is that it’s kind of possible, but probably doesn’t accomplish much. (Except that I learned about the window.postMessage() method :slight_smile:

2 Likes

I appreciate the valiant efforts here and don’t want to dampen them, however I must admit that I feel a bit skeptical about the premise of this topic, i.e.

I have two (genuine) questions about this Jim:

  1. What’s the reason why you want an iframe here instead of the normal topic embed functionality?
  2. I’m curious why you would still have a Wordpress site at all if you don’t want users to consume content there?
1 Like

I can’t speak for the original topic author, but I can give answers in my case:

I hacked together quite a few plugins on WordPress that allows me to have a podcast player with interactive transcripts (the words highlight as the audio plays and one can click to jump to that part of the audio), interactive chapter/show notes, and a searchable/sortable/filterable playlist.

So to just embed them here without an iframe, I wouldn’t be able to access the javascript and all the styling that I’ve put into it.

Oh, and I find it much easier for me to hack together those things on WordPress than on Discourse, I really struggle with doing javascript and plugins here.

To host the podcast episodes, I’ll need the WordPress site regardless. But to whether I want users to consume content there, I’m not sure. Since I’ve been using Discourse for comments on WordPress, interactivity has dropped off. I used to have people post on WordPress comments, but Discourse requires them to cross a domain threshold, and then they’re interacting in a separate place. If Discourse made it easier for people to post in an embedded way on a WordPress forum, I’d probably focus on that.

I’m not sure if it’s needed, I just get the feeling that I want to have one main place for people to gather, and before thought it’d be WordPress with embedded Discourse comments/posting but now I’m thinking Discourse with embedded WordPress posts might be easier and more likely to inspire people to interact with each other.

2 Likes

Cool!

Why not?

I understand! Nevertheless, depending on your answer to my previous question (“Why not?”) getting them properly embedded in a Discourse post would be a more stable approach than a dynamic iframe.

I’m sorry to hear about the drop off, and I also understand what you mean here. Simon’s broader topic on this theme comes to mind

1 Like

I mean, maybe I could? I struggled to get an audio player to use mediaelement.js before on here, I think I don’t understand the plugin api very well. It just seems like a lotttttt of work that maybe I could do in the long run, but right now, it’s actually looking pretty good with the iframe embed. The main challenge would be searchability of the text that’s embedded in the iframe, but I was thinking to publish that text in the post and hide it or put it under an accordion, so it would still show up in searches.

Plus, I think the bigger problem is actually that so many of the HTML classes get stripped out when the content is cooked (or whatever the lingo is lol), and so just simply trying to publish the WordPress post here and using a similar CSS seems to require a lot of rewriting, which inspired me to write this:

1 Like

I see. Let me have a think about this for a bit. I don’t have any significant insight on dynamic iframes beyond what Simon has already suggested, however your case is making me think a bit.

1 Like

It’s maybe worth noting that I’m actively working on this (using Discourse to power the comment system for websites). Mostly focused on headless WordPress sites at the moment, but the general approach might be helpful for regular WordPress and non-WordPress sites.

2 Likes

I can’t remember where I saw it, but there’s a hidden max-height of I think 1000px, maybe on the cooked content?

So perhaps that is messing with your solution.

I’ll take a look tomorrow :pray:t2:

1 Like

It’s on iframe elements:

iframe {
  max-width: 100%;
  max-height: #{"min(1000px, 200vh)"};
}

That can be fixed in a theme by targeting iframes with the data attribute:

[data-zalgFrame] > iframe {
    max-height: 100%;
    border: none;
}

That change needs to be made to display longer iframes, but the scroll bars I was seeing were coming from the iframe source page. The only way I’m getting good results is to create embed versions of posts on my blog - basically remove everything except the post content and mess around with the styles a bit. For example, with a WordPress custom post type:

single-zag_iframe.php
<?php
if ( have_posts() ) : while ( have_posts() ) : the_post();
?>
<style>
    body {
        overflow: hidden;
        height: 100%;
    }
    article.zalg-iframe {
        width: 100%;
        height: 100%;
        margin-left: auto;
        margin-right: auto;
        font-size: 1.25em;
        word-break: break-word;
    }
    article.zalg-iframe img {
        max-width: 100%;
        height: auto;
    }
</style>
<article class="zalg-iframe">
    <?php
    the_content();
    ?>
</article>
<script>
    function sendHeight() {
        const body = document.body,
            html = document.documentElement;
        // a bit of a crap shoot, I think `scrollHeight` is the correct target for this case
        const height = Math.max(body.scrollHeight, body.offsetHeight,
            html.clientHeight, html.scrollHeight, html.offsetHeight);

        window.parent.postMessage({
            'iframeHeight': height,
            'iframeId': 'zalgFrame'
        }, '*');
    }

    // Send initial height
    window.onload = sendHeight;

    // Optional: Update height on resize or other events
    window.onresize = sendHeight;
</script>
<?php
endwhile; endif;

It might take a bit of fiddling around to get this to work on a production site, but it seems worth looking into some more.