如何有意地冻结浏览器窗口(就像alert,confirm和prompt一样)?

8

是否有办法像alertconfirmprompt一样故意冻结浏览器窗口(简称“acp”)?

我想展示一个带有自定义css的模态对话框,而不是使用原生弹出框来实现“acp”,但我也想像“acp”一样能够冻结浏览器直到用户反馈。

但是,人们为什么认为这是不好的做法呢(嗯,我要downvote了)!那么,当这是不好的做法时 - 为什么“acp”提供这种同步行为呢?因为在某些特定情况下,它恰好是适当 UX 的正确工具。那些原生的模态对话框看起来真的很丑,并且同时非常受限制。

以下是一个快速而简单的示例,可以完全冻结浏览器,直到用户给出反馈。假设我们有一个具有 forminput[type="reset"] 元素的表单。因此,在我们真正让 form 重置之前,我们会问用户类似于:“您确定要重置吗(数据将丢失)?”

如果我对原生模态对话框的外观感到满意(但事实并非如此),我可以这样做:

document.querySelector('form').addEventListener(
  'reset', e => confirm('Are you sure you want to reset (data will be lost)?') || e.preventDefault()
);
<form>
  <input placeholder="type something, reset and cancel to keep input value" style="width:100%">
  <input type="reset">
</form>

所以,“每个人”都应该同意(编辑:或至少有人可以这样做),这不是不好的做法,对吧?
但我们如何通过自定义样式的模态框/对话框/弹出框来实现相同的效果?
当然,我并不是要求 HTML/CSS 的部分,而是需要用 JavaScript 冻结浏览器窗口的能力!

说实话,我其实预计会收到一些负评,但也许有这位“杀手级 JavaScript 忍者”,他们有一个特殊的技巧可以实现……


3
“每个人”都应该同意这不是一个糟糕的做法,对吗?不,我不同意。仅因为旧的本地方法会冻结浏览器,并不意味着你的逻辑也应该如此。防止用户与页面交互,并不意味着你必须冻结浏览器。覆盖层的整个概念正是为此而存在的。 - Taplar
2
你不能这样做,但你可以让它看起来像是你这样做了。例如,如果你看一下Bootstrap模态框,https://getbootstrap.com/docs/4.0/components/modal/,在你的例子中,不要使用重置按钮,只需显示你的模态框,然后手动调用表单的重置。 - Keith
1
那么,什么情况下是不良实践呢?为什么“acp”实际上提供这种同步行为?因为在某些特定的场景中,它恰好是适当用户体验的正确工具。我从未遇到过一个UXer认为冻结浏览器并剥夺用户控制是一种积极的体验。 - Taplar
2
不确定你想要什么答案。这是不可能的。主要原因是,如果你冻结了浏览器,你认为会呈现UI吗?你刚刚阻塞了浏览器的事件循环,它将无法处理用户的任何输入。同步方法确实感觉不错,但如果你使用async / await,你可以做到const sure = await showConfirm();,它只需要你的确认对话框是一个promise。 - Keith
2
是的,这就是为什么我说你需要手动调用它。无论如何,这就是我做的方式,似乎并不会使代码变得更加复杂,特别是如果你是一个相当有经验的人。 - Keith
显示剩余14条评论
2个回答

6

像本地对话框那样,您无法冻结浏览器应用程序。它们不是使用JavaScript构建的,而是使用本机代码构建的,可以以任何方式影响浏览器应用程序。 JavaScript被禁止以这种方式影响客户端应用程序。

但是,您可以在浏览器窗口内冻结与页面内容的交互....

只需放置一个具有固定位置的全屏 div,就可以防止用户与主页面(在其后面)上的任何内容进行交互。然后,在其上方显示您的模态对话框。当用户清除模态时,隐藏它和全屏 div,从而使主页面再次可交互。

let modal = document.querySelector(".modal");
let pageCover = document.querySelector(".pageCover");
let main = document.querySelector("main");        

document.getElementById("open").addEventListener("click", function(){
  modal.classList.remove("hidden");
  pageCover.classList.remove("hidden"); 
  main.addEventListener("focus", preventFocus);
});


document.getElementById("close").addEventListener("click", function(){
  modal.classList.add("hidden");
  pageCover.classList.add("hidden"); 
  main.removeEventListener("focus", preventFocus);
});

function preventFocus (evt){
  evt.preventDefault();
}
.hidden { display:none; }

.pageCover { 
  position:fixed; 
  z-index:10; 
  background-color:rgba(0,0,0,.25); 
  width:100vw; 
  height:100vh; 
  top:0; 
  left:0;
}

