Reputation: 45
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
Reputation: 3444
In short:
<dialog>
over custom implementations.opacity
of an element also applies to all its children.<dialog>
; namely its top layer mechanism.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.
<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">⨯</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.
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">⨯</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">⨯</button>
<p>A paragraph inside our custom dialog.</p>
</div>
</div>
Note: This does not trap focus inside the dialog.
Upvotes: 0
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