likes
comments
collection
share

标签页的打开控制与跨标签页通话

作者站长头像
站长
· 阅读数 20

这是每一个音乐网站都会实现的功能,快来看看自己会不会做!

我们看下图这个效果,在首次点击播放音乐时,打开了一个新的标签页并在新的标签页中播放音乐。

再次切换音乐时,并没有打开新的标签页,而是在原来的标签页基础上进行播放。

标签页的打开控制与跨标签页通话

不仅仅是这个网站,几乎所有的音乐类网站都是这么做的。

那这种效果是怎么实现的呢?我是渡一前端子辰老师,今天我将带你一探究竟,教你如何利用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 来通讯,我们要封装一个通用的方法,所以我们就要思考以下几个问题:

  1. 当前打开的便签页中建立通讯链接的有几个?
  2. 我们怎么知道建立链接成功了?
  3. 便签页通讯时我们怎么知道谁发送的消息?
  4. 标签页的通讯链接什么时候关闭?

好了,我们根据这几个实现了如下代码。

/**
 * @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,欢迎关注,获取最新最全最深入的技术讲解

感谢你阅读本文,如果你有任何疑问或建议,请在评论区留言,如果你觉得这篇文章有用,请点赞收藏或分享给你的朋友!