公众号【码农爱摸鱼】,专注于在摸鱼中愉快的工作和学习~
大家都知道,浏览器可以同时打开多个标签页的,而且在实际的使用场景中,打开多个标签页是非常常见的情况。 比如说某个网站的列表页面,点击之后新开标签页展示详情,用户在详情里面进行了操作,列表标签页也需要更新数据。 总的来说:多个标签页之间通信是很有必要的!!!
以下具体聊聊一些通信的方法:
1、localStorage
由于localStorage在同源(同协议、同IP、同端口)下可以实现共享,所以可以利用windw.addEventLister来监听storage内的 数据变化,实现标签页之间的通信效果。 下面来一个简单的例子: a.html
javascript
function setStorage(){
if(!localStorage.getItem('hasStorage')){
window.open('/b.html')
localStorage.setItem('hasStorage',1)
}
setTimeout(()=>{
localStorage.setItem('man','摸鱼君')
},3000)
}
b.html
javascript
window.onunload = () =>{
localStorage.removeItem('hasStorage')
localStorage.removeItem('man')
}
const app = document.getElementById('app')
window.addEventListener('stroage', e=>{
const app = document.getElementById('app')
app.innerText = e.newValue
})
2、window.postMessage(message, targetOrigin, [transfer])
先说说三个参数的意义: message:发送到其他window的信息 targetOrigin:发送信息的目标url,如果没有指定目标url用字符串*表示;但是为了安全,如果你知道目标url建议使用目标url。 transfer(可选参数):是一串和 message 同时传递的 Transferable 对象。这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。
window.postMessage只是用来发送消息的,接受消息则要通过监听方法:window.addEventListener('message', receiveMessage, false);实现。 window.addEventListener('message')的回调参数有三个:source(消息源,消息的发送窗口/iframe)、origin(消息源的URI,可以用来验证数据源)、data(发送过来的数据)
window.postMessage与localStorage相比最大的好处就是可以安全地实现跨源通信,使用起来更加方便。
3、webSocket
webSocket是一种网络通讯协议,它是一个全双工通信的协议,也就是说客户端和服务端可以互相通信,享受平等关系(比如说聊天工具)。 那么怎么利用webSocket实现多标签页通信呢?
其实原理也很简单,假如我们a.html和b.html都与服务器建立了websocket连接,那么这两个页面都可以实时接收服务端发来的消息,同时也可以实时向服务端发送消息。 如果a.html更改了数据,向服务端发送一条消息或数据,服务端在将这条消息或数据发送给b.html即可,这样就简单实现了两个标签页之间的通信。
说白了,就是给两个标签页之间提供了一个通信的"中介",两个页面通过这个"中介"来进行通信,所以这种方式也是可以跨源的。
4、sharedWorker
每个前端开发者都知道,js是单线程的。不过在实际的开发过程中,单线程对比于多线程,还是略有不足。 为了弥补单线程的缺陷,webWorker就应运而生了,它就是拿来给js提供多线程环境的。
其中,sharedWorker就是webWorker中的一种,它可以由所有同源页面共享。
其实说白了,sharedWorker就和webSocket的实现原理非常相似,都是发送消息/接收消息,在理解上可以直接理解为webSocket服务器。
简单上点代码: sharedWorker.js
dart
const set = new Set()
onconnect = event => {
const port = event.ports[0]
set.add(port)
// 接收信息
port.onmessage = e => {
// 广播信息
set.forEach(p => {
p.postMessage(e.data)
})
}
// 发送信息
port.postMessage("sharedWorker发出信息")
}
a.html
xml
<script>
const sharedWorker = new SharedWorker('./sharedWorker.js')
sharedWorker.port.onmessage = e => {
console.info("A收到:", e.data)
}
</script>
b.html
xml
<script>
const sharedWorker = new SharedWorker('./sharedWorker.js')
let btnB = document.getElementById("btnB");
btnB.addEventListener("click", () => {
sharedWorker.port.postMessage("B发出消息")
})
</script>
这就是一个最简单的a、b两个页面发送消息的实现demo了。SharedWorker初始化完成后:a页面首先收到"A收到:sharedWorker发出信息",然后b页面点击按钮,发送消息给a页面,a页面收到"A收到:B发出消息"。
5、BroadcastChannel
BroadcastChannel接口代理了一个命名频道,可以让指定origin下的任意browsing context 来订阅它。它允许同源的不同浏览器窗口、Tab页、frame 或者 iframe 下的不同文档之间相互通信。通过触发一个 message 事件,消息可以广播到所有监听了该频道的 BroadcastChannel 对象。
闲暇的时候写了个简单的demo,有兴趣可以自己跑一下来试试,代码奉上:
index.html
xml
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>摸鱼必备</title>
<!-- 页面样式 -->
<link rel="stylesheet" href="./index.css" />
</head>
<body>
<!-- 可移动的元素 -->
<div id="block" class="block" style="left: 0px; top: 0px"></div>
<!-- 初始化操作按钮 -->
<div id="init-btn" class="init-btn">
<span>初始化</span>
</div>
<!-- 处理交互逻辑 -->
<script src="./index.js"></script>
</body>
</html>
index.css
css
* {
padding: 0;
margin: 0;
}
html,
body {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
user-select: none;
}
html {
font-size: 16px;
line-height: 1;
}
body {
background-color: #EDF2F7;
}
.block {
display: block;
position: absolute;
width: 300px;
height: 200px;
border-radius: 8px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.07),
0 2px 4px rgba(0, 0, 0, 0.07),
0 4px 8px rgba(0, 0, 0, 0.07),
0 8px 16px rgba(0, 0, 0, 0.07),
0 16px 32px rgba(0, 0, 0, 0.07),
0 32px 64px rgba(0, 0, 0, 0.07);
background-color: #FFF;
cursor: grab;
}
.init-btn {
display: inline-block;
margin: 10px;
padding: 12px 16px;
border: 1px solid #2196F3;
border-radius: 8px;
background-color: #FFF;
color: #2196F3;
font-size: 16px;
text-align: center;
cursor: pointer;
}
.init-btn:hover {
border-color: #FFF;
background-color: #2196F3;
color: #FFF;
}
index.js
ini
(function () {
/**
* @typedef ChannelMessage
* @property {string} type
* @property {any} data
*/
/** 可移动元素 */
let block = document.getElementById('block');
/** 初始化按钮 */
let btn = document.getElementById('init-btn');
/** 跨标签通信广播 */
let broadcastChannel = new BroadcastChannel('DataTransfer');
/** 页面相对于窗口边缘的水平距离 */
let offsetX = 0;
/** 页面相对于窗口边缘的垂直距离 */
let offsetY = 0;
/**
* @description 转换坐标值
* @param {'p2s'|'s2p'} type
* @param {object} coords
* @param {number} coords.x
* @param {number} coords.y
*/
function changeCoords(type, coords) {
let { x, y } = coords;
let { screenLeft, screenTop } = window;
switch (type) {
// 页面坐标 -> 屏幕坐标
case 'p2s':
return {
x: x + screenLeft + offsetX,
y: y + screenTop + offsetY,
};
// 屏幕坐标 -> 页面坐标
case 's2p':
return {
x: x - screenLeft - offsetX,
y: y - screenTop - offsetY,
};
default:
console.error('转换失败:类型错误');
return { x: 0, y: 0 };
}
}
/**
* @description 处理校准距离
* @param {object} data
* @param {number} data.x
* @param {number} data.y
*/
function handleAdjustOffset(data) {
console.log('[AdjustOffset]', data);
offsetX = data.x;
offsetY = data.y;
if (btn) {
btn.remove();
}
}
/**
* @description 处理元素坐标更新
* @param {object} data
* @param {number} data.x 相对于屏幕的水平坐标
* @param {number} data.y 相对于屏幕的垂直坐标
*/
function handleUpdateCoords(data) {
let { x, y } = changeCoords('s2p', data);
block.style.left = x + 'px';
block.style.top = y + 'px';
}
/** 初始化元素坐标 */
function init() {
/** 元素宽度 */
let elementW = block.clientWidth;
/** 元素高度 */
let elementH = block.clientHeight;
/** 页面宽度 */
let windowW = window.innerWidth;
/** 页面高度 */
let windowH = window.innerHeight;
// 将元素移动到页面中心
block.style.left = Math.round(windowW / 2 - elementW / 2) + 'px';
block.style.top = Math.round(windowH / 2 - elementH / 2) + 'px';
}
/**
* @description 解析广播消息
* @param {MessageEvent<ChannelMessage>} event
* @returns {ChannelMessage}
*/
function parseChannelMessage(event) {
let data = event.data;
if (data && data.type) {
return data;
} else {
return {
type: '',
data: null,
};
}
}
// 处理元素拖拽
block.addEventListener('mousedown', function (downEvent) {
/** 元素相对于屏幕的坐标 */
let elementCoords = changeCoords('p2s', {
x: parseInt(block.style.left),
y: parseInt(block.style.top),
});
/** 元素起始水平坐标 */
let elementX = elementCoords.x;
/** 元素起始垂直坐标 */
let elementY = elementCoords.y;
/** 鼠标起始水平坐标 */
let cursorX = downEvent.screenX;
/** 鼠标起始垂直坐标 */
let cursorY = downEvent.screenY;
/**
* @description 处理鼠标移动
* @param {MouseEvent} moveEvent
*/
let handleMove = function (moveEvent) {
let newX = elementX + (moveEvent.screenX - cursorX);
let newY = elementY + (moveEvent.screenY - cursorY);
let coords = { x: newX, y: newY };
// 更新当前页面元素坐标
handleUpdateCoords(coords);
// 通知其他页面
broadcastChannel.postMessage({
type: 'UpdateCoords',
data: coords,
});
};
let handleUp = function () {
window.removeEventListener('mousemove', handleMove);
window.removeEventListener('mouseup', handleUp);
};
window.addEventListener('mousemove', handleMove);
window.addEventListener('mouseup', handleUp);
});
// 处理按钮点击
btn.addEventListener('click', function (ev) {
let { pageX, pageY, screenX, screenY } = ev;
let offsetX = 0,
offsetY = 0;
let relativeX = screenX - window.screenLeft;
let relativeY = screenY - window.screenTop;
while (relativeX > pageX) {
offsetX++;
relativeX--;
}
while (relativeY > pageY) {
offsetY++;
relativeY--;
}
// 更新当前页面数据
handleAdjustOffset({
x: offsetX,
y: offsetY,
});
// 通知其他页面更新数据
broadcastChannel.postMessage({
type: 'AdjustOffset',
data: { x: offsetX, y: offsetY },
});
// 获取元素相对于屏幕的坐标,用于同步其他页面
let coords = changeCoords('p2s', {
x: parseInt(block.style.left),
y: parseInt(block.style.top),
});
// 更新当前页面元素的坐标
handleUpdateCoords(coords);
// 通知其他页面更新元素坐标
broadcastChannel.postMessage({
type: 'UpdateCoords',
data: coords,
});
});
// 处理广播消息
broadcastChannel.addEventListener('message', function (ev) {
let { data, type } = parseChannelMessage(ev);
switch (type) {
case 'AdjustOffset':
handleAdjustOffset(data);
break;
case 'UpdateCoords':
handleUpdateCoords(data);
break;
default:
break;
}
});
init();
})();
我是摸鱼君,你的【三连】就是摸鱼君创作的最大动力,如果本篇文章有任何错误和建议,欢迎大家留言!
文章持续更新,可以微信搜索 【码农爱摸鱼】关注公众号第一时间阅读。