Jump to content
Search Community

GreenSock last won the day on April 21

GreenSock had the most liked content!

GreenSock

Administrators
  • Posts

    23,135
  • Joined

  • Last visited

  • Days Won

    817

Everything posted by GreenSock

  1. I sent you a private message with details. ✅
  2. Ah, maybe there's the disconnect - we cannot approach development that way with our tools. We work very hard to build trust with users so that things "just work" and GSAP is consistent. So if we tell users that they can use motionPath in a Flip.from()...but it'll only work in 5% of the cases and in all others it just totally breaks, that's no good. We have to think through all the edge cases and ensure there is consistency across the board as much as possible. Imagine if you used Photoshop but it only worked on images that have mostly red pixels and no human faces? Or you bought a car that only functioned on 4% of the roads in your city. 🙂
  3. Unfortunately it doesn't at all 🙂 I think you might be missing a fundamental logic impossibility here. Well most paths have x and y. In my example, I said a squiggly vertical line, thus there is some "x" movement in a squiggle. But in this case you're saying that the Flip.from() would totally ignore the motionPath? That's one of the key problems - the start and end points can't just magically be the same based on any motionPath you feed in. What if the start/end points are at a 45 degree angle from each other, 100px apart...but the motionPath you feed in has the start/end points at a 174degree angle from each other and 800px apart? How could this possibly be expected to work? Let me use an extreme case to make the point - imagine your <path> is a circle. So the start and end anchor points are in exactly the same place, and you use that as the motionPath on a Flip.from(). What would you expect to happen? Are you starting to see the fundamental logic problem in this suggestion? Or am I missing something?
  4. I still feel like we may not be connecting here... Can you please try answering this?: The magic of Flip is that even after you totally change the DOM and maybe reparent an element, it applies x/y/width/height offsets to make it look like it's in the original position. So let's say you Flip.getState(el) and then reparent it so that it ends up being exactly 100px to the right of where it was originally, and Flip figures out that it must animate x from -100 to 0. Great. But now let's say you add a motionPath:[...] to that Flip.from(), and the path data is from a 500px completely vertical line. How are you expecting this to work? You said that motionPath should take precedence. So let's say el.x starts at 0. Then you reparent your element such that it's now 100px to the right and Flip.from() would normally apply x: -100 to map it to that original position...but there's a motionPath that's a 500px totally vertical line. If that takes precedence, that means the element just suddenly jumps up 500px directly above or something? Can you see how it will no longer look like a proper Flip animation because it's not being mapped to the appropriate x/y position since the motionPath took over?
  5. That effect should be applied with an attribute, not a CSS stylesheet. That's more compatible and DrawSVG knows to look for that too. https://codepen.io/GreenSock/pen/dyLqXvX?editors=0010 Is that better?
  6. Maybe think of it like this... Let's say a you create a motionPath that (in an over-simplified way) animates from 100 -> 200 (a motion path is basically a bunch of coordinates, so let's say these are the coordinates). I'm only focusing on the "x" values for simplicity. Now let's also say you create a Flip tween that dynamically calculates the distance between two targets such that x goes from -152 to 0. It sounds like you're saying "just make motionPath work with Flip seamlessly" but how exactly would you expect that to happen in this scenario? You've got a motionPath that'd make x go from 100 to 200 over the course of the tween, and a Flip animation that makes x go from -152 to 0 over the course of the SAME tween. How can both happen simultaneously and seamlessly? Perhaps I'm misunderstanding you, but this seems like it's logically impossible to accommodate. You appear to be thinking of a motionPath more like an ease (style of movement; a matter of pacing/timing which could be applied to any interpolation) rather than an actual set of coordinates. For example, imagine a 500px vertical squiggly line that serves as the "motionPath"...and you want to somehow apply that to a Flip animation where a box goes horizontally 100px directly to the right. Help?
  7. Yeah, a fundamental problem is that your target element is inside a position: fixed element, and the Flip.fit() does calculations based on the normal document flow. Flip.fit() would indeed work correctly BUT you're wiring that up to scroll position. The element you're fitting it to is affected by scrolling, but the target element isn't because it's position: fixed. So, for example, let's say that the element you're fitting it into is 500px directly below it - Flip.fit() would basically say "move 500px down and you'll be on top of that element." but you're wiring it to scroll which effectively MOVES the fit position. When you scroll 500px, the spot it's supposed to fit to is now 500px higher. If the target is position: fixed and it animates 500px down while scrolling, it would of course end up where that original fitting element was BEFORE scrolling. It probably is possible to figure out all the calculations, but this well beyond the scope of help we can provide in these free forums. If you need more help, you could post in the Jobs & Freelance forum or contact us for paid consulting services. I wish there was a super simple solution to offer.
  8. Yeah, that's the nature of dynamically calculating velocity because you've always gotta choose how much "recording" to do time-wise and then compare the values. But I'm not sure why you're not just pre-calculating the velocity based on the linear tween's distance and duration: const velocity = ScrollTrigger.maxScroll(document.querySelector("#scroller")) / 10; // change / duration This demo shows a general idea of how the values are calculated, although in ScrollTrigger the measurements are always at least 50ms apart: https://codepen.io/GreenSock/pen/GRLXZXZ?editors=0010 In short, I think there's probably a much cleaner way to do what you're attempting without using getVelocity().
  9. Just to be clear, when you use the tools in Webflow, you'd just use the normal JavaScript files (downloaded in the ZIP) - the token is only necessary for using the private NPM registry. Your license covers an unlimited number of your work products while it’s active, like Rodrigo said. The only caveat is that it covers your unedited work, meaning that if your clients/customers want to make any changes, they should get their own license. Otherwise, it’d make it easy for a huge company to circumvent the license by hiring a freelancer to start a project and then take it in-house and piggy-back on that single-developer license without getting their own license for their numerous developers. See what I mean? And please don't share the bonus plugins in cloneables which would make it easy for other developers to piggy-back on your membership. It's totally fine to use them in your custom projects for clients of course. 👍
  10. It's very difficult to troubleshoot without a minimal demo, but my guess is that maybe your target element doesn't exist(?) If you still need some help, please make sure you post a minimal demo that clearly illustrates the issue (like a Stackblitz). Here's a starter template you can fork: https://stackblitz.com/edit/react-cxv92j
  11. Hi @denglertarea23 I noticed two problems: You don't have a "Business" Club GSAP membership, but you're trying to install the members-only package via the private repository. You don't have access to that. You must sign up first. https://gsap.com/pricing You must not have configured the .npmrc file properly, because according to your error code, it's trying to find that package in the main npmjs.org registry instead of the private npm.greensock.com registry. Make sure you follow the installation instructions carefully. https://gsap.com/install
  12. In case it wasn't already clear, that "jumping" that occurs when you don't have enough elements to "fill" the area horizontally is not a bug or anything - an element cannot exist in two places at once. Like you can't have half of the element showing on the left side of the screen while the other half of the element shows on the right side of the screen. It's in one place or the other.
  13. That's not valid - you should pass a ScrollTrigger configuration object to the timeline, not a ScrollTrigger instance itself: // BAD let st = ScrollTrigger.create({...}); gsap.timeline({ scrollTrigger: st }); // GOOD let st = {...}; gsap.timeline({ scrollTrigger: st }); Alternatively, you could do the inverse, where you create the animation and then pass it into the ScrollTrigger.create(): let tl = gsap.timeline(); tl.to(...) .to(...); ScrollTrigger.create({ animation: tl, ... }); Does that clear things up?
  14. No, that isn't really possible to do a nested default value like that. But perhaps in the next release, I could add a ScrollToPlugin.config({ autoKill: true }) method. I don't think anyone has ever requested something like that before. It wouldn't be terribly expensive kb-wise. I assume you'd both vote for this addition?
  15. The ScrollTrigger has a direction property that's 1 if the last scroll was forward, and -1 if it was backward. Sorta like: let tl = gsap.timeline({ scrollTrigger: { scrub: true, ... } }); tl.to(...); tl.add(() => { console.log("direction", tl.scrollTrigger.direction); }); Notice I'm using add() for the callback just because it's a little simpler than call() which is only useful if you're passing parameters (uncommon). Is that what you're looking for? If you're not using a ScrollTrigger at all, there's also a helper function for tracking the direction of an animation: https://gsap.com/docs/v3/HelperFunctions/helpers/trackDirection
  16. Unfortunately we can't really troubleshoot a live site (way too many factors and impossible to tweak/experiment), but it does look like you're using lazy-loading images which can be problematic. Basically, you need to call ScrollTrigger.refresh() when the layout is done shifting around so that the calculations are correct. You could explicitly set the width/height on your images so that they don't cause layout shifts, or you could use a helper function like the one in this thread: If you still need some help, please make sure you create a minimal demo (like CodePen/Stackblitz) that clearly illustrates the issue and then we'd be happy to look at that and answer any GSAP-related questions.
  17. Just check the docs for the scroller property. scroller: "#your-scroller"
  18. Yeah, that looks like a browser rendering thing, but you could try setting this on any transform-related animation: force3D: false Does that help?
  19. Ah, that's because inside the helper function there was a "resize" event handler that was re-initiating things. I just edited the helper function to put it inside a gsap.context() that uses a cleanup function for the "resize" event handler to remove that: https://stackblitz.com/edit/stackblitz-starters-jbsvf4?file=app%2Fhelper.js function horizontalLoop(items, config) { let timeline; items = gsap.utils.toArray(items); config = config || {}; gsap.context(() => { // use a context so that if this is called from within another context or a gsap.matchMedia(), we can perform proper cleanup like the "resize" event handler on the window let onChange = config.onChange, lastIndex = 0, tl = gsap.timeline({repeat: config.repeat, onUpdate: onChange && function() { let i = tl.closestIndex(); if (lastIndex !== i) { lastIndex = i; onChange(items[i], i); } }, paused: config.paused, defaults: {ease: "none"}, onReverseComplete: () => tl.totalTime(tl.rawTime() + tl.duration() * 100)}), length = items.length, startX = items[0].offsetLeft, times = [], widths = [], spaceBefore = [], xPercents = [], curIndex = 0, indexIsDirty = false, center = config.center, pixelsPerSecond = (config.speed || 1) * 100, snap = config.snap === false ? v => v : gsap.utils.snap(config.snap || 1), // some browsers shift by a pixel to accommodate flex layouts, so for example if width is 20% the first element's width might be 242px, and the next 243px, alternating back and forth. So we snap to 5 percentage points to make things look more natural timeOffset = 0, container = center === true ? items[0].parentNode : gsap.utils.toArray(center)[0] || items[0].parentNode, totalWidth, getTotalWidth = () => items[length-1].offsetLeft + xPercents[length-1] / 100 * widths[length-1] - startX + spaceBefore[0] + items[length-1].offsetWidth * gsap.getProperty(items[length-1], "scaleX") + (parseFloat(config.paddingRight) || 0), populateWidths = () => { let b1 = container.getBoundingClientRect(), b2; items.forEach((el, i) => { widths[i] = parseFloat(gsap.getProperty(el, "width", "px")); xPercents[i] = snap(parseFloat(gsap.getProperty(el, "x", "px")) / widths[i] * 100 + gsap.getProperty(el, "xPercent")); b2 = el.getBoundingClientRect(); spaceBefore[i] = b2.left - (i ? b1.right : b1.left); b1 = b2; }); gsap.set(items, { // convert "x" to "xPercent" to make things responsive, and populate the widths/xPercents Arrays to make lookups faster. xPercent: i => xPercents[i] }); totalWidth = getTotalWidth(); }, timeWrap, populateOffsets = () => { timeOffset = center ? tl.duration() * (container.offsetWidth / 2) / totalWidth : 0; center && times.forEach((t, i) => { times[i] = timeWrap(tl.labels["label" + i] + tl.duration() * widths[i] / 2 / totalWidth - timeOffset); }); }, getClosest = (values, value, wrap) => { let i = values.length, closest = 1e10, index = 0, d; while (i--) { d = Math.abs(values[i] - value); if (d > wrap / 2) { d = wrap - d; } if (d < closest) { closest = d; index = i; } } return index; }, populateTimeline = () => { let i, item, curX, distanceToStart, distanceToLoop; tl.clear(); for (i = 0; i < length; i++) { item = items[i]; curX = xPercents[i] / 100 * widths[i]; distanceToStart = item.offsetLeft + curX - startX + spaceBefore[0]; distanceToLoop = distanceToStart + widths[i] * gsap.getProperty(item, "scaleX"); tl.to(item, {xPercent: snap((curX - distanceToLoop) / widths[i] * 100), duration: distanceToLoop / pixelsPerSecond}, 0) .fromTo(item, {xPercent: snap((curX - distanceToLoop + totalWidth) / widths[i] * 100)}, {xPercent: xPercents[i], duration: (curX - distanceToLoop + totalWidth - curX) / pixelsPerSecond, immediateRender: false}, distanceToLoop / pixelsPerSecond) .add("label" + i, distanceToStart / pixelsPerSecond); times[i] = distanceToStart / pixelsPerSecond; } timeWrap = gsap.utils.wrap(0, tl.duration()); }, refresh = (deep) => { let progress = tl.progress(); tl.progress(0, true); populateWidths(); deep && populateTimeline(); populateOffsets(); deep && tl.draggable ? tl.time(times[curIndex], true) : tl.progress(progress, true); }, onResize = () => refresh(true), proxy; gsap.set(items, {x: 0}); populateWidths(); populateTimeline(); populateOffsets(); window.addEventListener("resize", onResize); function toIndex(index, vars) { vars = vars || {}; (Math.abs(index - curIndex) > length / 2) && (index += index > curIndex ? -length : length); // always go in the shortest direction let newIndex = gsap.utils.wrap(0, length, index), time = times[newIndex]; if (time > tl.time() !== index > curIndex && index !== curIndex) { // if we're wrapping the timeline's playhead, make the proper adjustments time += tl.duration() * (index > curIndex ? 1 : -1); } if (time < 0 || time > tl.duration()) { vars.modifiers = {time: timeWrap}; } curIndex = newIndex; vars.overwrite = true; gsap.killTweensOf(proxy); return vars.duration === 0 ? tl.time(timeWrap(time)) : tl.tweenTo(time, vars); } tl.toIndex = (index, vars) => toIndex(index, vars); tl.closestIndex = setCurrent => { let index = getClosest(times, tl.time(), tl.duration()); if (setCurrent) { curIndex = index; indexIsDirty = false; } return index; }; tl.current = () => indexIsDirty ? tl.closestIndex(true) : curIndex; tl.next = vars => toIndex(tl.current()+1, vars); tl.previous = vars => toIndex(tl.current()-1, vars); tl.times = times; tl.progress(1, true).progress(0, true); // pre-render for performance if (config.reversed) { tl.vars.onReverseComplete(); tl.reverse(); } if (config.draggable && typeof(Draggable) === "function") { proxy = document.createElement("div") let wrap = gsap.utils.wrap(0, 1), ratio, startProgress, draggable, dragSnap, lastSnap, initChangeX, wasPlaying, align = () => tl.progress(wrap(startProgress + (draggable.startX - draggable.x) * ratio)), syncIndex = () => tl.closestIndex(true); typeof(InertiaPlugin) === "undefined" && console.warn("InertiaPlugin required for momentum-based scrolling and snapping. https://greensock.com/club"); draggable = Draggable.create(proxy, { trigger: items[0].parentNode, type: "x", onPressInit() { let x = this.x; gsap.killTweensOf(tl); wasPlaying = !tl.paused(); tl.pause(); startProgress = tl.progress(); refresh(); ratio = 1 / totalWidth; initChangeX = (startProgress / -ratio) - x; gsap.set(proxy, {x: startProgress / -ratio}); }, onDrag: align, onThrowUpdate: align, overshootTolerance: 0, inertia: true, snap(value) { //note: if the user presses and releases in the middle of a throw, due to the sudden correction of proxy.x in the onPressInit(), the velocity could be very large, throwing off the snap. So sense that condition and adjust for it. We also need to set overshootTolerance to 0 to prevent the inertia from causing it to shoot past and come back if (Math.abs(startProgress / -ratio - this.x) < 10) { return lastSnap + initChangeX } let time = -(value * ratio) * tl.duration(), wrappedTime = timeWrap(time), snapTime = times[getClosest(times, wrappedTime, tl.duration())], dif = snapTime - wrappedTime; Math.abs(dif) > tl.duration() / 2 && (dif += dif < 0 ? tl.duration() : -tl.duration()); lastSnap = (time + dif) / tl.duration() / -ratio; return lastSnap; }, onRelease() { syncIndex(); draggable.isThrowing && (indexIsDirty = true); }, onThrowComplete: () => { syncIndex(); wasPlaying && tl.play(); } })[0]; tl.draggable = draggable; } tl.closestIndex(true); lastIndex = curIndex; onChange && onChange(items[curIndex], curIndex); timeline = tl; return () => window.removeEventListener("resize", onResize); // cleanup }); return timeline; } Is that better?
  20. That's because your code is set up assuming each box will be the width of the viewport. But they're narrower. You just need to do the calculations properly. I assume this is what you're looking for: https://codepen.io/GreenSock/pen/NWmMgjZ
  21. Personally, I would take an entirely different approach to this: https://codepen.io/GreenSock/pen/wvZjeGN?editors=1010 That gives you much more flexibility and the transitions are more intuitive.
  22. I don't have time to dig into that right now, but wouldn't it be?: end: .5 + cardSpaceAround[index] / 2
  23. Are you trying to do something like this?: https://codepen.io/GreenSock/pen/KKpLdWW There are a lot of demos here: https://codepen.io/collection/AEbkkJ
  24. There's a demo on the docs page I linked you to already. You can try the plugin for FREE on CodePen, Stackblitz, etc. See https://gsap.com/trial I'm not sure what you mean by "...where we can make it more smooth??" 🤷‍♂️
  25. That looks like exactly what ScrambleTextPlugin does: https://gsap.com/docs/v3/Plugins/ScrambleTextPlugin It's a membership benefit of Club GSAP, just so you know. 👍
×
×
  • Create New...