Thor The dog
Thor The dog

Reputation: 45

selective dimming of html

Im creating a submenu. The idea is that when the submenu is open, the brightness of the HTML reduces. Im reducing the opacity to achieve this. The problem with my code is that it is reducing the opacity of the entire HTML, including the submenu. I tried to include provisions for this in my javascript but its not working. I am attaching my code below. How do I solve this and what are other methods to reduce the brightness of the webpage?

const USERINFO = document.getElementById("USERINFO");
const subMenu = document.getElementById("login-subMenu");
const html = document.documentElement;

USERINFO.addEventListener("click", function() {
  subMenu.style.right = "0";
  html.classList.add("dimmed");
});

html.addEventListener("click", function(event) {
  if (event.target !== USERINFO && !subMenu.contains(event.target)) {
    subMenu.style.right = "-400px";
    html.classList.remove("dimmed");
  }
});

const closeButton = document.getElementById("closeButton");

closeButton.addEventListener("click", function() {
  subMenu.style.right = "-400px";
  html.classList.remove("dimmed");
});
#login-subMenu {
  position: fixed;
  top: 0;
  right: -400px;
  /* Hide the menu offscreen to start */
  width: 400px;
  height: 100%;
  background-color: #fff;
  z-index: 9999;
  transition: all 0.3s ease-out;
}

.dimmed {
  opacity: 0.5;
  pointer-events: none;
  transition: opacity 0.3s ease-out;
}
<div class="info">
  <a id="USERINFO">My Account</a>
</div>
<div id="login-subMenu">
  <button id="closeButton">Close X</button>
</div>

Upvotes: 1

Views: 91

Answers (2)

Oskar Grosser
Oskar Grosser

Reputation: 3444

In short:

  • Prefer <dialog> over custom implementations.
  • Your issue:
    • Reducing opacity of an element also applies to all its children.
    • Take inspiration from <dialog>; namely its top layer mechanism.

Regarding your code

opacity literally changes the opacity, and does not dim. The background of elements with opacity below 1 will be visible:

const main = document.querySelector("main");
const button = document.querySelector("button");

button.addEventListener("click", () => main.classList.toggle("dimmed"));
.dimmed {opacity: .25}

main {background-color: white}

/*Make main cover viewport*/
html {height: 100%}
body {
  margin: 0;
  min-height: 100%;
  display: grid;
  grid-template-areas: 1fr / 1fr;
}

/*Add below content*/
html {
  background-image: url(https://picsum.photos/1280/720);
  background-size: cover;
}
<main>
  <p>Some paragraph that will be "dimmed", but actually translucent.</p>
  <button>Toggle dimming</button>
</main>

So you may want to choose filter (e.g. with brightness()) for dimming instead:

const main = document.querySelector("main");
const button = document.querySelector("button");

button.addEventListener("click", () => main.classList.toggle("dimmed"));
.dimmed {filter: brightness(.25)}

main {background-color: white}

/*Make main cover viewport*/
html {height: 100%}
body {
  margin: 0;
  min-height: 100%;
  display: grid;
  grid-template-areas: 1fr / 1fr;
}

/*Add below content*/
html {
  background-image: url(https://picsum.photos/1280/720);
  background-size: cover;
}
<main>
  <p>Some paragraph that will be dimmed.</p>
  <button>Toggle dimming</button>
</main>

But both opacity and filter of an element also apply to all its children. This includes children with position: fixed or position: absolute, and even <dialog> elements.

Prefer <dialog>

For dialogs you should prefer the <dialog> element. Opening it via HTMLDialogElement.showModal() automatically dims the background and traps focus inside:

const dialog = document.querySelector("dialog");

const buttonOpen = document.getElementById("open");
const buttonOpenAsModal = document.getElementById("open-as-modal");

buttonOpen.addEventListener("click", () => dialog.show());
buttonOpenAsModal.addEventListener("click", () => dialog.showModal());
<p>Some paragraph outside a dialog.</p>
<button id="open">Open dialog</button>
<button id="open-as-modal">Open dialog as modal</button>

<dialog>
  <form method=dialog>
    <button aria-label="Close">&Cross;</button>
  </form>
  <p>A paragraph in a dialog.</p>
</dialog>

A modal dialog is displayed in a so-called top layer. That layer is effectively the last child of <body> with the highest z-index, ensuring it to be above all other elements.

A modal dialog also creates a ::backdrop pseudo-element that dims the background, i.e. all other elements.

Custom dialog

For a custom dialog implementation we can take inspiration from <dialog>, its ::backdrop and the top layer.

Placing the dialogs at the end of <body> with the highest z-index keep them top-most:

const dialog = document.querySelector("[role=dialog]");

const buttonOpen = document.getElementById("open");
const buttonClose = document.getElementById("close");

buttonOpen.addEventListener("click", () => dialog.classList.add("open"));
buttonClose.addEventListener("click", () => dialog.classList.remove("open"));
[role=dialog] {
  z-index: 9999;
  position: fixed;
}
[role=dialog]:not(.open) {display: none}

/*Presentational styling*/
[role=dialog] {
  top: 50%;
  left: 50%;
  border: 2px solid black;
  padding: .8rem;
  max-width: 90vw;
  max-height: 90vh;
  transform: translate(-50%);
  background-color: white;
}
<p>Some paragraph outside the custom dialog.</p>
<button id="open">Open dialog</button>

<div role=dialog>
  <button id="close" aria-label="Close">&Cross;</button>
  <p>A paragraph inside our custom dialog.</p>
</div>

Using a mock top layer allows for blocking clicks and dimming of the background:

const dialog = document.querySelector("[role=dialog]");

const buttonOpen = document.getElementById("open");
const buttonClose = document.getElementById("close");

buttonOpen.addEventListener("click", () => dialog.classList.add("open"));
buttonClose.addEventListener("click", () => dialog.classList.remove("open"));
#top-layer:has([role=dialog].open)::before {
  content: "";
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background-color: rgba(0, 0, 0, .25);
}

/*Same as before*/
[role=dialog] {
  z-index: 9999;
  position: fixed;
  top: 50%;
  left: 50%;
  border: 2px solid black;
  padding: .8rem;
  max-width: 90vw;
  max-height: 90vh;
  transform: translate(-50%);
  background-color: white;
}
[role=dialog]:not(.open) {display: none}
<p>Some paragraph outside the custom dialog.</p>
<button id="open">Open dialog</button>

<div id="top-layer">
  <div role=dialog>
    <button id="close" aria-label="Close">&Cross;</button>
    <p>A paragraph inside our custom dialog.</p>
  </div>
</div>

Note: This does not trap focus inside the dialog.

Upvotes: 0

Kostas Minaidis
Kostas Minaidis

Reputation: 5467

Using the documentElement will apply the opacity to all the elements included within the HTML document. You cannot exclude the submenu, as it's a child of HTML.

You just have to use another "container" for the dimmed part of the HTML.

const USERINFO = document.getElementById("USERINFO");
const subMenu = document.getElementById("login-subMenu");
const container = document.querySelector("#container");

USERINFO.addEventListener("click", function() {
  subMenu.style.right = "0";
  container.classList.add("dimmed");
});

container.addEventListener("click", function(event) {
  if (event.target !== USERINFO && !subMenu.contains(event.target)) {
    subMenu.style.right = "-400px";
    container.classList.remove("dimmed");
  }
});

const closeButton = document.getElementById("closeButton");

closeButton.addEventListener("click", function() {
  subMenu.style.right = "-400px";
  container.classList.remove("dimmed");
});
#login-subMenu {
  position: fixed;
  top: 0;
  right: -400px;
  /* Hide the menu offscreen to start */
  width: 400px;
  height: 100%;
  background-color: #fff;
  z-index: 9999;
  transition: all 0.3s ease-out;
}

.dimmed {
  opacity: 0.5;
  pointer-events: none;
  transition: opacity 0.3s ease-out;
}
<div id="container">
  <div class="info">
    <a id="USERINFO">My Account</a>
  </div>
</div>
<div id="login-subMenu">
  <button id="closeButton">Close X</button>
</div>

Upvotes: 1

Related Questions