catdad / canvas-confetti

🎉 performant confetti animation in the browser
https://catdad.github.io/canvas-confetti/
ISC License
10.55k stars 369 forks source link

DOMException when navigating away from page with animating confetti #184

Closed geoffwa closed 1 year ago

geoffwa commented 2 years ago

Thanks for making canvas-confetti it's awesome!

We (effectively) have the following code:

const confettiFrequency = 40;
const animationEnd = Date.now() + 1000;
const confettiOptions = {/* ... */};

let interval = setInterval(() => {
 requestAnimationFrame(() => confetti(confettiOptions));
  if (Date.now() > animationEnd) clearInterval(interval);
}, confettiFrequency);

This works great, but if the interval canvas element gets blatted by navigating away from the page a DOMException is raised:

Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node

Is there a good way to cleanup an animating confetti? Calling confetti.reset() doesn't help as by the time we know we should stop animating the canvas element has already been disconnected from the DOM tree.

catdad commented 2 years ago

Hey there. I can't say that I am entirely sure what "blatted" means. Also, navigating away from a page stops all code execution on that page. By page, do you mean something other than a web page?

The best and only way to clean up is by calling confetti.reset(). I am not sure what you mean by the rest of your description. It seems like the code you provided is not really related to the description of the problem. Are there any steps I should be taking to reproduce the issue you are reporting?

catdad commented 1 year ago

I am closing this due to lack of information and activity on this issue. It can be reopened if further information is added.

kyrylo commented 11 months ago

It's easy to replicate when you use StimulusJS and Turbo.

Navigate to a page that has this controller. The animation will start, then while the animation is running, navigate to another page.

import { Controller } from "@hotwired/stimulus";

import confetti from "canvas-confetti";

export default class extends Controller {
  connect() {
    confetti({
      particleCount: 100,
      spread: 70,
      origin: { y: 0.6 },
    });
  }

  disconnect() {
    confetti.reset();
  }
}

By the time disconnect() is invoked, the DOM element where the confetti was created no longer exists.

I worked around this by hooking into the turbo:before-render event.

  connect() {
    document.addEventListener("turbo:before-render", this.#resetConfetti);
  }

  disconnect() {
    document.removeEventListener("turbo:before-render", this.#resetConfetti);
  }

  #resetConfetti() {
    confetti.reset();
  }