Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Delete mojs-shapes from DOM on timeline Complete? #62

Closed
lifeinchords opened this issue Oct 9, 2016 · 9 comments
Closed

Delete mojs-shapes from DOM on timeline Complete? #62

lifeinchords opened this issue Oct 9, 2016 · 9 comments

Comments

@lifeinchords
Copy link

Hi Oleg,

Im excited to play with this library. Thanks for your work on it, and for the recent push on documentation, curve editor + player. These are AWESOME tools that I'm sure will push adoption.

I'm exploring this lib to draw shape fx for a web app. I'm already using isShowEnd to hide shapes generated, though considering so many will be generated in the life of a session (hours, even days in an open tab), it would be great to auto delete DOM elements created when each timeline completes.

A couple of questions:

  • Have I missed an option for this?
  • If not, is there a clean way to get a reference to that DOM element created, inside the onPlaybackComplete callback so I can manually delete?

If I do something like:

onPlaybackComplete: function(){
        $(".mojs-shape").remove();
      }

I think I'm going to run into trouble, because animations overlap in time and I want each to complete before the deleting the shape[s] it makes.

  • Even better, would you consider a deleteshapeOnComplete option or similar?

Much appreciated!
~s

giphy

@legomushroom
Copy link
Member

Hi Steven! Thanks a lot! Your demo looks awesome! Sorry for delayed reply.

Adding/removing elements to/from DOM is bad for performance since every DOM change will trigger the chain of browser reorganizations and cleanups. So every time you will remove an element from the DOM it will trigger reflow/repaint events which are bad for your animation. Also deleted elements have to be garbage collected just like any other objects are, which will block the main thread for a while and will make your animation jabber.

So mojs modules were designed to make as little DOM changes as possible. The best way to deal with the issue is to create modules once at page initialization and then reuse exact same modules on subsequent calls. For that, you have the tune public method that tunes options of module for ones that work for your current call:

const burst = new mojs.Burst({
  // options
});

$(document.body).on('click', '.some-card', function (e) {
   // reuse same burst by just tuning `x`/`y` of the burst to the current element's coordinates
   burst
       .tune({ x: e.pageX, y: e.pageY })
       // replay it after tune
       .replay()
});

codepen example

Of course, since user can click on your cards frequently, with small delay, and you animations can overlap, you want to create a pool of bursts and increment pointer to current element in that pool on each click event, probably 5 would be enough to cover all your cards: codepen example

I hope it sets more light over the problem. Please let me know if you have any further difficulties or if you have any thoughts.


P.S.

If you do want to remove the element from DOM after its done with it's job - just use .el property of the module. Callbacks are bound to module that they are called on and each module has .el property that is the root of the markup. So just do:

onPlaybackComplete () {
  $(this.el).remove();
}

Of course, I won't suggest to proceed with this approach since it will cause DOM thrashing and will bother GC a lot.

Cheers

@lifeinchords
Copy link
Author

hey Oleg,

Thanks for the good words, and for the detailed reply!

Ah I see, I had a feeling there was a good reason for keeping things in the DOM. That makes a lot of sense, re:performance. For our prototyping, the $(this.el).remove() is perfect for us, and we'll definitely continue exploring reuse as our project matures. Thanks for the Codepen + code examples, really helpful in understanding.

Talk soon,
~s

@legomushroom
Copy link
Member

Happy to help, please let me know if something you'll stumble upon something! 🎉

@darrentorpey
Copy link

This was great to know, @legomushroom. Have you considered writing out an FAQ for something like this (or build one as we go along)? I'm sure more than a few of us will be creating thousands of animations for art projects and can always benefit from advice like this on how to manage performance.

@legomushroom
Copy link
Member

@darrentorpey thanks for the feedback. Yeah, we definitely need the faq section and better docs. I'm working on the stable release at the moment and plan to do the better job with the docs and even to create a full-length mojs video course. The main issue is that I have limited capacity and the docs always seemed to be low priority (since the API can change) but it will change with the stable release. If you want/can help with the docs, shoot me a message I will highly appreciate your help.

@almgwary
Copy link

Pure javascript implementation
onComplete: function () {this.el.parentNode.removeChild(this.el);} }

@rajchandra3
Copy link

React js Component for context

import mojs from '@mojs/core';

class Heart extends mojs.CustomShape {}

Heart.prototype.getShape = () => {
    return '<path d="M73.6170213,0 C64.4680851,0 56.5957447,5.53191489 51.7021277,13.8297872 C50.8510638,15.3191489 48.9361702,15.3191489 48.0851064,13.8297872 C43.4042553,5.53191489 35.3191489,0 26.1702128,0 C11.9148936,0 0,14.0425532 0,31.2765957 C0,48.0851064 14.893617,77.8723404 47.6595745,99.3617021 C49.1489362,100.212766 50.8510638,100.212766 52.1276596,99.3617021 C83.8297872,78.5106383 99.787234,48.2978723 99.787234,31.2765957 C100,14.0425532 88.0851064,0 73.6170213,0 L73.6170213,0 Z"></path>';
};

mojs.addShape('heart', Heart);

const CIRCLE_RADIUS = 32;
const RADIUS = 48;
const circle = new mojs.Shape({
    left: 0,
    top: 0,
    stroke: '#E5214A',
    strokeWidth: { [2 * CIRCLE_RADIUS]: 0 },
    fill: 'none',
    scale: { 0: 1 },
    radius: CIRCLE_RADIUS,
    duration: 400,
    easing: 'cubic.out',
});

const burst = new mojs.Burst({
    left: 0,
    top: 0,
    radius: { 4: RADIUS },
    angle: 45,
    count: 14,
    timeline: { delay: 300 },
    children: {
        radius: 2.5,
        fill: '#E5214A',
        scale: { 1: 0, easing: 'quad.in' },
        pathScale: [0.8, null],
        degreeShift: [13, null],
        duration: [500, 700],
        easing: 'quint.out',
    },
});

const heart = new mojs.Shape({
    left: 0,
    top: 2,
    shape: 'heart',
    fill: '#E5214A',
    scale: { 0: 2 },
    easing: 'elastic.out',
    duration: 1600,
    delay: 300,
    radius: 11,
});

const showHeartWhereClicked = (e) => {
    const x = e.clientX;
    const y = e.clientY;
    const cords = { x, y };
    burst.tune(cords).replay();

    circle.tune(cords).replay();

    heart.tune(cords).replay();
};


export {
    showHeartWhereClicked
};

In the current code, I can show an animation but the heart remains on the screen even though the animation has ended, is there a fix for this?

Note: when the animation is fired again the heart is deleted and new animation is played, this is not an issue, the issue is again the next heart will remain on the screen.

@xavierfoucrier
Copy link
Member

Hi @rajchandra3,

As explained by @almgwary, you need to remove it by yourself as it is not implemented yet in the library.

Use:

const heart = new mojs.Shape({
  left: 0,
  top: 2,
  shape: 'heart',
  fill: '#E5214A',
  scale: { 0: 2 },
  easing: 'elastic.out',
  duration: 1600,
  delay: 300,
  radius: 11,
  onComplete: function() {
    this.el.parentNode.removeChild(this.el);
  }
});

😉

@rajchandra3
Copy link

Thanks, @xavierfoucrier for the quick response and making this clear.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants