/blog/

2022 0409 Failed Experiment: Svg Tweets: Second attempt: Calculate the size in JavaScript

(This is part 2 in a set of failed experiments trying to generate SVG tweets in Hugo. I eventually abandoned this goal, and generate HTML archives of tweets instead.)

You should be able to calculate the size of things via javascript right?

I tried generating <object> and <iframe> embeds and calculating the resulting size in JavaScript, but it doesn’t work.

Setup

Add this to config.yaml:

mediaTypes:
  ...
  text/html+tweet:
    suffixes: ["tweet.html"]

outputFormats:
  ...
  HtmlTweet:
    mediatype: text/html+tweet
    suffix: tweet.html
    isPlainText: true
    notAlternative: false

Add this to content/archive/tweets/_index.md:

type: tweets
cascade:
    type: tweets
    outputs:
        - HTML
        - SVG
        - HtmlTweet

Here’s layouts/tweets/single.tweet.html:

{{- $tweet := index .Site.Data.tweets .Params.tweetid -}}
<html style="height: 100%; overflow: visible;">
<head>
  <base target="_parent" />
  <title>Tweet from @{{ $tweet.username}} on {{ .Date.Format .Site.Params.dateform }}</title>
  <style>
    {{/* TODO: split out CSS so that I don't have to include all website CSS */}}
    {{- $inlineTweetStandalone := resources.Get "inlineTweetStandalone.scss" | resources.ToCSS | minify -}}
    {{- $inlineTweetStandalone.Content | safeCSS -}}
  </style>
  <script>
    /* Display explanation text for an inline tweet
    */
    function toggleInlineTweetExplanation(button) {
      const expl = button.parentElement.getElementsByClassName('inline-tweet-about-explanation')[0];
      console.log(expl.style.display);
      if (expl.style.display === 'none'){
        expl.style.display = 'block';
      } else {
        expl.style.display = 'none';
      }
    }

    /* Code to expand/collapse all tweets on a page
    */
    function setInlineTweetCollapsibleDisplay(state) {
      const tweets = document.getElementsByClassName("inline-tweet-collapsible");
      for (const tweet of tweets) {
        switch (state) {
          case "open": tweet.open = true; break;
          case "closed": tweet.open = false; break;
          default: console.log(`Unknown state: ${state}`);
        }
      }
    }

    /* Notify the parent of this iframe (if any) that the size has changed
     */
    // function notifyParentFrameSizeChange() {
    //   const msg = {
    //     mrlevent: "resize",
    //     w: window.innerWidth,
    //     h: window.innerHeight,
    //   }
    //   console.log(window.offsetHeight)
    //   window.parent.postMessage(msg, "*");
    // }
    // window.addEventListener('resize', notifyParentFrameSizeChange);


  </script>
</head>
<body>
<section id="content" class="single-tweet">
  {{ partial "inlineTweet.html" (dict "ctx" . "tweetId" .Params.tweetid) }}
</section>


  <script>
    const resizeObserver = new ResizeObserver(e => {
      const w = window.innerWidth;
      const h = window.innerHeight;
      const msg = {mrlevent: "resize", w, h}
      console.log(`In resizeObserver, ${w}x${h}`)
      console.log(window.offsetHeight)
      window.parent.postMessage(msg, "*");
    });
    resizeObserver.observe(document.documentElement);
  </script>


</body>
</html>

Here’s layouts/tweets/single.html that will inline the above:

{{- partial "header.html" . -}}
{{- $formattedDate := .Date.Format .Site.Params.dateform -}}
{{- $tweet := index .Site.Data.tweets .Params.tweetid -}}

<section id="content" class="single-tweet">
  <p><a href='{{ ref . "/archive/tweets" }}'>About archived tweets</a></p>
  <h1 id="single-tweet-page-h1">Tweet from @{{ $tweet.username}} on {{ .Date.Format .Site.Params.dateform }}</h1>
  <!--object id="embedded-tweet-html-object" class="embedded-tweet-html-object" style="width: 100%;" data="index.tweet.html" type="text/html"></object-->
  <iframe id="embedded-tweet-html-iframe" class="embedded-tweet-html-iframe" style="width: 100%;" scrolling="no" src="index.tweet.html"></iframe>
</section>

<script>
  const tweetHtmlObject = document.getElementById("embedded-tweet-html-object");
</script>


{{ partial "taxonomyList.html" . }}
{{ partial "footer.html" . }}

I tried a bunch of different things. Here’s a version of single.html that tries various things at the same time.

