Jump to content
Search Community

iongion

Members
  • Posts

    19
  • Joined

  • Last visited

iongion's Achievements

  1. I found it actually with your idea and after better observing what is happening. Actually, what happens is that all the 3 events happen in order and with certain progress. Positive scrub past a nested timeline - onStart - progress 1 - onUpdate - progress 1 - onComplete - progress 1 Negative scrub before a nested timeline - onStart - progress 0 - onUpdate - progress 0 - onComplete - progress 0 So, I am just memo-ing a hasPlayedThrough flag that I can use in either onUpdate or onComplete - if the onStart did not go directly to 1 or 0 depending on the seek/scrub type. This is just lovely, gsap is so well done!
  2. When one scrubs/seeks - inner timelines either get seeked to 0 or to 1, if the playhead of the timeline is before and or after their duration. Only for those timelines for which the playhead lands in between their start / end moments, we can consider as ever playing at all. What I am saying is that with current onStart / onComplete I can only detect if they either arrive at start or end, without actually knowing if they played at all (enter). This is what I would like to detect, if they actually have played at all.
  3. https://codepen.io/iongion/pen/PoaMJKN if you drag the root slider all the way to the end, the blue bordered log box will show this - which is correct, by design 1: ["box1","onStart",1] 1: ["box1","onUpdate",1] 1: ["box1","onComplete",1] 1: ["box2","onStart",1] 1: ["box2","onUpdate",1] 1: ["box2","onComplete",1] 1: ["box3","onStart",1] 1: ["box3","onUpdate",1] 1: ["box3","onComplete",1] if you drag it the other way - also correct, by design 0: ["box3","onUpdate",0] 0: ["box2","onUpdate",0] 0: ["box1","onUpdate",0] What I am trying to detect is if for example box1 has played between 0 and 1 progress and not directly going to 1.
  4. When using nested timelines - if one scrubs the parent timeline positively, all children tweens up until that moment get the `onComplete` and `onUpdate` with `progress = 1` triggered - if one scrubs the parent timeline negatively, all children tweens before that moment get the `onStart` and `onUpdate` with `progress = 0` triggered I am interested in detecting if the tweens have actually played and create some extra events some extra events like `onEnterTween` `onExitTween` What's the best kind of strategy that I should use ? One would be to check if the main timeline time is in between tween's start and end time - but the progress is not 0 or 1 - but this eliminates the bounds, I do want to keep them.
  5. Thank you, revisited and fixed. Coming back to original issue, there is no way than to compute manually the progress of the other sliders, once for example box2 slider is adjusted ?
  6. I am trying to find a way to keep timelines in sync, even when child timelines are modified. Now, when one drags the slider of the root timeline, all the children respect it. As soon as a child timeline is manipulated, they all go out of sync. Is there a simple way to keep them in sync without recomputing manually the positions ?
  7. I've recently had to work on a task where I had to overlay multiple story animations across a video. I synced their timelines in an acceptable way. But the performance hit is that I need to add all elements from start, why: - I start with a root timeline that I connect to a slider and normal play/resume/stop/rewind control buttons - Each stories happen between phases `phase1s,phas1e .... phase2s,phase2e ...... phase3s,phase3e` - these are exactly gsap timelines with offsets from root This above is great, the root timeline provides the slider with enough information to control, seek, etc. But there is a performance hit, I need to layout all phases scenes from start, which for 120 phases is not fun, as they all have their own complex DOM + SVG animations. So I've tried to find a way to still be able to have the prettiness of a single global timeline that controls the child timelines with frame precision, but avoid creating all in the DOM from start. The problem is that gsap needs DOM elements to exit, but what if instead of using selectors, I use JS Proxy. So I thought of building this helper called withActors const stageIdenitifer = 1; const { rectange, circle, logo, banner, person } = withActors(stageIdentifier, { rectangle: '.Rectangle', circle: '.Circle', logo: '.Logo', person: '.Person', }); // Start creating animation scenario using st - the scenario timeline const st = gsap.timeline(); // all these are not dom elements, they are proxies for elements that don't yet exist st.to(rectangle, { top: 200, left: 100 }); st.to(circle, { top: 250, left: 100 }); st.to(logo, { top: 300, left: 100 }); st.to(person, { top: 400, left: 100 }); And this is how an hasty implementation looks like const isPlayStageActor = Symbol("isPlayStageActor"); const playStageActorTargetElement = Symbol("playStageActorTargetElement"); function withPlayStageActors(id: any, keysMap: any) { const playStageSelector = `.Stage[data-stage-id="${id}"]`; const internalKeysMap = { ...keysMap, playStage: playStageSelector }; const selectorsMap = Object.keys(internalKeysMap).reduce((acc, actorKey) => { const elementSelector = `${playStageSelector} ${internalKeysMap[actorKey]}`; let element: any; acc[actorKey] = new Proxy({} as any, { get(target, propKey, receiver) { if (propKey === isPlayStageActor) { return true; } if (propKey === playStageActorTargetElement) { return target.element; } if (!element) { const nodeSelector = actorKey === "playStage" ? playStageSelector : elementSelector; element = document.querySelector(nodeSelector); target.element = element; } if (element) { if (typeof element[propKey] === "function") { if (!target[propKey]) { target[propKey] = (...args: any[]) => { return (element as any)[propKey].apply(element, args); }; } return target[propKey]; } return Reflect.get(element, propKey); } }, set(target, propKey, value) { if (!element) { const nodeSelector = actorKey === "playStage" ? playStageSelector : elementSelector; element = document.querySelector(nodeSelector); target.element = element; } if (element) { return Reflect.set(element, propKey, value); } return true; }, }); return acc; }, {} as any); return selectorsMap; } Now all is good, except that gsap CSSPlugin uses global window.getComputedStyle and that one wants to work only with Elements - not proxies, so to solve it as there is no way to inject it from outside into gsap CSSPlugin only, I have to patch it globally with something like this if (!(window as any).isGetComputedStylePatched) { const previousGetComputedStyle = window.getComputedStyle; window.getComputedStyle = function (node: any) { if (node[isPlayStageActor]) { return previousGetComputedStyle(node[playStageActorTargetElement]); } return previousGetComputedStyle(node); }; (window as any).isGetComputedStylePatched = true; } It works insanely well - just wanted to share with the next soul having to go through this. Now I can create 1000 nested timelines without having any impact on the DOM (especially as I am combining this with React where I always have to wait for useEffect/useLayoutEffect) What do you guys think ?
  8. 5 years after - having similar issues. So far, the problems I seen emerging are when creating custom objects in fabricjs with custom properties. Here is my attempt at a plugin. All properties should be updated in fabric using `set(key, value)` - some of them work directly through `fobj.prop = value` - but if one subclasses `fabric.Object` and decorates it with a custom property, gsap won't interpolate it. I actually don't know why. Here is my attempt at solving this - using a Proxy - intercept calls and then use the fabric way of setting / getting properties. But it is ugly as then props must be placed in the plugin registered key like this anim.to(pi, { fabric: { progress: 75, top: 20, }, duration: 3, ease: "none", onComplete: () => { pi.setCoords(); }, }); And here is the ugly plugin - hopefully another human in 5 years from now will find it useful. /* eslint-disable */ let gsap, _coreInitted, _win, _fabricjs, _windowExists = () => typeof window !== "undefined", _getGSAP = () => gsap || (_windowExists() && (gsap = window.gsap) && gsap.registerPlugin && gsap), _warn = (message) => console.warn(message), _initCore = (core) => { gsap = core || _getGSAP(); if (_windowExists()) { _win = window; } if (gsap) { _coreInitted = 1; } }; export const FabricJSPlugin = { version: "3.11.3", name: "fabric", init(target, value, tween, index, targets) { if (!_coreInitted) { _initCore(); if (!gsap) { _warn("Please gsap.registerPlugin(FabricJSPlugin)"); } } this.target = new Proxy(target, { get(obj, key) { return obj.get(key); }, set(obj, key, val) { obj.set(key, val); return obj; }, }); let p, end; for (p in value) { end = value[p]; if (this.target[p] != null) { this.add(this.target, p, "get", end); } } }, register: _initCore, }; FabricJSPlugin.registerFabricJS = (fabricjs) => { _fabricjs = fabricjs; }; _getGSAP() && gsap.registerPlugin(FabricJSPlugin); export { FabricJSPlugin as default }; Thank you guys, for such awesome tools and libraries.
  9. Given two nested timelines added a parent timeline that is controlled by a scrub bar as follows ------------------^----------------- -----[t1s...t1e]-----[t2s...t2e]---- When I am seeking past a certain nested timeline positively I see progress never reaches 1, it stops around 0.9. When I am seeking negatively, they always reach 0 Is there a way to ensure 1 is reached ? I rely on this to be able to hide some graphical elements completely from the DOM. As I notice, when first loading the timeline, if I let it progress naturally with a pseudo play control, that does progress update of the main timeline, all happens as expected, elements are properly hidden / unhidden from the DOM. But if I seek directly to a certain spot, they don't hide properly, some remain visible as if the onComplete is never reached.
  10. Oh man, that is it, it was as simple as that, I've replaced all the seek / pause with timeline.invalidate() after path changes! Using gsap and nothing else since flash days!
  11. Thanks guys, will try to make a minimal example. The example from @akapowl did help a bit. I actually don't understand why the svg path & guide are not followed after alteration/changing by the user using the control points. The const guide is what changes its points over time by user performing actions on a bezier curve (the svg path behind the guide variable) The location of the circle is constrained by the timeline time and bezier curve alteration. What works is to intercept whenever the bezier curve changes and re-animate (re-create) the gsap timeline. I thought it is not needed, I thought there is something to tell the tween to re-parse the svg path once it is altered. In your demos it appears to do so by default, I don't understand why it doesn't do it in my approach. I am using a senseless setPath dependency just to recreate the tween, I suspect it might be heavy with many tweens. useLayoutEffect(() => { console.debug("re-animate"); const durationFrames = frameEnd - frameStart; const durationSeconds = durationFrames / framerate; const duration = durationSeconds * 1000; const guide = `#ProgressIndicatorMotionPath-${id}`; const targetProps = { duration, transition: "linear", motionPath: { path: guide, align: guide, autoRotate: false, alignOrigin: [0.5, 0.5], // immediateRender: true, // curviness: 1.5, }, }; const startTime = (frameStart / framerate) * 1000; const ctx = gsap.context(() => { const tween = gsapTimeline.to(ref.current, targetProps, startTime); tweenRef.current = tween; }); return () => ctx.revert(); }, [path, id, frameStart, frameEnd, framerate, gsapTimeline]); percentageRef.current = currentFrame / framesCount; const onMotionGuideChange = useCallback((e: any) => { setPath(e); if (tweenRef.current) { const current = tweenRef.current.progress(); tweenRef.current?.restart(); tweenRef.current?.progress(current); tweenRef.current?.pause(); } }, []);
  12. They are close, but I am altering the svg path definition points programatically. Only some of them due to the constraints. What I have found working is re-initializing the entire timeline when path definition changes. But the experience is not that smooth.
  13. I am using an existing CodePen to exemplify the problem. If one uses inspect element and manipulates directly the motion guide / path definition, then the motionPath plugin still uses the previous definition. Is there a way to make it follow dynamic transform of the motion path ? I am planning to animate the motion path itself.
  14. Lovely, it works like a charm! Many thanks!
×
×
  • Create New...