Skip to content

[Bug] Immortal Gradients #766

@leiavoia

Description

@leiavoia

Describe the bug
Gradients are silently inserted as child elements of the scene graph and never removed. Additionally, gradients do not support gradient.remove() syntax.

Over long periods of creation and destruction of elements, unused gradients begin to clog the scene graph and noticeably impact performance.

I understand that gradients are likely shared objects and that tracking their orphaned status may be tricky. However, i have spent months tracking down this issue and finally narrowed it down to this most unlikely thing. I am building an artificial life environment that runs for long periods of time while creating and destroying thousands of SVG objects. This was dragging down frame rates and inflating the size of exported scene graphs to .svg files.

The issue is consistent across all three rendering contexts.

If there is an easier way to handle this that i have overlooked, please advise.

To Reproduce
Steps to reproduce the behavior:

  1. Create any gradient.
  2. Create any visual element ( line, path, etc ).
  3. Attach gradient as either stroke or fill.
  4. Remove the visual element. ( element.remove() )
  5. Observe that gradient remains.

Expected behavior
Gradients with no referencing elements should be deleted.

Environment (please select one):

  • Code executes in browser (e.g: using script tag to load library)
  • Packaged software (e.g: ES6 imports, react, angular, vue.js)
  • Running headless (usually Node.js)

Desktop:

  • Device: Laptop
  • OS: Linux
  • Browser: Firefox 136

.html Working Example

<!DOCTYPE html>
<html>
  <head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/two.js/0.8.15/two.min.js"></script>
  </head>
  <body id="body">
    <div class="shape-container" style="height: 100px; width: 100px">
      <div id="two_canvas"></div>
    </div>
    <script>
      // setup
      let two = new Two({ fitted: true, type: 'SVGRenderer' });
      let elem = document.getElementById('two_canvas');
      two.appendTo(elem);

      // baseline
      console.log('before starting:', two.scene.children.length);

      // create a line
      let line = two.makeLine(0, 0, 100, 100);
      console.log('after line created:', two.scene.children.length);

      // create a gradient (child count unexpectedly increases)
      let stops = [
        new Two.Stop(0, '#000'),
        new Two.Stop(0.5, '#AAA'),
        new Two.Stop(1, '#FFF'),
      ];
      let grad = two.makeLinearGradient(0, 0, 1, 1, ...stops);
      console.log('after creating gradient:', two.scene.children.length);

      // attach gradient to line stroke
      line.stroke = grad;
      console.log(
        'after attaching gradient to line stroke:',
        two.scene.children.length
      );

      // remove line (gradient is not removed automatically))
      line.remove();
      console.log('after removing line:', two.scene.children.length);

      // removing gradient possible, but only manually
      two.remove(grad);
      console.log(
        'after removing gradient manually',
        two.scene.children.length
      );

      // gradient is a child element, but grad.remove() is not a function

      console.log(two.scene.children);
    </script>
  </body>
</html>

References:
The project I am building with two.js has a demo here: https://leiavoia.net/vectorcosm/

Thank you all for your efforts on this library. It's been a lot of fun to work with on my project!

Metadata

Metadata

Assignees

Labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions