Web Animations API causing detached elements in Chrome DevTools Memory panel
Issue
When I use the Web Animations API, I notice several detached objects in the Memory panel of Chrome DevTools. I’m not sure what’s causing this.
Summary
- Objects retained by detached DOM nodes:
Detached <div class="line" style="display: none;">
Detached CSSStyleDeclaration
Detached DOMTokenList
Steps to Reproduce
- Open a page with the test case.
- Open DevTools → Memory, click Collect Garbage, then Take Heap Snapshot.
- First, run the test without animation (only with a timer):
- Click Start in the test case,
- Wait 1 second until the red element disappears,
- Go to DevTools → Memory, click Garbage Collect, then Take Heap Snapshot.
- Now we have two snapshots. Select Comparison mode and filter by
"detached"
. - Expected: No detached elements.
- Delete snapshots and reboot page, repeat 1 and 2 items, run the test with animation:
- In item 3 change the test mode to Animate and repeat the steps for create and comparise snapshots.
- On two different computers, I consistently see detached elements appearing in the snapshots.
Code example:
const moveRight = [
[{ transform: 'translateX(0)' }, { transform: 'translateX(100%)' }],
{
id: 'moveRight',
duration: 1000,
easing: 'linear',
fill: 'forwards',
},
];
const box = document.querySelector(".box")
let line = null
let animation = null
const cleanHandler = () => {
line.style.display = "none"
if (animation) {
animation.removeEventListener('finish', cleanHandler);
animation.cancel();
animation = null;
}
let localLine = line;
line = null
setTimeout(() => {
if (localLine) {
localLine.remove()
localLine = null;
}
})
};
document.querySelector("button.start").addEventListener("click", () => {
if (line) return
line = document.createElement("div")
line.classList.add("line")
const animated = document.getElementById("animate").checked
box.appendChild(line)
if (animated) {
animation = line.animate(...moveRight)
animation.addEventListener('finish', cleanHandler);
} else {
setTimeout(() => cleanHandler(), 1000)
}
})
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
.box {
display: flex;
overflow: hidden;
}
.line {
background-color: red;
flex: 1;
height: 100px;
}
<button class="start">Start</button>
<input type="radio" name="radio" value="0" id="timeout" checked />
<label for="timeout">Timeout</label>
<input type="radio" name="radio" value="1" id="animate"/>
<label for="animate">Animate</label>
<div class="box">
</div>