{{- partial "header.html" . -}}
{{- $formattedDate := .Date.Format .Site.Params.dateform -}}
{{- $tweet := index .Site.Data.tweets .Params.tweetid -}}

<section id="content" class="single-tweet">
  <p><a href='{{ ref . "/archive/tweets" }}'>About archived tweets</a></p>

  <h1>Experiments</h1>

  <h2>An HTML page in an iframe</h2>

  <iframe id="embedded-tweet-iframe" scrolling="no" class="" src="index.tweet.html"></iframe>

  <h2>An HTML page as an object</h2>

  <object id="embedded-tweet-html-object" class="" data="index.tweet.html" type="text/html"></object>

  <h2>The SVG as an object</h2>

  <object id="embedded-tweet-svg-object" data="index.svg" type="image/svg+xml"></object>

  <h2>The SVG as an img</h2>

  <img id="embedded-tweet-svg-img" style="" src="index.svg" />

  <h1 id="single-tweet-page-h1">Tweet from @{{ $tweet.username}} on {{ .Date.Format .Site.Params.dateform }}</h1>

  {{ partial "inlineTweet.html" (dict "ctx" . "tweetId" .Params.tweetid) }}

</section>


<script>
  const tweetHtmlIframe = document.getElementById("embedded-tweet-iframe");
  const tweetHtmlObject = document.getElementById("embedded-tweet-html-object");
  const tweetSvgObject = document.getElementById("embedded-tweet-svg-object");
  const tweetSvgImage = document.getElementById("embedded-tweet-svg-img");
</script>


{{ partial "taxonomyList.html" . }}
{{ partial "footer.html" . }}

Here’s assets/inlineTweetStandalone.scss:

blockquote.inline-tweet {

  --body-fg-color-deemphasize-nontext: #ddd;
  --body-fg-color-deemphasize-text: gray;
  --inline-tweet-dt-fg-color: rgb(101, 119, 134);
  --inline-tweet-username-fg-color: rgb(101, 119, 134);
  --inline-tweet-about-bg-color: rgb(75, 75, 172);
  --inline-tweet-about-fg-color: white;

  *, *:hover, *:hover *{
    all: revert;
  }

  .inline-tweet-qt {
    border-radius: 2%;
    border-style: solid;
    border-width: .1em;
    border-color: var(--body-fg-color-deemphasize-nontext);

    padding: 0;
    margin: 0 0 1em 0;

    .blockquote {
      padding: 0;
      margin: 0;
    }
  }

  margin: 0;
  background-color: var(--body-bg-color);
  border-radius: 2%;
  border-style: solid;
  border-width: .1em;
  border-color: var(--body-fg-color-deemphasize-nontext);
  padding: 1em;
  font-family: sans-serif;

  line-height: 1.4em;
  max-width: 30rem;

  a.inline-tweet-user {
    text-decoration: none;
    .inline-tweet-pfp {
      float: left;
      width: 4rem;
      height: 4rem;
      border-radius: 50%;
      margin: 0 .5rem 0 0;
      border-style: solid;
      border-width: .1em;
      border-color: var(--body-fg-color-deemphasize-nontext);
    }
    .inline-tweet-display-name {
      margin: 0;
      font-size: 1rem;
      text-decoration: none;
      color: var(--body-fg-color);
    }
    .inline-tweet-username {
      margin: 0;
      font-size: 1rem;
      font-weight: normal;
      color: var(--inline-tweet-username-fg-color);
    }
  }

  .inline-tweet-id {
    font-size: 70%;
    color: var(--body-fg-color-deemphasize-text);
    margin: 0;
    padding: 0;
  }

  .inline-tweet-text {
    clear: both;
    font-size: 1rem;
  }

  ol.media-inline-tweet-list {
    list-style-type: none;
    padding: 0;

    li:before {
      content: "";
      padding: 0;
      margin: 0;
    }
    li {
      display: inline-block;
      padding: 0.25em;
      margin: 0;

      .media-inline-tweet {
        max-width: 100%;
        border-radius: 2%;
        border-radius: 2%;
        border-style: solid;
        border-width: .1em;
        border-color: var(--body-fg-color-deemphasize-nontext);
      }
    }
  }
  ol.media-inline-tweet-list-1 li {
    max-width: 100%;
  }
  ol.media-inline-tweet-list-2 li {
    max-width: 45%;
  }
  ol.media-inline-tweet-list-3 li {
    max-width: 30%;
  }
  ol.media-inline-tweet-list-4 li {
    max-width: 20%;
  }

  .inline-tweet-footer {
    .inline-tweet-original-link {
      .inline-tweet-dt {
        font-size: .9rem;
        font-family: sans-serif;
        margin: 0;
        padding-bottom: 1rem;
        color: var(--inline-tweet-dt-fg-color);
        text-decoration: none;
      }
    }
    .inline-tweet-about-button {
      float: right;
      background-color: var(--inline-tweet-about-bg-color);
      color: var(--inline-tweet-about-fg-color);
      border-radius: 1em;
      width: 2em;
      height: 2em;
      text-align: center;
      padding: 0;
    }
    .inline-tweet-about-explanation {
      color: var(--body-fg-color-deemphasize-text);
      font-size: .8rem;
      line-height: 1.2rem;
      ul.inline-tweet-about-explanation-footer li {
      }
    }
  }

}

