8

Is there any way of freezing the browser window intentionally like alert, confirm and prompt (in short: "acp") does?

I want to show a modal dialog with custom css instead of the native popups for "acp" but also want to have the ability to freeze the browser until I have users feedback just like "acp".

But man, why? This is bad practice (uh I have to downvote)!
So when it is bad practice - then why does "acp" actually offer this synchronous behavior? Because in some particular scenarios its just exactly the right tool for an appropriate UX. Those native modals do look so ugly and are also very limited at the same time.

Here's just one quick and dirty example where it would be totally fine to freeze the browser until the user gives feedback. Lets say we have a form with an input[type="reset"]-element. So before we really let the form reset we ask the user something like: "Are you sure you want to reset (data will be lost)?".

If I would be fine with the native modal look (which I'm not) I could do:

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>

So "everybody" should agree (edit: or at least somebody could) this isn't bad practice, right?
But how can we achieve the same with a custom styled modal/dialog/popup?
I'm -of course- not asking for the HTML/CSS part but for the capability of freezing the browser window via JavaScript!

To be honest, I actually expect some downvotes for this but maybe there is this one Killer JavaScript Ninja, who have this one special hack to make it possible...

Axel
  • 3,331
  • 11
  • 35
  • 58
  • 3
    *So "everybody" should agree this isn't bad practice, right?* No, no I don't. Just because an old native method froze the browser, doesn't mean you're logic should. Preventing users from interacting with the page, does not mean you have to freeze the browser. The entire concept of overlays exists for this very purpose. – Taplar May 01 '19 at 21:32
  • 2
    You can't, but you can make it look like you have. If you look at Bootstrap modals for example. https://getbootstrap.com/docs/4.0/components/modal/ In your example, instead of using a reset button, just show your modal, and then call the reset on the form manually. – Keith May 01 '19 at 21:33
  • @Keith I know how to achieve this - don't worry (I'm quite experinced)... Its about the JavaScript-freeze not about the HTML/CSS. – Axel May 01 '19 at 21:35
  • @Taplar *Just because an old native method froze the browser, doesn't mean you're logic should.* - never said so! – Axel May 01 '19 at 21:38
  • 1
    *"So when it is bad practice - then why does "acp" actually offer this synchronous behavior? Because in some particular scenarios its just exactly the right tool for an appropriate UX."* I have never met a UXer who has championed freezing the browser and removing user control as a positive experience. – Taplar May 01 '19 at 21:39
  • There is no way to freeze the browser, short of an infinite loop that locks the UI, but that will cause you to lose all functionality so clearly it is of no use. The only option you have is to literally use one of those old dialogs, which *is* bad practice (just because they exist doesn't mean they're good design). You can emulate a modal dialog with overlays and/or disable interactive elements, but what you want is not possible. Even the old dialogs aren't truly modal anymore -- just try switching to a new tab or entering a URL in Chrome while the confirm dialog from your example is open. – Herohtar May 01 '19 at 21:41
  • @Taplar nice: caught in the act!!! Actually its my very personal opinon. I just had the feeling to have to back up :) – Axel May 01 '19 at 21:43
  • 2
    Not sure what answer you want,. It's not possible. The main reason been if you freeze the browser, what is it you think will render the UI, you have just blocked the browsers event loop, it won't be able to handle any input from the user. Synchronous methods do feel nice, but if you use `async / await`, you could do `const sure = await showConfirm();`, all it needs is your confirm dialog to be a promise. – Keith May 01 '19 at 21:45
  • Possible duplicate of [how to make popup windows always on top?](https://stackoverflow.com/questions/5576596/how-to-make-popup-windows-always-on-top) – Get Off My Lawn May 01 '19 at 21:46
  • @Keith right you are but meanwhile the event handler runs to its end and therefor also the `event.preventDefault`... – Axel May 01 '19 at 21:48
  • And actually as an alternative to the typical confirm approach to dangerous actions, recently I have seen UXers implementing options regarding holding clicks. Taking your example of the confirm for the reset button, rather than a confirm, you would require the user to hold down the button for a duration before the action happens. By the very nature of them consciously holding down the button, they are giving confirmation. Edit: So in that aspect, even overlays are being avoided in some new cases. – Taplar May 01 '19 at 21:49
  • By the way, the jQuery actually makes your code longer. `document.querySelector('form').addEventListener('reset', e => confirm('Are you sure you want to reset (data will be lost)?') || e.preventDefault())` ;) – Herohtar May 01 '19 at 21:51
  • @Herohtar BTW: no it doesn't `$('form').on('reset', e => confirm('Are you sure you want to reset (data will be lost)?') || e.preventDefault())` – Axel May 01 '19 at 21:54
  • @Axel That's not what you wrote in your question, but it's still longer because you have to have the additional ` – Herohtar May 01 '19 at 21:57
  • 2
    Yes, that's why I said you call it manually. That's how I do it anyway, doesn't seem to make code that much more complicated, especially if your someone who is quote `quite experinced`.. – Keith May 01 '19 at 21:57
  • @Herohtar no its not but what made your version shorter was not the jQuery part but the ES-style instead. And to add the `script` to the snippet - this was just exactly one mouse click and no typing at all. Also BTW: this is so off-topic. So just for you – Axel May 01 '19 at 22:05
  • I only pointed it out because you went to the effort of typing a comment in the code that you were just using jQuery for "shortness" (implying length of code in characters or lines, not amount of effort), but it actually resulted in having more code overall. – Herohtar May 02 '19 at 00:53
  • @Herohtar so what? Sorry - its really a waste of time, isn't it? At least for me it is! Please see the updated concerned source code. I really hope you feel better now :) – Axel May 02 '19 at 01:34
  • So it was weird that you felt it necessary to "justify" your use of jQuery with a comment next to it. Not really a waste for me; when I'm on SO it's because I'm browsing it for fun in my spare time anyway. I'm confused; the code has not changed from the original. Edit: Ah, you hadn't actually edited your post at the time. – Herohtar May 02 '19 at 01:45

2 Answers2

5

You can't freeze the browser application like the native dialogs do. Those are not built with JavaScript, they are built with native code and can affect the browser application in any way. JavaScript is prohibited from affecting the client application in such ways.

But, you can freeze interactions with your page content within the browser window....

Just place a window sized div above the page with fixed positioning. That will prevent the user from being able to interact with anything on the main page (behind it). Then, display your modal dialog on top of that. When the user clears the modal, hide it and the window sized div, thus making the main page interactive again.

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>
Scott Marcus
  • 64,069
  • 6
  • 49
  • 71
  • Dear Scott, thank you for taking time but this not what I'm after. *That will prevent the user from being able to interact with anything on the main page (behind it)* - Thats not the case! I can for example still play around on the "main page" with the tab key of my keyboard. Yes there is a way to loose focus and stuff but again I'm just asking for the freeze... Thanks again anyway! – Axel May 01 '19 at 21:59
  • *I began by indicating that what you are asking for is not possible and that's really the long and short of it.* So lets stay with it because this is exactly the only scope of my question... – Axel May 01 '19 at 22:14
  • @ScottMarcus you can replicate the issue Axel is talking about by putting `tabindex="0"` on your non model elements to simulate focusable content on the page. You can, indeed, tab to them with the overlay up. – Taplar May 01 '19 at 22:16
  • 1
    @Taplar Yeah, but if you ***don't*** put `tabindex` on the element, then you can't. – Scott Marcus May 01 '19 at 22:19
  • 1
    @Axel There's nothing to stay with. The first paragraph of my answer is answering your question. The second part was just a suggestion for a simulation. Ignore it if it's not suitable to you. – Scott Marcus May 01 '19 at 22:20
  • @ScottMarcus only if you do not have focusable content. If they page had input fields, which are inheriently focusable, the issue exists. – Taplar May 01 '19 at 22:21
  • 1
    But normally there are elements that are focusable by default (in a real world scenario). eg `input`, `button`, etc – Axel May 01 '19 at 22:21
  • @Taplar The main page has a button, which is focusable and I can't tab to it when the modal is open. – Scott Marcus May 01 '19 at 22:21
  • @ScottMarcus Sure you can. Run it, click the button, click on the modal, and then Shift+Tab – Taplar May 01 '19 at 22:22
  • @Taplar I see. I've added a little more code that I think corrects that. – Scott Marcus May 01 '19 at 22:28
  • @Axel Freezing the browser is also "hacky", but these are just issues you have to sort out once. If you go back to that Bootstrap dialog example, you will see it's possible. Open one of the dialogs, and tab works as you would expect. – Keith May 01 '19 at 22:35
  • So calling `confirm('are you sure you want to do this or that')` is hacky, in your opinion? And its also freezing the window... – Axel May 01 '19 at 22:38
  • 2
    @Axel Oh, in that case what's the problem, just use confirm. For some reason I thought you wanted to freeze the browser and create a custom looking dialog. Doing this of course would be hacky.. – Keith May 01 '19 at 22:42
  • Dear @Keith sorry I don't get it! Maybe you can explain your opinion to me? So if I use an ugly native `confirm`-modal which freezes the window (this is hacky? is not?) or I use a fancy custom-modal which freezes the window (this is hacky? is not?) – Axel May 01 '19 at 22:48
  • 2
    @Axel I'm not sure what you want everyone to say, what is it you want here exactly?, do you want a fancy dialog for confirm of a reset of a form. If you do, it's possible and people on SO will be happy to help. If you expect W3C to read your post, and say, OK, seems about time we implement that browser freeze feature Axel on SO keeps going on about, I'm not sure it's going to happen. – Keith May 01 '19 at 22:56
  • @Keith the title says it all: "How to freeze browser window intentionally (like alert, confirm and prompt does)?" - thats it, thats all. If its not possible then the answer is: "Its not possible.". I didn't ask for a workaround (actually also stated this fact in the end part of my question). **"OK, seems about time we implement that browser freeze feature Axel on SO keeps going on about, I'm not sure it's going to happen."** - what a pitty :) ! – Axel May 01 '19 at 23:03
  • 2
    @Axel What does the first paragraph of my answer say? You’ve gotten your answer by several people. You seem to just not like it, but continuing to argue isn’t going to change it. – Scott Marcus May 01 '19 at 23:33
  • Everything fine Scott. I actually expected this result - just wanted to back it up... The only reason why I didn't mark your answer as "accepted" is because I think its not straight forward enough. If it would be something like "No its not possible [period]" I would mark it as such. But IMHO the long part you added distracts from this essence. The question is not at all about a workaround... – Axel May 01 '19 at 23:48
4

You can place everything within a div except for your popup. You can then turn on/off pointer events via css.

Everything within the div will no longer be interactable. This will give the appearance of freezing the browser window.

.freeze { pointer-events: none; }

Note: When adding pointer-events to an element this affects all child elements as well, so placing it on the body would also lock the popup.

Once the popup has been closed, we can remove the freeze class from the div and everything will start working again.

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>
Get Off My Lawn
  • 34,175
  • 38
  • 176
  • 338
  • But I can still navigate at the background by the tab-key. I can even still type in another value in the `input`. Like I also wrote in my question: its not about the HTML/CSS but the freezing aspect... – Axel May 01 '19 at 22:27
  • 2
    Like others have stated, you cannot modify physical browser behavior, you can only simulate it. – Get Off My Lawn May 01 '19 at 22:29
  • So then this actually is the correct answer! Is there anything to back this up from the specs??? – Axel May 01 '19 at 22:31
  • What kind of specs are you looking for? [Pointer Events](https://developer.mozilla.org/en-US/docs/Web/CSS/pointer-events)? – Get Off My Lawn May 01 '19 at 22:33
  • There is no spec on that. – Get Off My Lawn May 01 '19 at 22:35
  • If you want to prevent editing inputs you can simply disable them or set them to readonly while the dialog is open. – Herohtar May 01 '19 at 22:56