论多窗口相互关联下window.open打开已在的窗口时只激活不刷新的实现方案

2023-05-16

前端博主,热衷各种前端向的骚操作,经常想到哪就写到哪,如果有感兴趣的技术和前端效果可以留言~博主看到后会去代替大家踩坑的~
主页: oliver尹的主页
格言: 跌倒了爬起来就好~
来个关注吧,点个赞吧,谢谢

论多窗口相互关联下window.open打开已在的窗口时只激活不刷新的实现方案

  • 一、前言
  • 二、本文内容概述
  • 三、待解决问题
  • 四、问题解决说明
    • 4.1 刷新问题解决
    • 4.2 多窗口的相互关联激活
      • 4.2.1 确认窗口name值
      • 4.2.2 确认窗口是否已经打开过
      • 4.2.3 给窗口命名
    • 4.3 缓存的问题
  • 五、代码下载
  • 六、小结

一、前言

近期,我司有个小伙伴遇到这么个场景实现起来感觉有点困难与我讨论,大概是这样的:
项目框架使用的是Vue开发的单页应用,这个单页应用在打开某个大模块路由时 需要使用window.open()去打开新的模块,也就是说如果打开的是大模块,那么会新建一个窗口,在这个窗口中打开页面,但是如果打开的换菜单又是小路由,那么使用的VueRouter切换路由,这就导致了一个问题,在重复点击大模块时出现了重复打开新窗口,并且后续又陆陆续续出现了许许多多的问题…因此有了本文作记录,以备后续不时之需~
耐心看完,也许你会有所收获~

二、本文内容概述

本文主要解决的场景是:
在类似Vue的单页应用项目中,在任意一个窗口内首次打开指定页面时通过新建窗口的方式打开,在任意一个窗口内打开已打开的界面时,仅激活对应窗口,比如:在A页面使用类似 window.open() 的效果打开了同域下的B页面和C页面(A,B,C页面具有相同的路由系统),切换窗口至B页面,点击B页面下的C页面地址,此时不会再次新建C页面窗口,而是将已打开的C页面窗口激活至可视状态,且C页面窗口不触发刷新保留原来的内容与操作状态;
同时,在任意界面中执行浏览器的f5刷新,不影响使用效果;
在这里插入图片描述
先简单说一下实现思路吧,在openUrl的内部判断当前名为title的窗口是否打开过,如果没有打开过,执行 window.open() 方法新建窗口,如果打开过,执行 window.open('javascript:;', title) 去激活名为title的窗口,让它显示出来
如果有小伙伴想要文件,请直接跳转至 第五部分下载文件 即可~

三、待解决问题

为了完成上面的场景需求,实际开发中遇到的主要问题一共存在三个:

  1. 刷新的问题,当使用 window.open(url,name) 打开对应name的窗口时,如果该name的窗口已存在确实会仅仅是激活对应name的窗口,但是这个激活会执行刷新,它刷新这个网页,假如这个网页存在复杂表单,用户输入的一半的内容或者页面操作的状态都将会被刷新掉,据产品经理讲体验甚是不好…
  2. 多页面相互激活的问题,这个问题就比较有意思了,由于是Vue开发的单页应用,整个路由系统其实是同一套,这就会出现一个问题,在A页面通过 window.open() 打开了B页面和C页面,而B页面和C页面自身其实也包含完整的路由系统的,这个路由系统中自然也有A,B,C的路由地址,那如果在B页面通过 window.open() 打开C页面,此时正确的逻辑应该是仅仅去激活C页面的窗口,而不是重新再打开一个,示例图如下:
    在这里插入图片描述

是不是挺有意思,这里面大概率存在通信问题,比如在C页面怎么知道在A页面中打开过哪些窗口并且获得窗口name等等~

  1. 缓存的问题,即浏览器刷新,在A页面执行浏览器的的f5刷新后,可以在点击对应模块时依然仅仅是去激活对应的模块页面,

四、问题解决说明

关于 window.open() 具体参数以及各个参数的说明可以看MDN的官方解释,具体链接如下:window.open

4.1 刷新问题解决

先说核心实现吧,刷新的问题核心解决应该是在激活的实现,而不是重新通过 window.open() 去打开url,经过实验,确认通过以下这个实现

window.open('javascript:;', name)

可能会有小伙伴奇怪,论激活窗口的功能不应该是通过 window.focus() 去实现么,使用 window.focus() 确实可以将指定name值的窗口激活,实现如下:

let win = null;
if(win){
  win.focus()
}
else{
  win = window.open(url, name);
}

但是由于我们的场景比较奇葩,需要实现跨窗口激活,也就是说在C页面打开B页面时需要在C页面执行B页面这个 win.focus(),但由于win这个对象它其实是一个最顶层的 window对象,它无法被传递,被转化,即想使用postMessage发送,会提示发送失败,根本无法跨窗口,同理,win也无法被缓存,一旦刷新界面,这个win对象就会丢失无法留存,自然待解决问题中第三个问题也同样无法实现;
在尝试 window.focus() 这条路走不通后,发现了另外一种方法,即上面说到的 window.open('javascript:;', name) 这个方法,这个方法同样可以解决激活窗口的问题,并且这个name值的类型是一个字符串,是窗口的名字也是 winodw.open() 中的name,代码如下

window.open(url, name);

这种方法的实现和focus()的实现非常接近,无非就是将 win.focus() 改成了 window.open('javascript:;', name)

if(判断是否打开过){
  window.open('javascript:;', name)
}
else{
 window.open(url, name);
}

4.2 多窗口的相互关联激活

多窗口的相互关联激活细想一下,上面采用的是 window.open('javascript:;', name) 实现的激活,在这个代码中唯一的变量就是一个字符串格式的name,扩展一下思维,在任意一个窗口里知道了其它窗口的name,是不是都可以通过 window.open('javascript:;', name) 实现激活,想了想从理论上来说这个方法应该是可行的(当然事实证明确实可以),剩下的就是要解决以下这三个小问题:

  1. 知道已打开窗口的name值,目的是为了方便使用 window.open('javascript:;', name) 去激活;
  2. 判断窗口是否已经打开过,目的是为了方便知道使用 window.open('javascript:;', name) 去激活窗口还是使用 window.open(url, name) 去打开窗口;
  3. 给每一个打开的窗口命名,这个name值是必须唯一的,并且需要和执行window.open('javascript:;', name) 中的name对应起来,不对应起来的话执行open的时候会找不到name值的窗口,自然也就无法实现激活;

4.2.1 确认窗口name值

name值的小问题其实非常好解决,这是一个Vue单页应用,路由名称和路由地址是固定的,也就是说,所有个这些个新窗口的路由也完全相同,而我们的路由往往是由菜单名字和路由地址组成的,具有唯一性(你总不能两个菜单的名字完全一模一样吧),因此往往是如下这种结构

// template
<div v-for="item in menuList" :key="item.value" @click="changeRouter(item)">
  <div>{{item.title}}</div>  
</div>

// js
changeRouter(item){
  window.open('javascript:;', item.title)
}

因此,到这里基本解决了一个小问题,多窗口由于共享的是一个路由系统,name的名字其实在各个页面是相互知道的,我们只需要知道这个name的窗口是否处于打开状态即可;

4.2.2 确认窗口是否已经打开过

如何确认窗口是否已经打开过?想了下,由于多个窗口实际上处于同域下,localStorage自然成了首选,当打开一个新页面时,往localStorage里插入一条消息,关闭页面的时候再到localStorage中将对应的窗口信息删掉;
大致的代码如下:
首先是存储信息

// 执行首次跳转
window.open(url, name)
// 将跳转的名字和地址保存下来
setTsLocalData(name, url)

function setLocalData(key, value) {
  let local = localStorage.getItem("demo")

  if (local) {
    local = JSON.parse(local)
  }

  console.log(local)
  const item = { ...local }
  item[key] = value

  localStorage.setItem("demo", JSON.stringify(item))
}

接着在执行跳转前加一个判断,判断打开过的页面不再执行 window.open(url, name) ,而是转而去执行window.open(‘javascript:;’, name)激活窗口

const urlname = getLocalName(url)
// 存在name的话执行激活,不执行跳转
 if (urlname) {
  window.open('javascript:;', urlname)
  return
}

// 执行首次跳转
window.open(url, name)
// ...上方代码

4.2.3 给窗口命名

当通过window.open打开某个页面后,自动给窗口命名,名字的来源来自于localStorage,还是因为同域的关系,共享了localStorage,我们可以通过当前的网址去localStorage做匹配,匹配到网址后取到对应的key,这个key就是窗口的名字,将其赋值给窗口

function loadWidName(url) {
  const newUrl = getLocalName(url)
  window.name = newUrl
}

function getLocalName(url) {
  let local = localStorage.getItem(localStorageKey)
  if (!local) return false

  local = JSON.parse(local)

  // return Object.prototype.hasOwnProperty.call(local, key);
  let name = ''
  for (const key in local) {
    if (!Object.prototype.hasOwnProperty.call(local, key)) continue

    if (local[key] === url) {
      name = key
      break
    }
  }
  return name
}

注意的是,loadWidName这个函数得在页面一加载就执行,因为必须得在执行open之前就给窗口命名成功,否则执行open的时候会认为当前页面没有被打开过,直接进行跳转了;

4.3 缓存的问题

在想想,仔细想想,缓存的问题是不是已经被解决掉了,缓存最大的困难是什么,是窗口的命名,一旦进行刷新窗口的名字就丢失了,因此在别的窗口执行 window.open('javascript:;', item.title) 的时候会找不到带有这个名字的窗口;
而如果在一加载页面的时候就执行了上面4.2.3这个方法,去主动给窗口命名,那么刷新的时候自然也会去执行这个命名的过程,自然再怎么刷新窗口的name值一直是保持有的;
缓存的问题除了主动给窗口命名还剩下的就是关闭窗口是去 移除对应缓存 了,如果不移除,那么把窗口关闭后在其他窗口中点击关闭窗口时,会认为该窗口依然处于打开状态,那就会去执行window.open('javascript:;', urlname),打开一个空白页面~
移除代码很简单,给window添加一个beforeunload事件

window.addEventListener('beforeunload', function () {
    let local = localStorage.getItem("demo")
    if (!local) return false
  
    local = JSON.parse(local)
    const newItem = {}
    for (let item in local) {
      if (item !== key) {
        newItem[item] = local[item]
      }
    }
    localStorage.setItem("demo", JSON.stringify(newItem))
  return
})

到这里基本就实现了这个功能了

五、代码下载

代码已经上传到CSDN上了,由于是正式使用的我简单压缩了一下,下载地址如下:前端window.open实现激活而非打开的功能;
如果有需要源码的小伙伴留言或者私信留下邮箱,博主看到后会及时发送的~问题不大
至于使用也非常简单,在main.js中引入文件,这个文件扩展了一个类似于 window.open() 的方法,所有执行 window.open() 的地方改用我们的方法代替,在方法内部去判断是新建窗口还是激活窗口,比如

// main.js
import openUrl from "xxx"

// 使用时在内部实现逻辑判断是新建还是激活
window.openUrl(url,title) 代替 winow.open(url,title)

六、小结

本文主要解决了在类似Vue的单页应用项目中,在任意一个窗口内首次打开指定页面时通过新建窗口的方式打开,在任意一个窗口内打开已打开的界面时,仅激活对应窗口的场景功能;
这个场景可能比较冷门,但确实真实存在并被我司的小伙伴遇到了,当然博主这种方法是不是最优解不太清楚,如果有小伙伴知道更优的方式,一定,一定记得留言告诉博主,谢谢~
如果有帮助,点个赞,点个关注吧~谢谢

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

