【问题标题】:Cross Domain IFrame element.scrollIntoView() Safari Issue跨域 IFrame element.scrollIntoView() Safari 问题
【发布时间】:2019-07-03 08:24:03
【问题描述】:

我遇到了一个困扰 google 的问题,但提供的解决方案都不能正常工作。我假设这些解决方案中的大多数不考虑跨域。

我有一个网站,我想指导用户使用(整页)IFrame 将其嵌入到他们的网站中。问题仅在某些版本的 Safari 上。 IFrame 中的元素无法自行滚动到视图中。

我注意到,如果我进行相同的域测试,IFrame 可以使用window.parent.scrollTo(0,element.top) 自行滚动。这有效,但不能跨域。另一个奇怪的事情是,没有其他浏览器需要 window.parent 方法来滚动 IFrame,只有 Safari。所有其他浏览器都可以在 IFrame 中使用element.scrollIntoView()。请注意,我已经使用 JavaScript workaround 来让 Safari 使用跨协议 IFrame。

我只在 IFrame 内的 Safari Mobile 上看到的另一个问题是,当向下滚动时,Bootstrap Modals 会在 IFrame 顶部出现在视野之外。虽然,我确信如果我们可以正确设置滚动位置,我们也应该能够设置模态位置。

这是我尝试过的;

 1. window.frames['IFrameName'].document.
    getElementById("elmId").scrollIntoView();
  1. Offset trick
  2. Velocity.js

我最后的手段(我认为)是在我的 IFrame 中使用 postMessage 来通知父域设置框架的滚动位置。

在我看来,这个问题已经存在了很长时间。还有比这更好的方法吗?

