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:
https://forum.cannabisanbauen.net/t/test-embed-fuer-ca-blog/6843

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?)

저도 이걸 해보려 하고 있습니다. WordPress 글을 Discourse에 임베드하면, 사용자를 두 개의 다른 사이트로 보내는 대신 Discourse로만 유도할 수 있다는 걸 깨달았거든요.

이걸 어떻게 해야 하는지 알아낸 분이 계신가요? 꽤 오랫동안 이 문제로 고생하고 있습니다.

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' );

게시물의 높이에 따라 height 속성을 설정하고 싶으신 건가요?

그렇다면 정확도는 어느 정도까지 맞춰야 할까요? 게시물의 문자 수를 세고, 고정된 높이를 가진 이미지나 기타 요소의 높이를 더한 뒤, 여유를 두기 위해 조금 더 높이를 추가하는 방식이 가능할 것 같다는 생각이 듭니다. 이를 위한 몇 가지 제안을 드릴 수 있을 것 같습니다.

또한, 특정 폭으로 렌더링된 게시물의 정확한 높이를 가져오기 위해 JavaScript를 사용하는 것도 가능할 수 있습니다.

아직 discourse_publish_format_html 필터에 통합하는 단계까지는 가지 않았습니다. 그냥 Discourse 게시글에 수동으로 추가해서, iframe 내부 콘텐츠의 높이를 읽고 iframe 크기를 조절하는 자바스크립트를 어떻게 작성해야 할지 알아보고 있었을 뿐입니다.

온라인에서 그런 방법을 보여주는 튜토리얼을 꽤 많이 봤는데, 어째서인지 Discourse에서는 그 JS가 작동하지 않습니다. API의 페이지 변경 이벤트와 decorateCookedElement를 시도해 봤지만, 여전히 작동하지 않습니다.

가능하다면 Discourse 자체에서 처리하는 방식을 선호합니다. 그래야 WordPress가 아닌 다른 사이트에서 임베드한 iframe도 전체 높이로 조절할 수 있으니까요.

흥미로운 문제입니다. Discourse 게시물에서 iframe 요소의 height 속성을 수동으로 수정하면 어떻게 될까요? 높이를 수정한 후 게시물을 다시 열람했을 때, 새 높이가 적용되나요, 아니면 수정된 높이가 반영되도록 Discourse 게시물을 다시 베이킹(rebake)해야 하나요?

수정: 흥미로운 수준이라 오늘 나중에 이 부분을 테스트해 보겠습니다.

iframe 태그에 height="400px"를 추가하면 페이지를 새로고침한 후 높이가 다시 조정됩니다. height="100%"로 추가하면 아무 효과가 없는 것 같습니다.

iframe에 대한 CSS 속성을 추가하면, 적어도 내가 수동으로 입력한 값만큼 높이가 제대로 변경되는 것 같습니다.

으, 결국 해결했어. 문제는 iframe 태그에 사용자 정의 클래스를 사용했던 거였는데, Discourse에서는 그런 태그를 전부 제거해버려. 며칠 전에 배운 교훈을 또 잊었네 (Formatting posts to look more like Wordpress blog - #6 by jimkleiber). 사용자 정의 HTML 클래스가 여기에서 작동하지 않는 이유가 보안 때문이라고 읽었긴 한데, 왜 그런지 정확히 모르겠어 :confused:

그럼에도 불구하고, .topic-body iframe 셀렉터를 사용하여 iframe 높이를 조정하는 코드를 여기에 올릴게 (다른 사람들에게도 작동하는지 확실하지 않음, 나한테는 잘 작동하는 것 같음):

<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); // 필요에 따라 지연 시간을 조정하세요                  
      },
      { id: "component-id", onlyStream: true}
    );
</script>

수정: 사실 작동하지 않더라고. iframe 태그에 height="4000px"를 추가했더니 그 덕분에 작동했던 것 같아.

이것은 까다로운 문제입니다. Discourse 내부에서 iframe의 콘텐츠에 액세스하는 것이 쉽지는 않다고 생각합니다. 다만 iframe의 소스와 Discourse 사이트가 같은 도메인의 다른 서브도메인에 있다면 작동하게 만들 수 있을지도 모릅니다.

