<h1>Spoiler</h1>
<details>
<summary>Citizen Kane Spoilers Ahead!</summary>
Rosebud is the sled
</details>
<h1>Inline Spoiler</h1>
<h2>Background</ h2>
<p tabindex="0" class="spoiler-1">Rosebud is the sled 😀</p>
<h2>Brightness</h2>
<p tabindex="0" class="spoiler-2">Rosebud is the sled 😀</p>
<h2>Blur</h2>
<p tabindex="0" class="spoiler-3">Rosebud is the sled 😀</p>
<h2>Toggle</h2>
<input type="checkbox" id="spoiler-4" />
<label class="spoiler-4" tabindex="0" for="spoiler-4"> Rosebud is the sled 😀</label>
<h2>Custom Element</h2>
<wc-spoiler>Rosebud is the sled 😀</wc-spoiler>
<wc-spoiler label="Citizen Kane Spoilers">Rosebud is the sled 😀</wc-spoiler>
.spoiler-1 {
background: black;
display: inline-block;
}
.spoiler-1:active,
.spoiler-1:focus {
background: transparent;
}
.spoiler-2 {
filter: brightness(0%);
background: white;
display: inline-block;
}
.spoiler-2:active,
.spoiler-2:focus {
filter: none;
background: transparent;
}
.spoiler-3 {
filter: blur(10px);
display: inline-block;
}
.spoiler-3:active,
.spoiler-3:focus {
filter: none;
}
.spoiler-4 {
filter: brightness(0%);
background: white;
}
input[type="checkbox"]#spoiler-4{
clip: rect(0 0 0 0);
height: 0px;
width: 0px;
padding: 0px;
margin: 0px;
}
.spoiler-4:has(input:checked),
input:checked + .spoiler-4{
filter: none;
background: white;
}
.spoiler-4:focus {
outline: 1px solid green;
}
class WcSpoiler extends HTMLElement {
#isShown = false;
bind(element) {
this.render = this.render.bind(element);
this.cacheDom = this.cacheDom.bind(element);
this.attachEvents = this.attachEvents.bind(element);
}
connectedCallback() {
this.render();
this.cacheDom();
this.attachEvents();
}
render() {
this.attachShadow({ mode: "open" });
const label = this.getAttribute("label")
? this.getAttribute("label")
: "Toggle to read spoiler";
this.shadowRoot.innerHTML = `
<style>
:host { position: relative; display: inline-block; }
#label-button {
width: 100%;
height: 100%;
position: absolute;
top: 0px;
left: 0px;
background-color: black;
border: none;
}
#label-button[aria-pressed="true"] {
background-color: transparent;
}
#button-text, #live-area {
clip: rect(0 0 0 0);
height: 0px;
width: 0px;
padding: 0px;
margin: 0px;
overflow: hidden;
}
</style>
<slot aria-hidden="true"></slot>
<div id="live-area" aria-live="polite"></div>
<button id="label-button" aria-role="switch"><div id="button-text">${label}</div></button>
`;
}
cacheDom() {
this.dom = {
labelButton: this.shadowRoot.querySelector("#label-button"),
buttonText: this.shadowRoot.querySelector("#button-text"),
liveArea: this.shadowRoot.querySelector("#live-area"),
slot: this.shadowRoot.querySelector("slot")
};
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue !== newValue) {
this[name] = newValue;
}
}
attachEvents() {
this.dom.labelButton.addEventListener("click", () => {
this.#isShown = !this.#isShown;
this.dom.slot.ariaHidden = this.#isShown ? "false" : "true";
this.dom.labelButton.ariaPressed = this.#isShown ? "true" : "false";
this.dom.liveArea.textContent = this.#isShown ? this.textContent : "";
});
}
set label(val){
if(this.dom?.buttonText && val.trim()){
this.dom.buttonText = val;
}
}
}
customElements.define("wc-spoiler", WcSpoiler);
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.