标签页的打开控制与跨标签页通话
这是每一个音乐网站都会实现的功能,快来看看自己会不会做!
我们看下图这个效果,在首次点击播放音乐时,打开了一个新的标签页并在新的标签页中播放音乐。
再次切换音乐时,并没有打开新的标签页,而是在原来的标签页基础上进行播放。
不仅仅是这个网站,几乎所有的音乐类网站都是这么做的。
那这种效果是怎么实现的呢?我是渡一前端子辰老师,今天我将带你一探究竟,教你如何利用Broadcast Channel API来实现一个简单的音乐播放器。
实现音乐播放器
当然工欲善其事,必先利其器,首先我们得有两个页面,一个列表页,一个播放页。
列表页呢,我们通过点击播放按钮,利用 window.open
去打开一个新的标签页,并将音乐的名字通过 url 传递过去。
const playBtns = document.querySelectorAll('i[data-name]');
for (const btn of playBtns) {
btn.onclick = (e) => {
window.open(`/music.html?name=${e.target.dataset.name}`, '_blank');
};
}
播放页呢,我们就可以通过地址栏参数获取到音乐的名字,然后通过调用 play 函数进行播放,为了直观的看到切换音乐的效果,还修改了一下当前页的名字。
function play(name) {
audio.src = `/music/${name}`;
}
function init() {
const url = new URL(location.href);
const name = url.searchParams.get('name');
if (!name) {
return;
}
document.title = name
play(name);
}
init();
基本的代码我们知道了,看一看效果如何。
很明显播放是没问题的,但是每一次都会打开一个新的标签页,这不是我们想要的,我们只需要一个标签页就可以了。
那么要在列表页切换歌曲,要在播放页播放歌曲,因为这是不同的页面,所以就要用到跨页面的通讯了。
就是说,当我们第一次播放音乐时判断一下是否有播放页,没有的话就打开一个新的播放页并播放歌曲,如果有的话就要告诉这个播放页我们切换歌曲了,播放页接收到这个通知然后在页面内实现歌曲的切换。
我们要实现的就是这个判断和通知以及页内切歌的过程,现在逻辑清楚了,问题是我们要使用什么来实现通知。
熟悉 Web API 的同学应该知道有一个属性叫做 Broadcast Channel,Broadcast Channel API 可以实现同源下浏览器不同窗口,Tab 页,frame 或者 iframe 下的浏览器上下文 (通常是同一个网站下不同的页面) 之间的简单通讯。
Broadcast Channel API 简单的使用方式,就如上图所示。
我们先来实现一下页面的通讯,看看效果然后再来实现完整的操作。
const playBtns = document.querySelectorAll('i[data-name]');
// 创建一个消息频道叫 music
const bc = new BroadcastChannel('music')
for (const btn of playBtns) {
btn.onclick = (e) => {
// 通过 postMessage 事件发生消息
// 消息的类型任意,这里我们使用一个对象,将音乐的名字传进去
bc.postMessage({
musicName: e.target.dataset.name
})
};
}
在列表页,我们创建频道并发送消息。
// 省略其它代码...
// 创建一个相同的频道
const channel = createChannel('music');
// 监听 message 事件
channel.addEventListener('message', (e) => {
// 如果有音乐的名字,就根据名字切换音乐
if (e.data.musicName) {
document.title = e.data.musicName
play(e.data.musicName);
}
});
在信息页我们根据消息切换歌曲。
好了,我们写出来了,在试试现在的效果。
到这里已经实现在播放页的页内切换歌曲了,但是我们现在写的是有播放页得情况,所以我们发出的消息才可以被接收,现在我们要做的就是想一种办法,来实现没有播放页新建播放页,有播放页切换歌曲的步骤了。
我们知道同源的所有标签页都可以使用 Broadcast Channel API 来通讯,我们要封装一个通用的方法,所以我们就要思考以下几个问题:
- 当前打开的便签页中建立通讯链接的有几个?
- 我们怎么知道建立链接成功了?
- 便签页通讯时我们怎么知道谁发送的消息?
- 标签页的通讯链接什么时候关闭?
好了,我们根据这几个实现了如下代码。
/**
* @description: 用于生成标签页的 ID
* @param {string} name:标签页的名字
* @return {string} 生成的 ID
*/
function createId(name) {
const key = `channel-${name}`;
// 利用同源下所有页面都可以访问到的数据来生成 ID,以防 ID 冲突
let id = +localStorage.getItem(key);
if (!id) {
id = 0;
}
id++;
localStorage.setItem(key, id.toString());
return id;
}
/**
* @description: 用于发送消息
* @param {any} msg:要发送的消息
* @param {*} channel 创建的频道
*/
function sendMsg(msg, channel) {
channel.postMessage({
id: channel.id,
msg,
});
}
/**
* @description: 用于建立通讯频道
* @param {string} name:频道的名字
* @return {*} channel 创建的频道
*/
function createChannel(name) {
// 创建频道
const channel = new BroadcastChannel(name);
// 得到标签页的 ID
channel.id = createId(name);
// 创建一个监听器用于监听建立链接的标签页
channel.listeners = new Set();
// 首次建立链接的时候要发送一条数据,请求链接
sendMsg("request-link", channel);
// 监听标签页的关闭,关闭通知频道删除链接
window.addEventListener("unload", () => {
sendMsg("close-link", channel);
});
// 监听所有的消息类型
channel.addEventListener("message", (e) => {
if (e.data.msg === "request-link") {
// 如果是首次建立链接,要确认链接,表示链接成功了
sendMsg("confirm-link", channel);
// 向监听器中添加当前标签页
channel.listeners.add(e.data.id);
} else if (e.data.msg === "confirm-link") {
// 如果有标签页确定了链接,我们就讲它页添加到监听器
channel.listeners.add(e.data.id);
} else if (e.data.msg === "close-link") {
// 断开链接时要将监听的标签页移除
channel.listeners.delete(e.data.id);
}
});
return channel;
}
好了,先来看一下监听的时候有效。
可以看到,我们监听是有效果的,那么我们就可以利用 listeners 的长度来判断播放页是否被打开了。
const playBtns = document.querySelectorAll('i[data-name]');
const channel = createChannel('music');
for (const btn of playBtns) {
btn.onclick = (e) => {
// 没有监听的时候打开播放页
if (channel.listeners.size === 0) {
window.open(`/music.html?name=${e.target.dataset.name}`, '_blank');
} else {
// 如果有监听了通知播放页切换歌曲
channel.postMessage({
musicName: e.target.dataset.name,
});
}
};
}
而播放页在第一次打开时通过 url 的数据播放,后期只需要根据信息切换歌曲就可以了。
const audio = document.querySelector('audio');
audio.ondurationchange = () => {
audio.currentTime = 9;
audio.play();
};
function play(name) {
audio.src = `/music/${name}`;
}
function init() {
const url = new URL(location.href);
const name = url.searchParams.get('name');
if (!name) {
return;
}
document.title = name
play(name);
}
init();
const channel = createChannel('music');
channel.addEventListener('message', (e) => {
if (e.data.musicName) {
document.title = e.data.musicName
play(e.data.musicName);
}
});
我们来看一下效果如何。
总结
这个效果已经实现了,当然这是非常简易的版本,但是逻辑就是这个逻辑,在实际开发者还要考虑不少细节,特别是对建立和确认建立这个环节,要多做一些设计。
通过这篇文章,我们学习了如何使用 Broadcast Channel API 来实现一个简单的音乐播放器,它可以让我们在列表页切换歌曲,而在播放页播放歌曲,而且只需要一个标签页就可以了。
这样不仅可以节省资源,也可以提高用户体验。
当然,这只是 Broadcast Channel API 的一个应用场景,它还有很多其他的用途,比如实现多窗口的协作、同步、通知等功能。
如果你对这个 API 感兴趣,可以查看 MDN 的文档,或者尝试一些其他的例子 ,来发现它的更多可能性。
本文来源
本文来源自渡一官方公众号:Duing,欢迎关注,获取最新、最全、最深入的技术讲解
感谢你阅读本文,如果你有任何疑问或建议,请在评论区留言,如果你觉得这篇文章有用,请点赞收藏或分享给你的朋友!
转载自:https://juejin.cn/post/7254811625055797306