瀏覽器跨 Tab 窗口通信原理及應(yīng)用實(shí)踐
當(dāng)前位置:點(diǎn)晴教程→知識管理交流
→『 技術(shù)文檔交流 』
最近,相信大家一定被這么個(gè)動效給刷屏了: 以至于,基于這個(gè)效果的二次創(chuàng)作層出不窮,眼花繚亂。 基于跨窗口通信的彈彈球: 基于跨窗口通信的 Flippy Bird: 我也嘗試制作了一個(gè)跨 Tab 窗口的 CSS 動畫聯(lián)動,效果如下: 代碼不多,核心代碼 200 行,感興趣的可以戳這里:Github - broadcastAnimation 當(dāng)然,本文的核心不是去一一剖析上面的效果具體的實(shí)現(xiàn)方式,而是講講其中比較關(guān)鍵的一個(gè)技術(shù)點(diǎn): 而是應(yīng)用如何在多窗口下進(jìn)行互相通信。 所謂多窗口下進(jìn)行互相通信,是指在瀏覽器中,不同窗口(包括不同標(biāo)簽頁、不同瀏覽器窗口甚至不同瀏覽器實(shí)例)之間進(jìn)行數(shù)據(jù)傳輸和通信的能力。 當(dāng)然,本文我們探討的是純前端的跨 Tab 頁面通信,在非純前端的方式下,我們可以借助諸如 Web Socket 等方式,藉由后端這個(gè)中間載體,進(jìn)行跨頁面通信。 因此,本文我們更多的重心將放在,如何基于純前端技術(shù),實(shí)現(xiàn)多窗口下進(jìn)行互相通信。 為了實(shí)現(xiàn)跨窗口通信,它應(yīng)該需要具備以下能力:
方式一:Broadcast Channel()Broadcast Channel 是一個(gè)較新的 Web API,用于在不同的瀏覽器窗口、標(biāo)簽頁或框架之間實(shí)現(xiàn)跨窗口通信。它基于發(fā)布-訂閱模式,允許一個(gè)窗口發(fā)送消息,并由其他窗口接收。 其核心步驟如下:
同時(shí),Broadcast Channel 遵循瀏覽器的同源策略。這意味著只有在同一個(gè)協(xié)議、主機(jī)和端口下的窗口才能正常進(jìn)行通信。如果窗口不滿足同源策略,將無法互相發(fā)送和接收消息。 因?yàn)橛型聪拗?,我們需要起一個(gè)服務(wù),這里我基于 Vite 快速起了一個(gè) Vue 項(xiàng)目,簡單的基于 其核心代碼非常簡單: <template> <div id="j-main"> // ... </div> </template> <script> import { onMounted } from 'vue'; export default { setup() { function createBroadcastChannel() { broadcastChannel = new BroadcastChannel('broadcast'); broadcastChannel.onmessage = handleMessage; } function sendMessage(data) { broadcastChannel.postMessage(data); } function handleMessage(event) { console.log('接收到 event', event); // TODO: 處理接收到信息后的邏輯 } function resizeEventBind() { window.addEventListener('resize', () => { const pos = getCurPos(); sendMessage(pos); }); } // 計(jì)算當(dāng)前元素距離顯示器窗口右上角的距離 function getCurPos() { const barHeight = window.outerHeight - window.innerHeight; const element = document.getElementById('j-main'); const rect = element.getBoundingClientRect(); // 獲取元素相對于屏幕左上角的 X 和 Y 坐標(biāo) const x = rect.left + window.screenX; // 元素左邊緣相對于屏幕左邊緣的距離 const y = rect.top + window.screenY + barHeight;// 元素頂部邊緣相對于屏幕頂部邊緣的距離 return [x, y]; }
onMounted(() => { createBroadcastChannel(); resizeEventBind(); }); return {}; } }; </script> <style lang="scss"></style> 這里,我們的核心邏輯在于:
在 這樣,當(dāng)我們同時(shí)打開兩個(gè)窗口,移動其中一個(gè)窗口,就可以向另外一個(gè)窗口發(fā)生當(dāng)前窗口希望傳遞過去的信息,在本例子中就是 假設(shè) 可以看到,如果我們同時(shí)打開兩個(gè)一個(gè)的頁面,當(dāng)觸發(fā)右邊頁面的 Resize,左邊的頁面會收到基于 而一個(gè)完整的 Event 信息如下: 譬如,傳遞過來的信息放在 data 屬性內(nèi)、同時(shí)也可以獲取當(dāng)前的的 Broadcast Name 等。 基于 BroadcastChannel,就可以實(shí)現(xiàn)每個(gè) Tab 內(nèi)的核心信息互傳, 可以得知當(dāng)前在線設(shè)備數(shù),再基于這些信息去完成我們想要的動畫、交互等效果。 這里的核心點(diǎn),還是:
其本質(zhì)就是一個(gè)數(shù)據(jù)共享池子。 方式二:SharedWorker API好,介紹完 Broadcast Channel(),我們再來看看 SharedWorker API。 SharedWorkerAPI 是 HTML5 中提供的一種多線程解決方案,它可以在多個(gè)瀏覽器 TAB 頁面之間共享一個(gè)后臺線程,從而實(shí)現(xiàn)跨頁面通信。 與其他 Worker 不同的是,SharedWorker 可以被多個(gè)瀏覽器 TAB 頁面共享,且可以在同一域名下的不同頁面之間建立連接。這意味著,多個(gè)頁面可以通過 SharedWorker 實(shí)例之間的消息傳遞,實(shí)現(xiàn)跨 TAB 頁面的通信。 它的實(shí)現(xiàn)與上面的 Broadcast Channel 非常類似,我們來看一看實(shí)際的代碼: <template> <div id="j-main"> // ... </div> </template> <script> import { onMounted } from 'vue'; export default { setup() { // 創(chuàng)建一個(gè) SharedWorker 對象 let worker;
function initWorker() { // 創(chuàng)建一個(gè) SharedWorker 對象 worker = new SharedWorker('/shared-worker.js', 'tabWorker'); // 監(jiān)聽消息事件 worker.port.onmessage = function (event) { console.log('接收到 event', event); handleMessage(event); }; }
function handleMessage(data) { // TODO: 處理接收到信息后的邏輯 } function sendMessage(data) { // 發(fā)送消息 worker.port.postMessage(data); } function resizeEventBind() { window.addEventListener('resize', () => { const pos = getCurPos(); sendMessage(pos); }); } function getCurPos() { const barHeight = window.outerHeight - window.innerHeight; const element = document.getElementById('j-main'); const rect = element.getBoundingClientRect(); // 獲取元素相對于屏幕左上角的 X 和 Y 坐標(biāo) const x = rect.left + window.screenX; // 元素左邊緣相對于屏幕左邊緣的距離 const y = rect.top + window.screenY + barHeight;// 元素頂部邊緣相對于屏幕頂部邊緣的距離 return [x, y]; }
onMounted(() => { initWorker(); resizeEventBind(); }); return {}; } }; </script> <style></style> 簡單描述一下,上面也說了,跨 Tab 頁通信的核心在于數(shù)據(jù)向外的發(fā)送與接收的能力:
當(dāng)然,上面有引入一個(gè) //shared-worker.js const connections = []; onconnect = function (event) { var port = event.ports[0]; connections.push(port); port.onmessage = function (event) { // 接收到消息時(shí),向所有連接發(fā)送該消息 connections.forEach(function (conn) { if (conn !== port) { conn.postMessage(event.data); } }); }; port.start(); }; 簡單解析一下,下面對其進(jìn)行解析:
總而言之,shared-worker.js 腳本創(chuàng)建了一個(gè)共享 Worker 實(shí)例,它可以接收來自不同頁面的連接請求,并將接收到的消息發(fā)送給其他連接的頁面。通過使用 SharedWorker API,實(shí)現(xiàn)跨 TAB 頁面之間的通信和數(shù)據(jù)共享。 同理,我們來看看基于 Worker 的數(shù)據(jù)傳輸效果,同樣是簡化 DEMO,當(dāng) Resize 窗口時(shí),向另外一個(gè)窗口發(fā)送當(dāng)前窗口下 可以看到,如果我們同時(shí)打開兩個(gè)一個(gè)的頁面,當(dāng)觸發(fā)右邊頁面的 Resize,左邊的頁面會利用 而一個(gè)完整的 Event 信息如下: 可以看到,在 SharedWorker 方式中,傳輸數(shù)據(jù)與 Broadcast Channel 是一樣的,都是利用
兼容性方面,到今天(2023-11-26),broadcast Channel 看著是兼容性更好的方式: 另外,需要注意的是,兩個(gè)方法都使用了 但是,單獨(dú)使用 方式三:localStorage/sessionStorageOK,最后一種跨 Tab 窗口通信的方式是利用 與上面 Broadcast Channel、SharedWorker 稍微不同的地方在于:
簡單看看代碼: <template> <div id="j-main"> // ... </div> </template> <script> import { ref, reactive, computed, onMounted } from 'vue'; export default { setup() { function initLocalStorage() { let tabArray = JSON.parse(localStorage.getItem('tab_array')); if (!tabArray) { const tabIndex = 1; id = tabIndex; localStorage.setItem('tab_array', JSON.stringify([tabIndex])); } else { const tabIndex = tabArray[tabArray.length - 1] + 1; id = tabIndex; const newTabArray = [...tabArray, tabIndex]; localStorage.setItem('tab_array', JSON.stringify(newTabArray)); } } function setLocalStorage(data) { localStorage.setItem(`tab_index_${id}`, JSON.stringify(data)); } function handleMessage(data) { const rArray = JSON.parse(data); remoteX.value = rArray[0]; remoteY.value = rArray[1]; } function resizeEventBind() { window.addEventListener('resize', () => { const pos = getCurPos(); setLocalStorage(pos); });
window.addEventListener('storage', (event) => { console.log('localStorage 變化了!', event); console.log('鍵名:', event.key); console.log('變化前的值:', event.oldValue); console.log('變化后的值:', event.newValue); handleMessage(event.newValue); }); } function getCurPos() { const barHeight = window.outerHeight - window.innerHeight; const element = document.getElementById('j-main'); const rect = element.getBoundingClientRect(); // 獲取元素相對于屏幕左上角的 X 和 Y 坐標(biāo) const x = rect.left + window.screenX; // 元素左邊緣相對于屏幕左邊緣的距離 const y = rect.top + window.screenY + barHeight;// 元素頂部邊緣相對于屏幕頂部邊緣的距離 return [x, y]; }
onMounted(() => { initLocalStorage(); resizeEventBind(); }); return {}; } }; </script> <style></style> 同樣的簡單解析一下:
交互傳輸結(jié)果,與上述兩個(gè)動圖是一致的,就不額外貼圖了,但是基于 我們通過 當(dāng)然,由于 雖然看起來這種方式最不優(yōu)雅,但是結(jié)合兼容性一起看, localstorage 反而是兼容性最好的方式。在數(shù)據(jù)量較小的時(shí)候,性能相差不會太大,反而可能是更好的選擇。 我基于上面三種方式:Broadcast Channel、SharedWorker 與 localStorage,都實(shí)現(xiàn)了一遍下面這個(gè)跨 Tab 頁的 CSS 聯(lián)動動畫: 三種方式的代碼都不多,感興趣的可以戳這里:Github - broadcastAnimation 實(shí)際應(yīng)用思考當(dāng)然,上面的實(shí)現(xiàn)其實(shí)有很大一個(gè)瑕疵。 那就是我們只顧著實(shí)現(xiàn)通信,沒有考慮實(shí)際應(yīng)用中的一些實(shí)際問題:
基于實(shí)際應(yīng)用,我們需要基于上述 3 種方式,進(jìn)一步細(xì)化方案。 上面,為了方便演示,每次傳輸數(shù)據(jù)時(shí),只傳輸動畫需要的數(shù)據(jù)。而實(shí)際應(yīng)用,我們可以需要細(xì)化整個(gè)傳輸數(shù)據(jù),設(shè)定合理的協(xié)議。譬如: { // 傳輸狀態(tài): // 1 - 首次傳輸 // 2 - 正常通信 // 3 - 頁面關(guān)閉 status: 1 | 2 | 3, data: {} } 接收方需要基于收到信息所展示的不同的狀態(tài),做出不同的反饋。 當(dāng)然,還有一個(gè)問題,我們?nèi)绾沃理撁姹魂P(guān)閉了?基于組件的 這些信息都有可能因?yàn)?Tab 頁面失活,導(dǎo)致關(guān)閉的信息無法正常被發(fā)送出去。所以,實(shí)際應(yīng)用中,我們經(jīng)常用的一項(xiàng)技術(shù)是心跳上報(bào)/心跳廣播,一旦建立連接后,間隔 X 秒發(fā)送一次心跳廣播,告訴其他接收端,我還在線。一旦超過某個(gè)時(shí)間閾值沒有收到心跳上報(bào),各個(gè)訂閱方可以認(rèn)為該設(shè)備已經(jīng)下線。 總而言之,跨 Tab 窗口通信應(yīng)用在實(shí)際應(yīng)用的過程中,我們需要思考更多可能隱藏的問題。 跨 Tab 窗口通信應(yīng)用場景當(dāng)然,除了最近大火的跨 Tab 動畫應(yīng)用場景,實(shí)際業(yè)務(wù)中,還有許多場景是它可以發(fā)揮作用的。這些場景利用了跨 Tab 通信技術(shù),增強(qiáng)了用戶體驗(yàn)并提供了更豐富的功能。 以下是一些常見的應(yīng)用場景:
譬如這個(gè):
舉幾個(gè)實(shí)際的例子:
總之,跨 Tab 窗口通信在實(shí)時(shí)協(xié)作、數(shù)據(jù)同步、通知提醒等方面都能發(fā)揮重要作用,為用戶提供更流暢、便捷的交互體驗(yàn)。 最后本文只羅列了 3 種較為常見,適用性強(qiáng)的方式。除去本文羅列的方式,肯定還有其他方式能夠?qū)崿F(xiàn)跨 Tab 通信。 譬如,基于 Window: opener property 配合 更多有意思的方式,期待大家的補(bǔ)充與探索。 好了,本文到此結(jié)束,希望對你有幫助 😃 作者:ChokCoco 來源鏈接:https://www.cnblogs.com/coco1s/p/17861360.html 該文章在 2023/11/28 15:03:45 編輯過 |
關(guān)鍵字查詢
相關(guān)文章
正在查詢... |