<head>
<style>
button {
margin: 0.5rem;
}
.opacity-0 {
opacity: 0;
}
.opacity-100 {
opacity: 1;
}
.container {
font-family: monospace;
border: 1px solid black;
padding: 0.25rem 0.75rem;
margin: 0.5rem;
transition: opacity linear 1s;
}
</style>
</head>
<body>
<div id="wrapper">
<button>Toggle</button>
<div id="c1" class="container">getComputedStyle: Never, Disconnect from DOM: No</div>
<div id="c2" class="container" data-compute-before>
getComputedStyle: Before, Disconnect from DOM: No
</div>
<div id="c3" class="container" data-compute-after>
getComputedStyle: After, Disconnect from DOM: No
</div>
<div id="c4" class="container" data-disconnect>
getComputedStyle: Never, Disconnect from DOM: Yes
</div>
<div id="c5" class="container" data-compute-before data-disconnect>
getComputedStyle: Before, Disconnect from DOM: Yes (this is the broken one)
</div>
<div id="c6" class="container" data-compute-after data-disconnect>
getComputedStyle: After, Disconnect from DOM: Yes
</div>
<div id="c7" class="container">
getComputedStyle: Never, Disconnect from DOM: No; read from subtree
<span data-subtree></span>
</div>
<div id="c8" class="container" data-compute-before>
getComputedStyle: Before, Disconnect from DOM: No; read from subtree
<span data-subtree></span>
</div>
<div id="c9" class="container" data-compute-after>
getComputedStyle: After, Disconnect from DOM: No; read from subtree
<span data-subtree></span>
</div>
<div id="c10" class="container" data-disconnect>
getComputedStyle: Never, Disconnect from DOM: Yes; read from subtree
<span data-subtree></span>
</div>
<div id="c11" class="container" data-compute-before data-disconnect>
getComputedStyle: Before, Disconnect from DOM: Yes; read from subtree (this is the broken one)
<span data-subtree></span>
</div>
<div id="c12" class="container" data-compute-after data-disconnect>
getComputedStyle: After, Disconnect from DOM: Yes; read from subtree
<span data-subtree></span>
</div>
</div>
<script>
let wrapper = document.querySelector("#wrapper");
let containers = Array.from(document.querySelectorAll(".container"));
let show = true;
let compute = false;
let disconnect = false;
function play(el) {
let from = show ? "opacity-0" : "opacity-100";
let to = show ? "opacity-100" : "opacity-0";
// 1. Add element to DOM (if needed)
wrapper.append(el);
// 2.a Read a computed property value _before_ adding the class
// This one is the broken case
if ("computeBefore" in el.dataset) {
let subtree = el.querySelector("[data-subtree]");
window.getComputedStyle(subtree ? subtree : el).getPropertyValue("z-index");
}
// 3. Add a class representing the current state of the element
// e.g. if we're showing the element then it starts out hidden (opacity-0)
// otherwise it starts out visible (opacity-100)
el.classList.add(from);
// Normally an element has 0 animations at this point
// However, if we read a computed property value _before_ adding the class
// the element will have 1 animation
console.log("Element %s has %d animation(s)", el.id, el.getAnimations().length);
// 2.b Read a computed property value _after_ adding the class
// This one is totally fine
if ("computeAfter" in el.dataset) {
window.getComputedStyle(el).getPropertyValue("z-index");
}
requestAnimationFrame(() => {
// 4. Change the class list to trigger the transition
el.classList.remove(from);
el.classList.add(to);
// Normally an element has 1 animation at this point
// However, if we read a computed property value _before_ adding the class
// the element will have 0 animations
console.log("Element %s has %d animation(s)", el.id, el.getAnimations().length);
el.addEventListener(
"transitionend",
() => {
if (!("disconnect" in el.dataset)) return;
// Reset the element's state
el.classList.remove(to);
// Remove element from DOM (if needed)
show || el.remove();
},
{ once: true },
);
});
}
// Log when animations are done or cancelled for debugging purposes
containers.forEach((el) => {
el.addEventListener("transitionrun", () => console.log(el, "transitionrun"));
el.addEventListener("transitionstart", () => console.log(el, "transitionstart"));
el.addEventListener("transitionend", () => console.log(el, "transitionend"));
el.addEventListener("transitioncancel", () => console.log(el, "transitioncancel"));
});
// Start animation(s)
document.querySelector("button").addEventListener("click", () => {
show = !show;
containers.forEach(play);
});
</script>
</body>
Output
You can jump to the latest bin by adding /latest
to your URL
Keyboard Shortcuts
Shortcut | Action |
---|---|
ctrl + [num] | Toggle nth panel |
ctrl + 0 | Close focused panel |
ctrl + enter | Re-render output. If console visible: run JS in console |
Ctrl + l | Clear the console |
ctrl + / | Toggle comment on selected lines |
ctrl + ] | Indents selected lines |
ctrl + [ | Unindents selected lines |
tab | Code complete & Emmet expand |
ctrl + shift + L | Beautify code in active panel |
ctrl + s | Save & lock current Bin from further changes |
ctrl + shift + s | Open the share options |
ctrl + y | Archive Bin |
Complete list of JS Bin shortcuts |
JS Bin URLs
URL | Action |
---|---|
/ | Show the full rendered output. This content will update in real time as it's updated from the /edit url. |
/edit | Edit the current bin |
/watch | Follow a Code Casting session |
/embed | Create an embeddable version of the bin |
/latest | Load the very latest bin (/latest goes in place of the revision) |
/[username]/last | View the last edited bin for this user |
/[username]/last/edit | Edit the last edited bin for this user |
/[username]/last/watch | Follow the Code Casting session for the latest bin for this user |
/quiet | Remove analytics and edit button from rendered output |
.js | Load only the JavaScript for a bin |
.css | Load only the CSS for a bin |
Except for username prefixed urls, the url may start with http://jsbin.com/abc and the url fragments can be added to the url to view it differently. |