Here’s stuff I added to the whole-site JS header layouts/partials/header.js.html (just the JS I added):

  function getDocHeight(doc) {
    doc = doc || document;
    // stackoverflow.com/questions/1145850/
    const body = doc.body;
    const html = doc.documentElement;
    const height = Math.max(
      body.scrollHeight,
      body.offsetHeight,
      html.clientHeight,
      html.scrollHeight,
      html.offsetHeight
    );
    return height;
  }

  function resizeEmbeddedTweets() {
    const tweetHtmlObjs = document.getElementsByClassName("embedded-tweet-html-object");
    for (const tweetHtmlObj of tweetHtmlObjs) {
      window.TESTTWEET = tweetHtmlObj;
      const tweetHeight = getDocHeight(tweetHtmlObj.contentDocument);
      console.log(`tweetHeight: ${tweetHeight}`);
      tweetHtmlObj.height = tweetHeight + 10;
    }
  }

  function resizeEmbeddedTweets2() {
    const tweetHtmlFrames = document.querySelectorAll("iframe.embedded-tweet-html-iframe")

    for (const tweetHtmlFrame of tweetHtmlFrames) {
      const objWindow = tweetHtmlFrame.contentWindow
      const objDoc = objWindow.document
      const objHtml = objDoc.documentElement
      const objBody = objDoc.body

      if(objBody) {
          // objBody.style.overflowX = "scroll" // scrollbar-jitter fix
          // objBody.style.overflowY = "hidden"
      }
      if(objHtml) {
          // objHtml.style.overflowX = "scroll" // scrollbar-jitter fix
          // objHtml.style.overflowY = "hidden"

          // var style = window.getComputedStyle(html)
          // tweetHtmlObj.width = parseInt(style.getPropertyValue("width")) // round value
          // tweetHtmlObj.height = parseInt(style.getPropertyValue("height"))
          // console.log(`Just set <object> height to ${tweetHtmlObj.width}x${tweetHtmlObj.height}`);

          const objHtmlStyle = objWindow.getComputedStyle(objHtml);
          const compW = objHtmlStyle.getPropertyValue("width");
          const compH = objHtmlStyle.getPropertyValue("height");
          const docHeight = getDocHeight(objDoc);
          console.log(`Computed style: ${compW}x${compH}; JS detected height ${docHeight}.`);
          console.log(objHtml.getBoundingClientRect())

          tweetHtmlFrame.width = parseInt(compW);
          tweetHtmlFrame.height = parseInt(docHeight);
      }
    }

  }
    // requestAnimationFrame(resizeEmbeddedTweets2);
    // resizeEmbeddedTweets2();

  window.onloads.push(resizeEmbeddedTweets2);
  // window.addEventListener('resize', resizeEmbeddedTweets2);

  window.addEventListener('message', function (e) {
    if (e.data.mrlevent) {
      console.log(`Receiving mrlevent from iframe...`)
      window.TESTEVENT = e;
      if (e.data.mrlevent === "resize") {
        console.log(`Getting resize message from iframe with dimensions: ${e.data.w} x ${e.data.h}`);
        return;
        resizeEmbeddedTweets2();
      }
    // } else {
    //   console.log(`Receiving non-mrl event?`)
    //   console.log(e)
    }
  })

Result

Unsatisfying :(

  • That resizeObserver thing seems like it ought to be handy, but it doesn’t trigger when the user clicks button that shows the about message for the tweet embed.
  • Measuring the height gets it wrong in every case. Not sure if differently wrong or the same wrong, but wrong.

Responses

Webmentions

Hosted on remote sites, and collected here via Webmention.io (thanks!).

Comments

Comments are hosted on this site and powered by Remark42 (thanks!).