使用JavaScript实现浏览器标签页/窗口之间的通信

153

什么是在同一浏览器的不同标签页/窗口之间进行JavaScript通信最可靠的方式?例如,当第二个标签页开始音频播放时,第一个标签页可以知道并停止它的播放器。

我正在构建一个带有音乐播放器的网站... 因此,如果您打开两个网站标签页,您可能会在两个标签页上同时播放音乐。这显然很糟糕,因此我正在寻找解决方案。


2
无论如何,自动播放音频都是不好的。为什么不让用户点击“播放”按钮,并在遇到这种情况时手动暂停其他选项卡呢? - Mike Ruhlin
9
没有自动播放。但如果用户不必手动暂停其他标签页的话会更好。例如,Youtube 在使用 Flash 时可以实现这个功能。 - adamJLev
https://dev59.com/snfZa4cB1Zd3GeqPSH2y#19165781 - inf3rno
还有其他选项,比如共享Web Workers和本地存储事件... - inf3rno
请查看此库 - StefansArya
10个回答

150

如果您需要更现代的解决方案,请查看 https://stackoverflow.com/a/12514384/270274

引用:

我会继续使用问题中提到的共享本地数据解决方案,使用 localStorage。从可靠性、性能和浏览器兼容性来看,这似乎是最好的解决方案。

localStorage 在所有现代浏览器中都得到了实现。

storage 事件在其他标签页修改 localStorage 时触发。这对于通信非常有用。

参考资料:
http://dev.w3.org/html5/webstorage/
http://dev.w3.org/html5/webstorage/#the-storage-event


5
这比目前被接受的解决方案更好。它不需要您不断地检查新信息,也没有延迟,并且使您能够接收所有事件。 - Stephane Mathis
2
我在IE11上使用localStorage时遇到了问题,请参考这篇文章(我遇到了第3点)http://blogs.msdn.com/b/ieinternals/archive/2009/09/16/bugs-in-ie8-support-for-html5-postmessage-sessionstorage-and-localstorage.aspx,所以对于IE来说,使用cookie解决方案更好。 此外,我尝试禁用cookie,但它仍然可以工作(这是在IE 11上)。 - Anas
3
好的演示页面 - http://html5demos.com/storage-events#view-source - JsAndDotNet
1
@Anas:链接失效了。新网址:https://blogs.msdn.microsoft.com/ieinternals/2009/09/15/html5-implementation-issues-in-ie8-and-later/ - Tim Down
1
这解决了许多问题(are比BroadcastChannel更广泛地实现),但是请注意,如果用户在同一浏览器中打开2个“发送者”选项卡,则可能会有一些意外惊喜:拥有1个公共通信通道可能会引起一些意外。 - Xenos

93

出于历史原因,您可以将旧方案保留在下方,并更新为现代解决方案。

您可以使用广播通道API发送和接收消息。 https://developer.mozilla.org/zh-CN/docs/Web/API/Broadcast_Channel_API

// Connection to a broadcast channel
const bc = new BroadcastChannel('test_channel');

// Example of sending of a very simple message
// It doesn't have to be a string, it could be a JS object
bc.postMessage('This is a test message.');

接收消息:

// A handler that only logs the event to the console:
bc.onmessage = function (ev) {
  console.log(ev);
}

并且关闭通道的方法:

// Disconnect the channel
bc.close();

这是一种历史悠久的做法,对于现代浏览器,请使用上面的方法!

您可以使用cookie在浏览器窗口之间(包括选项卡)进行通信。

下面是发送者和接收者的示例:

sender.html

<h1>Sender</h1>

<p>Type into the text box below and watch the text 
   appear automatically in the receiver.</p>

<form name="sender">
<input type="text" name="message" size="30" value="">
<input type="reset" value="Clean">
</form>

<script type="text/javascript"><!--
function setCookie(value) {
    document.cookie = "cookie-msg-test=" + value + "; path=/";
    return true;
}
function updateMessage() {
    var t = document.forms['sender'].elements['message'];
    setCookie(t.value);
    setTimeout(updateMessage, 100);
}
updateMessage();
//--></script>

receiver.html:

<h1>Receiver</h1>

<p>Watch the text appear in the text box below as you type it in the sender.</p>