문제를 올바르게 이해하고 있다면, 콘텐츠를 가져오는 서브도메인과 Discourse에서 실행 중인 스크립트 모두에서 document.domain을 루트 도메인으로 설정해야 합니다.

iframe의 소스가 실제로 루트 도메인이라면, 스크립트를 다음과 같이 조정해 보세요:

<script type="text/discourse-plugin" version="0.8.18">
   document.domain = "your_root_domain.com"; // 여기에 도메인으로 수정
    api.decorateCookedElement(
      element => {

iframe의 도메인도 루트 도메인의 서브도메인이라면, 해당 iframe에서도 document.domain을 루트 도메인으로 설정해야 합니다.

Discourse에서 iframe(또는 기타 어떤 것)을 스타일링하려면 콘텐츠를 데이터 속성이 있는 div로 감쌀 수 있습니다:

<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>

테마 CSS:

[data-full-height] > iframe {
      // 선택적으로 외부 iframe 스타일 지정: 높이, 너비 등
     // 불행히도 height: 100%는 작동하지 않습니다 - iframe의 포함 요소에는 설정된 높이가 없기 때문입니다.
}

document.domain 방식이 작동하지 않으면 다른 솔루션이 있을 수 있습니다 - iframe의 부모 문서와 Discourse 간 통신을 위해 window.postMessage를 사용하는 것 등이 있습니다.

iframe의 높이를 소스 사이트에서 계산하는 제 초기 아이디어는 작동하지 않을 것 같습니다 - 렌더링된 iframe의 너비는 Discourse를 보는 기기에 따라 달라지므로요.

음, 제 포럼은 루트 도메인의 서브도메인이라서 제안해 주신 대로 루트 도메인을 추가했는데 이 에러가 나네요:

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

이제 postMessage를 사용해보려 하지만 실패하고 있습니다. Discourse의 자바스크립트가 어떻게 작동하는지 더 잘 이해해야 할 것 같은데, 다른 사이트들과 비슷하게 작동한다고 가정하고 있었는데 그렇지 않을 수도 있거나, 아니면 제가 자바스크립트, 특히 크로스오리진 관련 처리를 잘 모르는 것일 수도 있겠네요. ㅋㅋ

이 문제 살펴봐 주셔서 감사합니다!

이 문제에 대해 꽤 오래前から 궁금해하고 있었습니다. 여기 개념 증명(PoC)을 하나 공유합니다. (참고로 이 방법은 iframe에서 스크롤바를 제거하는 문제는 해결하지 못합니다.)

임베드하려는 게시물에 다음 스크립트 태그를 추가하세요:

<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' // 여러 iframe이 있는 경우 고유한 식별자를 사용하세요
        }, '*'); // 보안을 위해 부모 도메인을 지정하는 것을 고려하세요
    }

    // 초기 높이 전송
    window.onload = sendHeight;

    // 선택 사항: 리사이즈 또는 기타 이벤트 발생 시 높이 업데이트
    window.onresize = sendHeight;
</script>

스크립트에서 식별자로 "zalgFrame"을(를) 사용하고 있습니다.

Discourse 테마에서:

<script type="text/discourse-plugin" version="1.29.0">
let iframeHeight, iframeId;
window.addEventListener('message', (event) => {
  if (event.origin !== "http://wp-discourse.test") return; // 제 테스트 도메인입니다. 본인의 도메인으로 업데이트하거나 주석 처리하세요.
  // `wp-discourse.test`에서 전달된 iframe 높이를 가져오고, 거기서 설정한 iframeID와 iframeId가 일치하는지 확인합니다.
  if (event.data.iframeHeight && event.data.iframeId === 'zalgFrame') {
      // 콘솔을 열고 iframe이 포함된 Discourse 페이지를 방문하세요.
      // 창을 리사이즈할 때 부모 사이트에서 업데이트된 높이가 전송되는 것을 볼 수 있습니다.
      console.log("we got an event:" + event.data.iframeHeight); 
      iframeHeight = event.data.iframeHeight;
      iframeId = event.data.iframeId;
  }
  }, false);
</script>

Discourse 게시물에서:

<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>

따라서 렌더링된 iframe의 실제 높이를 부모 창에서 가져올 수 있습니다.

