I’ve described my previous approach in How to Embed Twine on Your WordPress Website. It works but not in Firefox (I had to implement fallback to a fixed height). And the height of the parent page remains the same instead of changing with each Twine passage. Nevertheless, my first Twine embed tutorial provides basic pointers so I’d recommend checking it out before reading further.

Before we begin, visit my About page and see how it works. That way you’ll know what to expect.

One last thing, I’m using Twine story format: Harlowe 1.2.4. There are differences between Harlowe, Chapbook, Snowman and SugarCube. I can’t promise you this is going to work with a different story format. Give it a try and let me know!

Responsive IFRAME

How can we get the parent page to scale automatically with each passage?

Edit your Twine story

Here is how it’s done. Add a new passage to your Twine story. Tag it “footer”.

Passages tagged as footer are automatically appended after each active passage:

It is often very useful to want to reuse a certain set of macro calls in every passage, or to reuse an opening block of text. You can do this by giving the passage the special tag header , or footer . All passages with these tags will have their source text included at the top (or, for footer , the bottom) of every passage in the story, as if by an invisible (display:) macro call.

We’re going to paste some JavaScript in the footer passage. Footer code is executed every time a passage is changed.

<script> var passage = document.getElementsByTagName("tw-passage")[0];//get passage var newHeight = passage.offsetHeight;//read its height if(newHeight<500){newHeight=500;}//minimum height newHeight = newHeight + 88;//margin on tw-story is 2x40.63 window.parent.postMessage(["setHeight", newHeight], "*");//send message to parent page </script>

Make sure to include script HTML tag.

What’s happening in the script?

First, we find the HTML element “tw-passage” and read its offsetHeight. Then we’re going to correct the height to be at least 500. I believe this is a reasonable minimum height for passage. In the next step, we add 88 to make sure we include the width of the margin above and below the passage. Feel free to adjust both magic numbers as you see fit. The real magic happens in the last step. This is where we send message setHeight with the value of newHeight to the parent page.

To be completely honest, I’ve put my code above in function and call it with a slight delay of 50ms like this:

<script> function RLUpdateHeight(){ var passage = document.getElementsByTagName("tw-passage")[0]; var newHeight = passage.offsetHeight; if(newHeight<500){newHeight=500;} newHeight = newHeight + 88;//margin on tw-story is 2x40.6333 window.parent.postMessage(["setHeight", newHeight], "*"); } setTimeout(RLUpdateHeight, 50); </script>

Edit the parent website in WordPress

Now it’s time to play with the parent page. This is the page where we’ll include the Twine iframe.

Please note, that I’m using the latest (as of November 2019) WordPress version with Gutenberg editor.

Open WordPress and edit the page where you want to embed your Twine story.

Currently, there is an issue with Gutenberg. It keeps adding <br> tags in Code Editor view. This would mess up our JS.

No worries! Remain in Visual Editor and insert “Custom HTML” block.

It’s time to add the code:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script> <script type="text/javascript"> window.addEventListener('message', function(e) { //find iframe element with jquery var $iframe = jQuery("#my_iframe"); //load parameters var eventName = e.data[0]; var data = e.data[1]; //choose action based on event name switch(eventName) { case 'setHeight': $iframe.height(data); //change the height break; } }, false); </script> <div><iframe id="my_iframe" src="https://www.romanluks.eu/about/TwineAbout.html" scrolling="yes" width="100%"></iframe></div>

What’s happening here?

First, we need to load jQuery from Google CDN. Then we’ll add event listener to receive messages from the iframe. Within this listener, we find iframe, load parameters and add case what should happen when we receive setHeight message. This is the one we’re sending from the footer passage in Twine. Potentially, you can add more cases to react to different messages. The last step inside the listener is to change the iframe height.

Kudos to user Marty Mulligan who provided this code on StackOverflow.

Below the script, we include our Twine iframe. Make sure to change the URL to point to your own Twine story.

You might notice, I’ve enabled scrolling. This is because sometimes passage doesn’t report its height correctly. If this happens, iframe scrollbar will appear and user/visitor will be able to read the entire passage. It’s ugly but hey! At least it remains usable. The issue happens when passage includes an image. Not sure why it happens. I suspect a responsive image size to be the culprit. I might fix it sometime in the future. No promises!

And that’s it, now your Twine iframe height should auto-adjust to the height of active passage.

jQueryless version

<script type="text/javascript"> window.addEventListener('message', function(e) { //find iframe var myIframeEl = document.getElementById("my_iframe"); //load parameters var eventName = e.data[0]; var data = e.data[1]; //choose action based on event name switch(eventName) { case 'setHeight': fixData = data + 30;//magic-number adjustment for style.height document.getElementById("my_iframe").style.height = fixData + "px"; break; } }, false); </script> <div><iframe id="my_iframe" src="https://www.romanluks.eu/about/TwineAbout.html" scrolling="yes" width="100%"></iframe></div>

Autoscroll

With the automatic height adjustment, a UX issue emerges. Imagine reading a very long passage. There are links at the very bottom. It takes a while to get there. You click a link and voilá, a new passage is loaded. A much shorter passage. Now, you have to scroll all the way up! Cumbersome and #BadUX.

Let’s fix that!

We need to edit our code slightly:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script> <script type="text/javascript"> var myIframeTop = null; window.addEventListener('message', function(e) { var $iframe = jQuery("#my_iframe"); var eventName = e.data[0]; var data = e.data[1]; switch(eventName) { case 'setHeight': $iframe.height(data); window.scrollTo({ top: myIframeTop, left: 0, behavior: 'smooth' }); break; } }, false); function cacheIframe(){ var position = myIframeEl.getBoundingClientRect(); myIframeTop = position.top - 100; } setTimeout(cacheIframe, 50); </script> <div><iframe id="my_iframe" src="https://www.romanluks.eu/about/TwineAbout.html" scrolling="yes" width="100%"></iframe></div>

jQueryless version

<script type="text/javascript"> var myIframeEl = null; var myIframeTop = null; window.addEventListener('message', function(e) { var eventName = e.data[0]; var data = e.data[1]; switch(eventName) { case 'setHeight': fixData = data + 30;//magic-number adjustment for style.height document.getElementById("my_iframe").style.height = fixData + "px"; window.scrollTo({ top: myIframeTop, left: 0, behavior: 'smooth' }); break; } }, false); function cacheIframe(){ myIframeEl = document.getElementById("my_iframe"); var position = myIframeEl.getBoundingClientRect(); myIframeTop = position.top - 100; } setTimeout(cacheIframe, 50); </script> <div><iframe id="my_iframe" src="https://www.romanluks.eu/about/TwineAbout.html" scrolling="yes" width="100%"></iframe></div>

We’ve declared two global variables, one for the iframe, and one for its top position.

Inside the event listener, within setHeight behavior, we’ve added function scrollTo, which does the actual scrolling.

The whole new section is cacheIframe function and 50ms timeout. This is function is executed only once. It calculates the initial vertical position of the iframe and saves it for later use by the scrollTo function. I had to adjust the value by 100 to make it work.

Please note, autoscroll works in Firefox and Chrome. It doesn’t work in Edge.

And that’s it, folks. Enjoy your embedded Twine stories.