Javascript 和 Iframe
内联框架
我们为什么真的需要 Javascript?
问题(同源策略)
发送消息 API
在 iframe 中接收消息
活动
从标题你就知道我要写关于Javascript和iframe 的文章了。在日常生活中,作为一名开发者或博主,在网站中嵌入 iframe 非常简单。
例如,如果您需要将 YouTube 视频嵌入到您的网站,您只需从 YouTube 复制代码并将其粘贴到您的网站即可。
<iframe width="560" height="315" src="https://www.youtube.com/embed/$SOME_ID" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
这就是我所说的“直截了当”。
我意识到这一点,并在几个月前开始开发一个评论插件Hyvor Talk 。这并不容易。在本文中,我将解释其中的原因。在创建这个插件的过程中,我学到了很多东西,也设计了一些技巧。我将在本文中解释这些技巧。如果您计划开发一个插件或任何使用 iframe 加载到其他网站的东西(例如评论插件),本文将帮助您了解如何有条理地完成某些任务。此外,我还会解释如何克服您在开发此类插件时可能遇到的障碍。
在本教程中,我们假设您正在创建一个插件,该插件会加载到其他网站的 iframe 中(我将该网站的所有者称为“客户”)。此外,您的网站域名为pluginx.com
,而客户网站的域名为client.com
。
内联框架
首先,让我们了解一下什么是 Iframe。
HTML 内联框架元素 () 表示嵌套的浏览上下文,将另一个 HTML 页面嵌入到当前页面中 - MDN
在本文中,我不仅仅会向网站添加 HTML 代码,还会重点介绍如何使用 Javascript 处理 Iframe。
我们为什么真的需要 Javascript?
JavaScript 可以让你更好地控制 iframe。尤其是,当 iframe 中的内容发生变化时自动调整 iframe 的大小,这只能通过 JavaScript 来实现。这需要 iframe(你的网站)和网站(其他网站)之间的通信。所以,最好的方法不是给<iframe>
客户端提供代码,而是让他们添加一个 JavaScript 文件。
客户将代码添加到他的网站。代码包括一个<script>
标签和一个<div>
标签(iframe容器)。
<html>
<head>
...
</head>
<body>
...
<div id="pluginx-iframe-container"></div>
<script src="https://pluginx.com/iframe-loader.js"></script>
</body>
现在,我们可以编写iframe-loader.js来加载托管在您的域上的 HTML 页面。
添加 iframe
此任务需要简单的 JavaScript 代码。需要记住的一点是,将所有变量都放在局部作用域中。这可以通过将代码包装在自调用函数中来实现。这可以防止你的 JS 变量与客户端网站的 JS 变量发生冲突。如果你使用像webpack这样的打包工具,则无需这样做,因为 webpack 会将所有变量都设置为局部变量。
(function() {
var iframe = document.createElement("iframe");
iframe.src = "https://pluginx.com/iframe"; // iframe content (HTML page)
iframe.name = "pluginx-main-iframe"; // this is important
})();
我稍后会解释该属性的重要性name
。请确保为其设置一个唯一的名称。
我发现添加以下属性可以使您的 iframe 在所有浏览器中看起来都很好。
iframe.width = "100%";
iframe.allowTranparency = true;
iframe.tabIndex = 0;
iframe.scrolling = "no";
iframe.frameBorder = 0;
iframe.style.border = "none";
iframe.style.overflow = "hidden";
iframe.style.userSelect = "none";
iframe.style.height = "250px"; // set initial height
现在,我们可以附加<iframe>
。
var container = document.getElementById("pluginx-iframe-container");
container.appendChild(iframe);
问题(同源策略)
如果两个网站都在同一个域名下,那么你就可以简单地执行彼此页面的功能、访问变量等等。但是,根据同源策略,这是无法做到的。如果两个网站不在同一个域名下,浏览器会限制父网站访问 iframe 窗口的功能和变量,反之亦然。起初,我认为这是不必要的安全措施。但现在我明白了,这是保护网站安全的最佳方法之一。为什么?因为如果你的客户端可以使用 JavaScript 访问你的网站,他们就可以运行你的功能、更新内容、点击按钮、添加点赞、窃取用户数据等等。
有几种方法可以绕过同源策略。最好的方法是使用HTML 5 Post Message API。
发送消息 API
在完成此应用程序的开发之前,不要考虑Post Message API以外的任何其他好朋友!
该window.postMessage()
方法安全地启用了 Window 对象之间的跨域通信。我们可以使用它来实现插件和客户端网站之间的通信。我们可以通过该window.onmessage
事件监听 postMessages。请注意,您只能通过此事件发送字符串。
在创建 Hyvor Talk 时,我创建了一个简单的类来封装这种通信。我推荐你使用它,因为它可以简化流程。代码如下。接下来我会解释一下。
/**
* Creates a messenger between two windows
* which have two different domains
*/
class CrossMessenger {
/**
*
* @param {object} otherWindow - window object of the other
* @param {string} targetDomain - domain of the other window
* @param {object} eventHandlers - all the event names and handlers
*/
constructor(otherWindow, targetDomain, eventHandlers = {}) {
this.otherWindow = otherWindow;
this.targetDomain = targetDomain;
this.eventHandlers = eventHandlers;
window.addEventListener("message", (e) => this.receive.call(this, e));
}
post(event, data) {
try {
// data obj should have event name
var json = JSON.stringify({
event,
data
});
this.otherWindow.postMessage(json, this.targetDomain);
} catch (e) {}
}
receive(e) {
var json;
try {
json = JSON.parse(e.data ? e.data : "{}");
} catch (e) {
return;
}
var eventName = json.event,
data = json.data;
if (e.origin !== this.targetDomain)
return;
if (typeof this.eventHandlers[eventName] === "function")
this.eventHandlers[eventName](data);
}
}
我使用 ES6 编写了这个类。如果你没有使用 ES6,你可以使用Babel将其转换为浏览器支持的 JavaScript 。
在解释该类之前,让我们在我们的脚本(iframe-loader.js)中使用它。
// handle each event from the iframe
var clientEventsHandler = {
resize: (data) => {
// resize the iframe
}
};
messenger
var clientMsger = new CrossMessenger(iframe.contentWindow, "https://pluginx.com", eventHandlers)
在向 iframe 发送消息的同时,我们还需要接收消息并对数据做出反应。该clientEventsHandler
对象将包含这些函数。(键名和事件名称)。
我们回班级吧。
构造函数需要三个参数。
- 第一个是我们调用该函数的 iframe 窗口
postMessage()
。 - 然后,目标域名就是您的网站域名。这使我们能够使用以下方式验证传入的消息
e.origin
- 最后是事件处理程序。稍后我将向您展示如何编写一些常见的事件处理程序,例如调整大小事件。
现在,我们可以使用 向 iframe 发送消息clientMsger.post(eventName, data)
。这里的 data 应该是一个对象。它将被转换为 JSON 并发送到 iframe。
在 iframe 中接收消息
目前,我们处理了添加到客户网站的 JavaScript 文件。现在,我们将开始处理 iframe 的脚本。我们需要在 iframe 中使用相同的类来接收消息。
您的 iframe 内容 (HTML)
<html>
... html stuffs
<script src="script.js"></script>
</html>
script.js
var iframeEventHandlers = {}; // some event handlers as the clientEventHandlers
var clientDomain = ""; // How to find this???
var iframeMsger = new CrossMessenger(window.parent, clientDomain, iframeEventHandlers)
我们需要找到客户端的域名。我的做法是在请求 iframe 时设置 GET 参数。
var domain = location.protocol + "//" + location.hostname;
iframe.src = "https://pluginx.com/iframe?domain=" + domain;
然后,您可以从后端语言接收它并clientDomain
动态设置变量。
例如:对于 PHP
var clientDomain = "<?= $_GET['domain'] ?>"; // make sure this is in the PHP page ;)
现在,我们的两个信使都完成了。让我们考虑一下你可能需要的一些事件。
活动
1. 调整大小
默认情况下,iframe 不会自动调整大小。我们必须明确设置 iframe 的高度。否则,iframe 的一部分将无法显示。因此,调整大小是任何 iframe 的关键部分。
调整大小比我们想象的要复杂得多。你需要了解scrollHeight
iframe 文档的内容才能调整 iframe 的大小。这只能由 iframe 来实现。
以下是调整 iframe 大小的重要时刻。
- 当 iframe 加载时
- 当客户端浏览器调整大小时
- 当 iframe 内容的高度发生变化时(例如:添加了新元素)
1 号和 2 号(加载和调整大小时)
我们可以从客户端网站上的脚本(iframe-loader.js)监听iframe的onload
事件和浏览器的事件。onresize
(function() {
// setting iframe and appending
iframe.onload = function() {
requestResize();
// do other onload tasks
}
window.addEventListener("resize", requestHeight);
})();
函数requestHeight()
这个函数只做一件事:从 iframe 中请求 iframe 的高度。
clientMsger.post("iframeHeightRequest");
这会向 iframe 发送一条消息,事件名称为“iframeResizeRequest”,但没有数据。
我们必须在 iframe 中监听此事件。在iframeEventHandlers
中添加事件名称和处理程序script.js
。
var iframeEventHandlers = {
iframeHeightRequest: () => {
var docHeight = document.body.scrollHeight;
iframeMsger.post("resize", { height: docHeight });
}
}
现在,我们可以从客户端的脚本中接收高度并调整 iframe 的大小。
var clientEventHandlers = {
resize: () => {
var height = data.height;
var winScrollLeft = window.scrollX,
windowScrollTop = window.scrollY;
commentsIframe.style.visibility = "hidden";
commentsIframe.style.height = 0;
commentsIframe.style.visibility = "visible";
commentsIframe.height = height + "px"; // + 10 to add little more height if needed
// scroll to initial position
window.scrollTo(winScrollLeft, windowScrollTop);
}
}
这个过程可能会让人感到困惑。我一开始也有同样的感觉。不过,你很快就能适应。如果你感觉“茫然”,可以稍事休息一下。(我记得我就是这么做的)。休息一会儿后,看看这张照片。
3.(iframe 高度改变)
在 iframe 中工作时,可能会添加新元素。这会导致 iframe 的高度发生变化。因此,每次高度发生变化时,我们都必须调整 iframe 的大小。
我们需要监听 iframe 中的 DOM 元素变化。这是我从网上找到的一个非常适合这个任务的函数。
var DOMObserver = (function(){
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
return function( obj, callback ){
if( !obj || !obj.nodeType === 1 ) return; // validation
if( MutationObserver ){
// define a new observer
var obs = new MutationObserver(function(mutations, observer){
callback(mutations);
})
// have the observer observe foo for changes in children
obs.observe( obj, { childList:true, subtree:true });
}
else if( window.addEventListener ){
obj.addEventListener('DOMNodeInserted', callback, false);
obj.addEventListener('DOMNodeRemoved', callback, false);
}
}
})();
将其添加到script.js
然后调用该函数。
DOMObserver(document.body, iframeEventHandlers.iframeHeightRequest);
每次添加或删除节点时,iframeEventHandlers.iframeHeightRequest
都会调用该函数。iframe 的大小将被调整!
除了调整大小之外,您还可以添加任何事件并在 iframe 和客户端之间传达消息。
如果您正在创建插件或任何在 iframe 内加载的内容,这里有我的一些提示。
- 将所有数据保留在您的窗口中。仅与客户网站共享必要的数据。切勿共享用户数据等敏感数据。
- 在您的 iframe 中执行所有 AJAX 操作。
- 切勿使用CORS并允许他人访问您的网站。请始终使用 postMessage API。
希望本文能帮助您了解如何在 iframe 中使用 JavaScript。我尽力讲解了我在创建 Hyvor Talk 时学到的知识。如有遗漏,我会在以后补充。如果您喜欢这篇文章,请分享并评论。
谢谢。 :)
这篇文章最初发布于Hyvor Groups 的Web Developers Group
鏂囩珷鏉ユ簮锛�https://dev.to/supunkavinda/javascript-and-iframes-87