하지만 이벤트 리스너의 데이터를 api.decorateCookedElement 호출에 어떻게 전달해야 하는지 모르겠습니다. 또한 긴 iframe의 세로 스크롤바를 제거하는 데 이것이 실제로 작동할지 확실하지도 않습니다. iframe 요소에 큰 높이(1600px)를 하드코딩하여 시도해 보았지만, 여전히 스크롤바가 나타납니다.

수정: 완전성을 위해 추가합니다:

<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;
        // data-zalgFram div로 감싸진 iframe이 하나만 있을 것이라는 가정 하에
        let iframe = e.querySelector("[data-zalgFrame] iframe");
        if (iframe) {
          iframe.style.height = `${iframeHeight}px`;
        }
        // iframe의 실제 렌더링된 높이를 설정한 후
        // 이벤트 리스너를 제거합니다.
        window.removeEventListener("message", handleMessage, false);
      }
    }
    window.addEventListener("message", handleMessage, false);
  },
  { id: "component-id", onlyStream: true }
);
</script>

높이가 ~1000px을(를) 초과하는 경우, Discourse가 스크롤바를 추가하는 것을 피할 방법이 없어 보이기 때문에 이 접근법을 권장하지 않습니다.

제 생각에는 OP(질문자)에게 대한 답은 "어떤 식으로든 가능하지만, 실제로는 큰 효과를 보지 못할 가능성이 높다"는 것입니다. (다만 window.postMessage() 메서드에 대해 배울 수는 있었습니다 :slight_smile:

여기서 기울이고 계신 열정적인 노력을 존중하며, 그 분위기를 해치고 싶지는 않습니다. 다만 이 주제의 전제에 대해 솔직히 조금 회의적인 감정을 느낀다고 고백해야 합니다. 즉,

Jim에게 두 가지(진지한) 질문을 드리고 싶습니다:

  1. 일반적인 토픽 임베드 기능 대신 여기에 iframe을 사용하려는 이유는 무엇인가요?
  2. 사용자가 콘텐츠를 거기에서 소비하지 않기를 원한다면, 왜 여전히 WordPress 사이트를 유지하고 계신지 궁금합니다.

원래 주제 작성자를 대신해서 말하기는 어렵지만, 제 경우를 들어 답변해 드릴 수는 있습니다:

저는 WordPress에서 꽤 많은 플러그인을 직접 만들어서(해킹) 팟캐스트 플레이어에 인터랙티브 트랜스크립트(오디오가 재생되는 동안 단어가 하이라이트되고, 해당 단어를 클릭하면 오디오의 해당 부분으로 이동할 수 있음), 인터랙티브 챕터/쇼 노트, 그리고 검색/정렬/필터링 가능한 재생 목록을 사용할 수 있게 만들었습니다.

즉, iframe 없이 여기에 단순히 임베드하려면 제가 여기에 쏟아부은 JavaScript와 모든 스타일링에 접근할 수 없게 됩니다.

아, 그리고 WordPress에서 이런 것들을 직접 만들어내는 것이 Discourse에서 하는 것보다 훨씬 쉽습니다. 저는 여기서 JavaScript와 플러그인 작업을 할 때 정말로 고생하거든요.

팟캐스트 에피소드를 호스팅하려면 어쨌든 WordPress 사이트가 필요합니다. 하지만 사용자가 그곳에서 콘텐츠를 소비하도록 원하느냐는 점에 대해서는 확신이 없습니다. WordPress의 댓글로 Discourse를 사용해 온 결과, 상호작용이 줄었습니다. 예전에는 사람들이 WordPress 댓글에 글을 남겼지만, Discourse는 도메인 경계를 넘어야 하며, 그러면 그들은 별도의 장소에서 상호작용하게 됩니다. 만약 Discourse가 WordPress 포럼에서 사람들이 임베드된 방식으로 더 쉽게 게시할 수 있게 해준다면, 저는 아마도 그것에 집중할 것입니다.

필요한지 확신은 없지만, 사람들이 모이는 하나의 주요 장소를 갖고 싶다는 느낌을 받습니다. 이전에는 WordPress에 Discourse의 댓글/게시글을 임베드하는 것이 더 좋다고 생각했지만, 이제는 WordPress의 게시글을 Discourse에 임베드하는 것이 더 쉽고, 사람들이 서로 상호작용하도록 영감을 주는 데 더 효과적일 수 있다고 생각합니다.

멋지네요!

왜 그런가요?

이해합니다! 다만, 제가 이전에 드린 질문(“왜 그런가요?”)에 대한 답변에 따라, 동적 iframe보다 디스코스 게시물에 제대로 임베드하는 것이 더 안정적인 방법일 수 있습니다.

인터랙션이 줄었다는 소식에 안타깝고, 말씀하시는 부분도 잘 이해합니다. 시몬의 이 주제에 대한 더 넓은 논의가 떠오르네요.

아, 어쩌면 가능할 수도 있겠네요. 여기서 mediaelement.js를 사용해 오디오 플레이어를 설정하는 데 전에 꽤 애를 먹었거든요. 플러그인 API를 잘 이해하지 못하는 것 같습니다. 장기적으로는 해볼 수 있겠지만, 지금은 iframe 임베드로 꽤 괜찮아 보입니다. 가장 큰 과제는 iframe에 포함된 텍스트의 검색 가능성일 텐데, 이 텍스트를 게시물에 게시하고 숨기거나 아코디언 안에 넣어두면 검색 결과에 여전히 나오도록 하려고 생각 중입니다.

게다가 더 큰 문제는 콘텐츠가 처리(cook)될 때(혹은 그런 표현을 쓰는 거라면요, ㅋㅋ) HTML 클래스가 많이 제거된다는 점입니다. 그래서 여기서 WordPress 게시물을 그대로 게시하고 비슷한 CSS를 사용하려고 하면 많은 부분을 다시 작성해야 해서, 이걸 쓰게 되었습니다:

알겠습니다. 잠시 생각해보겠습니다. Simon이 이미 제안한 것 외에 동적 iframe에 대해 제가 특별히 통찰력이 있는 것은 아니지만, 당신의 사례가 제게 조금 더 생각하게 만들고 있습니다.

제가 이 부분에 대해 적극적으로 작업 중이라는 점도 언급할 가치가 있습니다. (웹사이트의 댓글 시스템을 위해 Discourse를 사용 중입니다.) 현재는 주로 헤드리스 WordPress 사이트에 집중하고 있지만, 일반적인 접근 방식이 일반 WordPress 사이트나 WordPress가 아닌 사이트에도 도움이 될 수 있습니다.

어디서 본 건지 기억이 나지 않는데, cooked content에 1000px 정도의 숨겨진 max-height가 있는 것 같습니다.

그래서 아마 당신의 해결책에 영향을 주고 있을 수 있겠네요.

내일 한번 확인해 보겠습니다 :folded_hands:t2:

iframe 요소에 적용되어 있습니다:

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

이것은 data 속성을 가진 iframe을 대상으로 하여 테마에서 수정할 수 있습니다:

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

더 긴 iframe을 표시하려면 이 변경 사항이 필요하지만, 제가 보던 스크롤바는 iframe 소스 페이지에서 발생했습니다. 좋은 결과를 얻는 유일한 방법은 블로그에 게시물의 임베드 버전을 만드는 것입니다. 기본적으로 게시물 내용 외에는 모두 제거하고 스타일을 조금 조정합니다. 예를 들어, WordPress 커스텀 게시물 유형을 사용하는 경우:

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;
        // 이 경우 `scrollHeight`가 올바른 대상이라고 생각합니다. 조금은 추측에 의존합니다.
        const height = Math.max(body.scrollHeight, body.offsetHeight,
            html.clientHeight, html.scrollHeight, html.offsetHeight);

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

    // 초기 높이 전송
    window.onload = sendHeight;

    // 선택 사항: 리사이즈 또는 기타 이벤트 시 높이 업데이트
    window.onresize = sendHeight;
</script>
<?php
endwhile; endif;

프로덕션 사이트에서 이것이 작동하도록 하려면 약간의 조정이 필요할 수 있지만, 더 자세히 살펴볼 가치가 있어 보입니다.

네, 작동하게 만들었어요. 다른 곳에서 js에 버그가 있어서 막혀 있었던 것 같아요. 우와, 제 사이트에 아주 잘 통합되어 있네요. @simon님, 정말 감사합니다!