.modal { 
  position:absolute; 
  z-index:100; 
  background-color:aliceblue;
  border:2px double grey;
  width:200px; 
  height:200px; 
  top:30%; 
  left:30%; 
  text-align:center;
}

.modalTitle {
  margin:0;
  background-color:#00a;
  color:#ff0;
  padding:5px;
  font-weight:bold;
  font-size:1.1em;
}

#close { 
  background-color:#800080;
  color:#ff0;
  border:1px solid #e0e0e0;
  padding:5px 10px;
}

#close:hover { background-color:#c000c0; }
<main>
  <h1>This is some page content</h1>
  <p>And more content</p>
  <button id="open">Click Me To Show Modal</button>
  <div>More content</div>
</main>

<div class="hidden pageCover"></div>

<div class="hidden modal">
  <div class="modalTitle">Modal Title Here</div>
  <p>
    Now, you can only interact with the contents of this "modal".
  </p>
  <button id="close" >Close</button>
</div>


1
@Taplar 是的,但是如果您在元素上放置tabindex,那么您就无法这样做。 - Scott Marcus
1
@Axel 没有需要留下的东西。我的回答的第一段已经回答了你的问题。第二部分只是一个模拟的建议。如果不适合你,可以忽略它。 - Scott Marcus
2
@Axel 哦,既然是这样,问题在哪里呢?只需使用 confirm 即可。由于某种原因,我以为你想要冻结浏览器并创建一个自定义外观的对话框。当然,这样做是不正规的。 - Keith
2
@Axel 我不确定你想让大家说什么,你到底想要什么?是要一个漂亮的对话框来确认重置表单吗?如果是的话,这是可能的,SO上的人们会很乐意帮助。如果你期望W3C读完你的帖子并说:“好的,看起来我们该实现Axel在SO上一直谈论的浏览器冻结功能了”,我不确定它会发生。 - Keith
2
@Axel,我的回答的第一段说了什么?你已经得到了几个人的答案。你似乎只是不喜欢它,但继续争论并不能改变它。 - Scott Marcus
显示剩余13条评论

4
你可以将弹出窗口以外的所有内容放在一个div中,然后通过CSS打开/关闭指针事件。该div中的所有内容将不再可交互,看起来就像是冻结了浏览器窗口。
.freeze { pointer-events: none; }

注意:当为一个元素添加pointer-events属性时,所有子元素也会受到影响,所以将其放置在标签上也会锁定弹出窗口。

一旦弹出窗口被关闭,我们就可以从

中删除freeze类,然后一切都会重新开始工作。

const content = document.getElementById('content')
const overlay = document.querySelector('.overlay')
const a = document.querySelector('.open-overlay')
const close = document.querySelector('.overlay .close')

// Open a popup and freeze the browser content
a.addEventListener('click', e => {
  e.preventDefault()
  content.classList.add('freeze')
  overlay.classList.remove('hidden')
})

// Close the popup window and unfreeze the browser content
close.addEventListener('click', e => {
  e.preventDefault()
  content.classList.remove('freeze')
  overlay.classList.add('hidden')
})
.freeze { pointer-events: none; }

.hidden { display: none; }

.overlay {
  position: fixed;
  z-index: 100;
  padding: 20px;
  background: white;
  border: solid 1px #ccc;
  width: 300px;
  top: 20px;
  left: 0;
  right: 0;
  margin: auto;
}
<div class="overlay hidden">
  <h1>Overlay</h1>
  <a href="" class="close">Close</a>
</div>

<div id="content">
  <a href="" class="open-overlay">Open Overlay</a>

  <p>Hello World</p>
  <p>Hello World</p>
  <p>Hello World</p>
  <p>Hello World</p>
  <p>Hello World</p>
  <form>
    <input type="text">
  </form>
</div>


但是我仍然可以通过Tab键在后台导航。我甚至仍然可以在“输入”中键入另一个值。就像我在问题中写的那样:这不是关于HTML/CSS,而是关于冻结方面的问题... - Axel
2
就像其他人所说的那样,您无法修改浏览器的物理行为,只能模拟它。 - Get Off My Lawn
那么这就是正确的答案了!规格说明中有什么可以支持这个结论吗? - Axel
你要找什么样的规格?指针事件 - Get Off My Lawn
那方面没有规格。 - Get Off My Lawn
如果您想防止编辑输入,您可以在对话框打开时将其禁用或设置为只读。 - Herohtar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接