<form name="receiver">
<input type="text" name="message" size="30" value="" readonly disabled>
</form>

<script type="text/javascript"><!--
function getCookie() {
    var cname = "cookie-msg-test=";
    var ca = document.cookie.split(';');
    for (var i=0; i < ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0)==' ') c = c.substring(1,c.length);
        if (c.indexOf(cname) == 0) {
            return c.substring(cname.length, c.length);
        }
    }
    return null;
}
function updateMessage() {
    var text = getCookie();
    document.forms['receiver'].elements['message'].value = text;
    setTimeout(updateMessage, 100);
}
updateMessage();
//--></script>

2
我也考虑过这样的解决方案,但是希望有比使用cookies/setTimeout更好的方法。然而,这可能是唯一的解决方案。谢谢。 - adamJLev
16
不要将字符串传递给 setTimeout - 这样做会使用 eval。相反,使用 setTimeout(updateMessage, 100) 直接传递函数。 - Yi Jiang
2
我也建议使用 setInterval() - Julian F. Weinert
1
我在IE11上使用localStorage时遇到了问题,请参考这篇文章(我遇到了第3点)http://blogs.msdn.com/b/ieinternals/archive/2009/09/16/bugs-in-ie8-support-for-html5-postmessage-sessionstorage-and-localstorage.aspx,所以对于IE来说,使用cookie解决方案更好。 此外,我尝试禁用cookie,但它仍然可以在IE 11、IE 10和IE 9上工作。 - Anas
1
Tomáš Zato,请注意,这个答案是在2010年发布的——当时并不是所有浏览器都支持HTML5,IE6和IE7的份额相当高。现在有更好的解决方案。 - Roman Goyenko
显示剩余7条评论

16

我认为您不需要使用cookies。每个文档的JavaScript代码都可以访问其他文档元素。因此,您可以直接使用它们来共享数据。

您的第一个窗口w1打开w2并保存引用

var w2 = window.open(...)

在w2中,您可以使用windowopener属性访问w1。


2
使用Cookie?吃掉它们然后享受!还有一个更简单的方法! 只需访问另一个窗口的变量。如果在w1中有一个值变量,则可以使用window.opener.value从w2中访问它! - donkeydown
2
假设用户都打开了它们,那么有类似的解决方案吗? - Fardin K.
27
大家需要知道,这个答案是错误的,正如@Ferdinak已经试图表达的那样。你没有一个用户打开的标签的引用。 - DDS
4
有趣是无关紧要的。首要的是这是一个问答网站。人们来到这个页面是为了寻找答案来回答提问者的问题。如果你想分享一些有趣的内容,可以考虑写一个新的百科问答式的问题。 - ivanjonas
6
在我看来,@jonas.ninja的问题并没有完全说明如何打开选项卡,因此即使这不是普适性答案,它仍然是一个完全有效的答案。 - Superole
显示剩余8条评论

15

您可以通过本地存储API来实现。请注意,这仅适用于两个选项卡之间的操作。您无法将发送方和接收方都放在同一页上:

在发送方页面上:

localStorage.setItem("someKey", "someValue");

接收方页面:

    $(document).ready(function () {

        window.addEventListener('storage', storageEventHandler, false);

        function storageEventHandler(evt) {
            alert("storage event called key: " + evt.key);
        }
    });

我本来打算使用这种方法,但后来发现WebBrowser控件不会触发“storage”事件处理程序方法。不确定为什么。可能是个bug。 - Brain2000
2
感谢您提供的解决方案,让我的一天变得美好了。它不能使用file:///协议工作,但可以在有效域名下工作。另一个类似的演示网站是http://html5demos.com/storage-events。 - Vikram Kumar
3
访问localStorage时无需等待DOM就绪。 - meandre

15

还有一项实验性技术称为Broadcast Channel API,专门用于不同浏览器上下文之间的通信(具有相同的源)。您可以向另一个浏览器上下文发布消息并从其接收消息,而无需引用它:

var channel = new BroadcastChannel("foo");
channel.onmessage = function( e ) {
  // Process messages from other contexts.
};
// Send message to other listening contexts.
channel.postMessage({ value: 42, type: "bar"});

显然这是实验性技术,并且尚未在所有浏览器上得到支持。


2
它已不再是实验性的,尽管Edge可能尚未实现它(在MDN中标记为“?”)。 - Xenos
1
现在Edge已经拥有它了。 - Doin

12

以下窗口(w1)会打开另一个窗口(w2)。 任何窗口都可以向另一个窗口发送/接收消息。因此我们应该理想地验证消息是否来自我们打开的窗口(w2)。

在w1中

var w2 = window.open("abc.do");
window.addEventListener("message", function(event){
    console.log(event.data);
});

在w2(abc.do)中

window.opener.postMessage("Hi! I'm w2", "*");

它能在所有浏览器上运行吗? - Stepan Yakovenko
@StepanYakovenko 浏览器支持:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/open#Browser_Compatibility - Ashwin Aggarwal

10

即使在 HTML5 之前,如果文档属于相同的来源,则可以支持在不同的 JavaScript 执行上下文之间进行通信。

如果不是这种情况或者您没有对其他 Window 对象的引用,则可以使用 HTML5 引入的新的postMessage API。我在 Stack Overflow 答案中对这两种方法进行了详细说明。


5
postMessage API 不是为此设计的 https://dev59.com/THNA5IYBdhLWcg3wEpkU#1100416,你需要引用特定窗口来发布消息。 - mems

9

如果窗口之间存在父子关系,您可以在它们之间进行通信(无论是标签式还是非标签式窗口)。

创建并更新一个子窗口:

<html>
<head>
<title>Cross window test script</title>
<script>
var i = 0;
function open_and_run() {
    var w2 = window.open("", "winCounter"); 
    var myVar=setInterval(function(){myTimer(w2)},1000);
}

function myTimer(w2) {
    i++;
    w2.document.body.innerHTML="<center><h1>" + i + "</h1><p></center>";
}
</script>
</head>
<body>
Click to open a new window 
<button onclick="open_and_run();">Test This!</button>    
</body>
</html>

子窗口可以使用parent对象与生成它的父窗口进行通信,因此您可以从任一窗口控制音乐播放器。

在此处查看其操作:https://jsbin.com/cokipotajo/edit?html,js,output


这里的一个问题是,我们不能(除非使用hacky方法)共享链接到我们同步的视图... - yckart
@yckart 有很多种方法可以实现这个功能,但我见过最常用的方法是将字符串发送到另一个窗口的输入框中。您可以为值更改创建事件侦听器。例如:https://dev59.com/Ieo6XIcBkEYKwwoYQiS7 。我认为更好的方法是在另一个窗口中调用JavaScript函数,并传递URL。例如:<a href="javascript:window.parent.mySendURL(url)"> 来自子窗口或 <a href="javascript:myChildWindow.mySendURL(url)"> 来自父窗口。 - Victor Stoddard

2

我发现使用HTML5本地存储的另一种方法。我创建了一个具有类似API的事件库:

sysend.on('foo', function(message) {
    console.log(message);
});
var input = document.getElementsByTagName('input')[0];
document.getElementsByTagName('button')[0].onclick = function() {
    sysend.broadcast('foo', {message: input.value});
};

https://github.com/jcubic/sysend.js

它可以向所有其他页面发送消息,但不会发送给当前页面。

编辑:

最新版本的库还支持广播通道通信,但仍然可以在仅支持本地存储的IE11中运行。它还支持跨源通信(不同域),但需要一些代码。

最新的API还支持emit函数,可在同一页上执行事件。

即使是最新版本,也支持管理窗口,向特定窗口发送消息或获取窗口/选项卡列表。


你能否添加一个链接到这个库? - Dennis Nerush
1
@DennisNerush https://github.com/jcubic/sysend.js - jcubic

-1

使用Flash,您可以在任何窗口、任何浏览器(是的,在运行时从Firefox到Internet Explorer)之间进行通信...任何形式的Flash实例(ShockwaveActiveX)。


6
问题不是关于 Flash。 - Stever B
1
这在大多数移动设备上无法工作。 - Soviut
公平地说,Flash运行的是“一种形式的”Javascript,正如OP所请求的那样。 - user3638471
它是如何工作的?机制是什么?浏览器不会阻止它工作吗?它是否通过互联网上的某个服务器进行传输? - Peter Mortensen
好的,他已经离开了。还有其他人可以加入吗? - Peter Mortensen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接