【问题讨论】:

    标签: javascript jquery iframe safari


    【解决方案1】:

    另一个选项是iframeResizer 库。您可以在 iframePage 中使用两种方法:scrollTo 和 scrollToOffset,它们的作用与您所描述的几乎相同 - 它们通过消息进行通信。它在 Safari 中为我们解决了这个问题。

    在父页面内部,在为 iframe 设置大小调整时,必须为其 onScroll 事件分配一个回调函数:

    iframeNode.iframeResize({ 
      ...,
      onScroll: ({x,y}) => callback
    }
    

    在 iframe 页面内:

    if('parentIFrame' in window){
      window.parentIFrame.scrollTo(0, someNode.offsetTop);
    }
    

    【讨论】:

      【解决方案2】:

      这最终比代码更多的研究。发生的事情是 - 我有根据内容调整 IFrame 大小的代码。

      在所有其他浏览器中,这可以正常工作并消除滚动条。原来 Safari automatically sizes the Iframe 留下了它自己的卷轴。在我的应用程序中,静态页面为零。这给我留下了无法使用链接中描述的scrolling=no 修复的问题。

      在确切发现发生了什么之后,我采取了不同的方法来修复elm.scrollIntoView()。除了重要的部分之外,代码更多的是 cmets;

      1. 检测何时使用 RequiresIframeScrollFix 应用 Iframe 修复
      2. 使用 elm.getBoundingClientRect().top 从 Iframe 中获取我们的滚动位置。
      3. 与父级通信以滚动window.parent.postMessage
      4. window.addEventListener('message',...)在父级中接收消息

      这就是它的样子。

      Iframe 网站

      我们的 Iframe 网站目前将其元素滚动到这样的视图中 elm.scrollIntoView(); 我们已将其更改为以下内容。

      if (RequiresIframeScrollFix())
           window.parent.postMessage(elm.getBoundingClientRect().top, "*"); // Tell IFrame parent to do the scrolling. If this is not a test environment, replace "*" with the parent domain.
       else
           elm.scrollIntoView(); // If not scroll into view as usual.
      

      可选:使用 elm.getBoundingClientRect().top 修复 IOS IFrame 中的引导模式定位。

      $('#modalId').css('top', elm.getBoundingClientRect().top); // This fixes modal not in view on Safari Iframes.
      

      RequiresIframeScrollFix() 主要由一些很好的文档代码组成,围绕 SO 确定我们是在 iPad 还是 iPhone 上的 iframe 中。

      // Used to help identify problematic userAgents.
      var debugNavigator = false;
      
      // Detects an issue on mobile where the Parent is an iframe which cannot have it's scroll bars removed.
      // Presumably not a bug as safari will autosize it's iframes: https://salomvary.com/iframe-resize-ios-safari.html
      // Can use "scrolling=no" fix instead if the parent knows the initial size of your iframe.
      function RequiresIframeScrollFix() {
          try {
              // Debug navigator Agent
              if (debugNavigator)
                  alert(navigator.userAgent);
      
              // We know this issue happens inside an IFrame on;
              // Safari iPhone
              // Safari iPad
              // Safari Desktop Works fine.
      
              // Check for safari
              var is_safari = navigator.userAgent.indexOf("Safari") > -1;
              // Chrome has Safari in the user agent so we need to filter (https://stackoverflow.com/a/7768006/1502448)
              var is_chrome = navigator.userAgent.indexOf('Chrome') > -1;
              if ((is_chrome) && (is_safari)) { is_safari = false; }
      
              // If we need to narrow this down even further we can use a more robust browser detection (https://stackoverflow.com/questions/5916900)
              // Problematic browsers can be adjusted here.
              if (is_safari && inIframe() && (
                      navigator.userAgent.match(/iPad/i) ||
                      navigator.userAgent.match(/iPhone/i)
                      ))
                  return true;
              else
                  return false;
      
          } catch (e) {
              alert(e.message);
          }
      }
      
      // (https://stackoverflow.com/questions/326069/)
      function inIframe() {
          try {
              return window.self !== window.top;
          } catch (e) {
              return true;
          }
      }
      

      父网站

      我们的父网站拥有由 Safari Mobile 自动调整大小的 IFrame。因此,父站点现在有它自己的滚动条,而不是 IFrame。我们在父站点内设置监听器,以便在它收到来自 IFramed 站点的消息时自行滚动。

      // Safari Mobile Iframe Cross Domain Scroll Fix.
      window.onload = function () {
           // Calback function to process our postMessages.
           function receiveMessage(e) {
                try {
                     // Set the scroll position from our postMessage data.
                     // Non-Test pages should uncomment the line below.
                     //if (e.origin.includes("your-iframe-domain.com"))
                     window.scrollTo(0, e.data);
                 }
                 catch (err) {
                 }
            }
      
            // Setup and event to receives messages form our iframe (or window)
            window.addEventListener('message', receiveMessage);
      }
      

      希望这有助于其他人剖析移动设备上的 Safari iframe 问题。另外,如果我忽略了更好的解决方案,请告诉我。

      【讨论】:

      • 我有类似的问题,但用例有点复杂。我在窗口上有一个iframe,我有一个弹出模型,它也有一个iframe。我已经在窗口 iframe 上实现了这个解决方案并且它可以工作。但是对于弹出模型,我无法解决这个问题。当我单击模型 iframe 中的链接时,窗口 iframe 会滚动,而不是模型的 iframe。你对此有什么建议吗?
      • @KuldeepBhimte 如果在这种情况下理解正确,滚动条应该在您的弹出模式上吗?听起来弹出模式中的iframe 仍在告诉您的父母滚动自己,当您希望模式自行滚动时?在这种情况下,我认为您需要让模态页面也监听来自其子 iframe 的消息以滚动模态本身。您需要将父级与其iframe 之间的通信以及弹出模式与其iframe 之间的通信分开。您可以通过在您的帖子消息中添加一个唯一标识符或过滤掉域来做到这一点。
      • 以防万一这对任何人都有帮助...我在 Safari 版本 9.1、12.1 和 13(在 Browserstack 中)遇到了同样的问题。 iFrame 不会在父框架内向上滚动。
      猜你喜欢
      • 2012-04-11
      • 2012-03-12
      • 2012-12-11
      • 2011-02-18
      相关资源
      最近更新 更多