论多窗口相互关联下window.open打开已在的窗口时只激活不刷新的实现方案 的相关文章

  • javascript - 检测到浏览器/选项卡关闭时发出警报

    我有这个代码 当我单击链接 or refresh or 关闭选项卡 但我需要警惕only on close窗口 选项卡 这个怎么做 我的网站上有许多外部和内部链接
  • C# WPF DragMove 不带 Window_LocationChanged()

    我在 Windows 窗体中实现了类似于 DragMove 的功能 但边界设置为主屏幕边距的 10 个单位 当切换到 WPF 时我发现这个线程 https stackoverflow com questions 568012 wpf win
  • 如何使用 C++ 在 Linux 中创建窗口?

    我期待 Linux API 与 Windows API 类似 我在 Google 上看到的都是对 Qt 和 GTK 的引用 我实际上只需要一个简单的窗口来使用 OpenGL 进行绘制 所以这些库对于我的使用来说显得臃肿 Linux下Qt和G
  • 将元素与窗口底部对齐,但允许滚动到下方的内容

    我现在正在编写一个 jquery 滑块 并将宽度设置为 100 并且无论窗口如何调整大小 我都希望它与窗口底部对齐 我已经找到了让它在向下滚动时一直粘在底部的方法 但我不希望这样 我希望能够滚动到此滑块下方以获取更多内容 这个网站展示了我正
  • 活动背景昏暗

    我试图将活动显示为具有暗背景的浮动窗口 并且我使用以下代码来执行此操作 该代码取自 Google I O 2016 项目 protected void setupFloatingWindow int width int height int
  • 子窗口关闭时如何运行父窗口的功能?

    我正在调用 javascript window open 函数来在弹出窗口中加载另一个网址 用户完成操作后 会将他们带到最后一页 其中有一个链接 其中显示调用 window close 函数的关闭窗口 现在 当该页面关闭时 我需要更新打开窗
  • 从渲染器接收消息超时:600.000 当我们使用 Jenkins windows 服务模式执行 selenium 脚本时

    我们每天都使用 jenkins 窗口服务 无头模式 执行我们的 selenium 自动化脚本 直到昨天它都工作正常 突然它停止工作并且无法启动浏览器 它显示以下错误消息 15536 77874 187 严重 从渲染器接收消息超时 600 0
  • C# 将程序添加到Windows启动(Windows 7)

    我试图通过将程序执行路径添加到注册表编辑器来将我的程序添加到 Windows 启动程序中 这是代码 RegistryKey registryKey Registry CurrentUser OpenSubKey SOFTWARE Micro
  • 打开带有动态内容的窗口

    是否可以从 PHP 打开一个具有预定义内容的窗口 很明显 您可以从框架现有页面的 javascript 链接打开一个窗口 或者仅从引用现有页面的常规 a 标记执行 target blank 但我正在生成一些内容 并希望在新链接中打开该内容
  • SDL2 升起窗口而不给予焦点

    我需要在窗口上显示工具提示 我正在使用工具提示创建第二个窗口 并使用 SDL RaiseWindow 将其带到顶部 然而 这样做会导致工具提示窃取焦点 这不是我想要的 有没有办法在不改变焦点的情况下将窗口置于顶部 另外 有没有办法在不改变窗
  • 如何在UWP中获取可用的串口?

    我正在寻找可以获取 UWP 应用程序中的串行端口列表的 API 由于 System IO Ports 不适用于 UWP 您能否建议以下代码的任何替代方案 string ports SerialPort GetPortNames 首先将此项目
  • 当页面比屏幕大时如何将div定位在屏幕中间

    您好 我正在使用类似于以下内容的方法来将 div 放置在屏幕中间
  • createwindow(...)之后,如何给窗口赋予颜色?

    我创建了一个窗口 其句柄是handle parent 然后我创建了一个子窗口 如下所示 hwnd child CreateWindow child class name T WS CHILDWINDOW 0 0 0 0 hwnd paren
  • 如何使调整 WPF 窗口大小时不那么“滞后”?

    我对 WPF 世界比较陌生 我立即注意到的一件事是 当您调整窗口大小时 窗口内容的绘制是多么滞后 例如 如果窗口边缘有滚动条 则这些滚动条在缩小时将部分隐藏 并且在放大时它们与窗口边框之间有空间 即使在 Visual Studio 中创建的
  • 来自静态资源的 Wpf 窗口标题

    我正在使用资源字典进行本地化 我在 wpf 中有以下代码
  • 无法打开目标 = 空白的 Electron webview 链接

    我正在使用 Electron 我有一个显示外部网站的 webview 但我无法成功显示通常由该网站上的链接打开且目标 blank 的附加窗口 a href mentions html target blank Mentions l gale
  • IE 不会在使用 window.open 创建的窗口中加载 PDF

    问题就在这里 仅发生在 Internet Explorer IE 中 我有一个页面 其中包含指向几种不同类型文件的链接 这些文件中的链接执行一个 Javascript 函数 该函数打开一个新窗口并加载特定文件 这非常有效 除非我需要在新窗口
  • 在新选项卡或窗口中打开链接[重复]

    这个问题在这里已经有答案了 是否可以开一个a href链接在新选项卡而不是同一选项卡中 a href http your url here html Link a 您应该添加target blank and rel noopener nor
  • .NET WPF 窗口淡入和淡出动画

    下面是窗口淡入和淡出动画的代码片段 Create the fade in storyboard fadeInStoryboard new Storyboard fadeInStoryboard Completed new EventHand
  • WPF 模式进度窗口

    如果这个问题已经被回答了很多次 我很抱歉 但我似乎找不到适合我的答案 我想创建一个模式窗口 在我的应用程序执行长时间运行的任务时显示各种进度消息 这些任务在单独的线程上运行 我能够在过程的不同阶段更新进度窗口上的文本 跨线程通信一切正常 问

随机推荐