Jump to content
Search Community

Search the Community

Showing results for 'whipped' in content posted in GSAP.

  • Search By Tags

    Type tags separated by commas.
  • Search By Author

Content Type


Forums

  • GreenSock Forums
    • GSAP
    • Banner Animation
    • Jobs & Freelance
  • Flash / ActionScript Archive
    • GSAP (Flash)
    • Loading (Flash)
    • TransformManager (Flash)

Product Groups

  • Club GreenSock
  • TransformManager
  • Supercharge

Categories

There are no results to display.


Find results in...

Find results that contain...


Date Created

  • Start

    End


Last Updated

  • Start

    End


Filter by number of...

Joined

  • Start

    End


Group


Personal Website


Twitter


CodePen


Company Website


Location


Interests

Found 118 results

  1. I couldn't help myself - I whipped together a helper function that'll draw a pie chart piece with a bunch of animatable properties like startAngle, endAngle, radius, cx, cy: https://codepen.io/GreenSock/pen/eYMRGJo?editors=0010 You should be able to wire that up to ScrollTrigger quite easily. If you get stuck, just post a minimal demo. I hope that helps.
  2. Hey Steve! Welcome to the forums. And to GSAP! I'm not a Vue guy, and it's super difficult to troubleshoot without a minimal demo but I'd suggest you just focus on the basics initially and then get it working in Vue. Here's a super quick demo I whipped up for you: https://codepen.io/GreenSock/pen/QWmGQJG And yes, you'd need some ScrollTriggers (if I understand your goal correctly) If you're still having trouble, just post your attempt here as a minimal demo (in CodePen or CodeSandbox) and we'll do our best to answer any GSAP-specific questions. You can click the "fork" button in the lower right corner of that CodePen I provided above, and then edit away to your heart's content. Have fun!
  3. Hello everyone, Thanks for creating such an amazing tool. I know that this is a common issue but unfortunately I'm having a bit of difficulty fixing my problem. I've whipped up a quick demo of the problem on codepen and included a video of how it's behaving on the actual site itself. I'm pinning the three sections at the same time to keep the middle section in view with no gap but for some reason on resize just the bottom half of the scrolltrigger glitches and then dissappears. Would really appreciate some insight, thanks! VIDEO: https://www.veed.io/view/5fca3e04-1151-468e-bb65-aec9a428e537
  4. Cool, glad to help! I think that is a bit of a miss understanding, a demo with code that is not working are the best once! Then anyone can see what your thought proces is and all they need to do is update some values to help you out. With out one anyone that wants to help first needs to put in the time to create a demo, now I had 10 minutes to spare and whipped up a codepen, but keep in mind that most of the contributors here are volunteers and help others for free.
  5. Oh, sure, that's totally expected behavior since you're pinning the one element but the others continue to scroll. That's very much by design and not a bug at all. In fact, if it didn't behave like that, I bet we'd get a ton of complaints about that being a bug As you've discovered, there are ways to get the behavior you wanted (pinning the container). Thanks for the minimal demo, by the way - that made it very easy to see what you meant. Oh goodness, no! I hope you've seen that we keep adding features, improving things, etc. Virtually every GSAP release since ScrollTrigger was born has had new ScrollTrigger goodies. I've been spending hundreds of hours on ScrollTrigger-related improvements the past few weeks alone. I have never gotten to the place with any tool where I thought "yep, that's perfect...there's nothing that could possibly be improved." When I call it an "edge case", I don't mean to dismiss you or imply your suggestion has no merit. Every feature has a cost (file size, performance, development time, API surface area, code complexity, etc.). We just have to be careful about where we focus our limited resources and which features are truly worth the costs. What if the feature you're asking for made ScrollTrigger 30% bigger and 25% slower across the board, and only 0.02% of our user base would even use the feature - would that be worth it? What if it took 6 months of development due to the complexities? What if there were 10 other super-cool features that could be developed in that same amount of time that would significantly benefit 85% of our user base in their daily work and cost a fraction of the kb with no performance penalty? Can you see why it wouldn't be prudent to make that tradeoff? I know that when you are the one experiencing a frustration and desire a particular feature, it probably feels like an extremely common problem that tons of people must run into. You may be right, but it's definitely not what the data show (at least according to the data I have access to). Sometimes I think it's difficult for users to understand all the complexities involved with creating and maintaining a tool like ScrollTrigger. It may sound easy to "just make it do ____ because my project needs it and I think a lot of other people would want that too," but making it a reality is quite a different thing. It may be a fun experiment for you to try to either build something like ScrollTrigger with a similar feature set (plus the one you're requesting), or try editing ScrollTrigger's source code to add the feature you're requesting. My guess is that it'll become apparent pretty quickly that it's no easy task My only point in bringing that up is that if it was as simple as "tweak 20 lines of code," I'd totally do that because the cost wouldn't be terribly high. But what you're asking for is far more complex and would likely open a can of worms that could lead to non-intuitive behavior like I described earlier where the "progress" doesn't report as expected, etc. Here's another one - I whipped up a helper function that should make it easier for you: https://codepen.io/GreenSock/pen/ZErZrLm Here's the helper function: function pinSafe(animation) { let originalVars = animation.vars.scrollTrigger, markers = originalVars.markers, // we'll just use this ScrollTrigger for positioning purposes. It's identical to the original (but no markers) st = ScrollTrigger.create(Object.assign(originalVars, {markers: false})), container = st.pinnedContainer, split = [], // store the new tweens we create here onRefresh = () => { let distance = st.end - st.start; // now find the ScrollTrigger(s) with the pinnedContainer as the pin ScrollTrigger.getAll().forEach(curST => { if (curST.pin === container) { let curDistance = curST.end - curST.start; if (curST.start > st.start && st.end > curST.start) { // split it into two tweens (one for the first part of the animation before the pin, and the second for the part after the tween) split.push( gsap.fromTo(animation, {progress: 0}, {progress: (curST.start - st.start) / distance, ease: "none", scrollTrigger: {start: st.start, end: curST.start, scrub: originalVars.scrub, markers: markers}}), gsap.fromTo(animation, {progress: (curST.start - st.start) / distance}, {progress: 1, ease: "none", scrollTrigger: {start: curST.end, end: st.end + curDistance, scrub: originalVars.scrub, markers: markers} }) ); } else if (st.start === st.start) { // special case (if the start positions are identical, push it down) split.push(gsap.fromTo(animation, {progress: 0}, {progress: 1, ease: "none", scrollTrigger: {start: curST.end, end: st.end + curDistance, scrub: originalVars.scrub, markers: markers}})); } } }); if (!split.length) { // if none were created, just revert back to the "normal" one. split.push(gsap.fromTo(animation, {progress: 0}, {progress: 1, ease: "none", scrollTrigger: originalVars})); } }; animation.scrollTrigger.kill(true, true); animation.pause(); ScrollTrigger.addEventListener("refresh", onRefresh); ScrollTrigger.addEventListener("refreshInit", () => { split.forEach(s => s.kill(false)); split.length = 0; }); } Just pass the tween/timeline into the pinSafe() method and it looks for other ScrollTriggers that pin the pinnedContainer and then if/when it finds one, it splits it apart into two tweens of the animation's progress (before and after the pinning chunk) I hope that helps. Very kind of you to say. ?
  6. First of all, thanks for being a Club GreenSock member, @HeadGremlin! ? We don't normally do this (build out solutions), but this was a fun challenge for me so I went ahead and whipped together a little demo with one way of doing it: https://codepen.io/GreenSock/pen/RwQEKBw?editors=0010 I added some commented-out code for if you want to break things into an Array for columns and another for rows because if you're gonna have a lot fo them, you may want to optimize the algorithm so that it can skip the out-of-range columns/rows. I don't have time to do all of that for you (optimization is a deep topic), but hopefully this gets you moving in a good direction. Enjoy!
  7. It's been a minute since Jack has "whipped up" a new helper function. Time to dust off this old meme.
  8. There isn't a simplistic solution for that, but I whipped up a helper function for you: https://codepen.io/GreenSock/pen/dydgLWN?editors=0010 function addStaggerCallback(animation, vars) { animation.getChildren && animation.getChildren(true, true, false).forEach(t => addStaggerCallback(t, vars)); animation.timeline && animation.timeline.getChildren(false, true, false).forEach(t => { for (let p in vars) { t.eventCallback(p, vars[p], t.targets()); } }); } Usage: let flip = Flip.from(..., {stagger: yourFunc}); addStaggerCallback(flip, { onComplete: target => console.log("done!", target), onStart: target => console.log("started", target) }); Does that help?
  9. Hello @TrulyNewbie That sounds and looks to me like you want to do something very similar to what was asked in the thread linked below. @GreenSock forged (or in other words, he whipped together ) a neat solution for that. Maybe have a look and see if that will help you.
  10. Ah, right, there's no smoothing happening on mobile so it's not a shock that is more abrupt. If you're trying to smooth that out, you could definitely use a gsap.quickTo() to introduce some gradual interpolation. In fact, I whipped together a helper function that'll return a FUNCTION that you can call anytime to get the smoothed-out velocity: function velocitySmoother(smoother, duration) { duration = duration || 0.5; let tracker = {v: 0}, vLast = 0, tLast = Date.now(), velocityTo = gsap.quickTo(tracker, "v", { duration: duration }); duration *= 1000; return () => { let v = smoother.getVelocity(), t = Date.now(), ratio = 1 - Math.min(1, (t - tLast) / duration); if (v !== vLast) { velocityTo(v); vLast = v; tLast = t; } return v - (v - tracker.v) * ratio; } } Usage: let smoother = ScrollSmoother.create({ ... }); let getVelocity = velocitySmoother(smoother); // log out the velocity every 100ms... setInterval(() => console.log("velocity", getVelocity()), 100); Does that help?
  11. First of all, please don't spam the forums with multiple posts about the same topic. The main problem is that you're misusing containerAnimation. That's for when you're scrolling the page vertically but you want to move something horizontally. But in your case, you're moving things vertically AND scrolling vertically. That won't work. Your vertical movement is throwing off the scroll intersection points. You also had the "end" position set to a specific pixel value rather than a relative one that is prefixed with "+=". To solve this, I believe you'd need to do the math and calculate where the intersection points would be. My idea would be to create a timeline with callbacks spaced out where the intersection spots would be and then you can just run animations in those (opacity). Normally we don't provide custom solutions like this, but I was curious and enjoy a challenge, so I whipped up a demo for you with reusable functions: https://codepen.io/GreenSock/pen/poarboL?editors=0010 Side note: your demo was also far more complicated than it needed to be; if you want an accurate answer quickly, please take the time to isolate the issue with as little code and markup as possible. You had over 500 lines of HTML/CSS/JS to weed through when you probably needed less than 60 (a few colored <div> elements, one column, etc.) I hope that helps!
  12. Sorry about any confusion there - it looks like a regression since 3.6.0 that'd cause only CSS variables that are complex values (more than one number) to neglect animating the others beyond the first value, but that has already been fixed in the next release which you can preview at https://assets.codepen.io/16327/gsap-latest-beta.min.js In the meantime, you can either use that beta file or I whipped together this cssVars plugin that'll work around it for you: gsap.registerPlugin({ name: "cssVars", init(target, vars) { this.t = {}; this.target = target; for (let p in vars) { this.t[p] = gsap.getProperty(target, p); this.add(this.t, p, this.t[p], vars[p]); } }, render(ratio, data) { let pt = data._pt; while (pt) { pt.r(ratio, pt.d); data.target.style.setProperty(pt.p, data.t[pt.p]); pt = pt._next; } } }); https://codepen.io/GreenSock/pen/RwjYqaZ?editors=0010
  13. Thank you for your reply @GreenSock ! I decided, to try and make what I want from scratch, i whipped up a little demo, which might not be the most optimal way to solve the problem, but it kinda works, but I also got into another problem. I don't know if this is even on topic, if not i'm sorry, but I'm trying to find out, how to continue an animation from existing "x", meaning that I'd like the slides to continue on to the green slide instead of restarting the animation from the red slide. Horizontal slideshow (codepen.io) Edit: should i use timeline? sorry i'm really new to GSAP
  14. I was asked yesterday about animating to/from backgroundSize: "cover" or "contain" with GSAP, so I figured I'd share the solution here in case it helps anyone else. The problem: GSAP interpolates between numbers, but how is it supposed to interpolate between something like "300px 250px" and "contain" (not a number)? So I whipped together a function that basically translates "contain" or "cover" into their px-based equivalents for that particular element at whatever size it is then. Once we've got it converted, it's easy to animate. //this function converts the backgroundSize of an element from "cover" or "contain" or "auto" into px-based dimensions. To set it immediately, pass true as the 2nd parameter. function getBGSize(element, setInPx) { var e = (typeof(element) === "string") ? document.querySelector(element) : element, cs = window.getComputedStyle(e), imageUrl = cs.backgroundImage, size = cs.backgroundSize, image, w, h, iw, ih, ew, eh, ratio; if (imageUrl && !/\d/g.test(size)) { image = new Image(); image.setAttribute("src", imageUrl.replace(/(^url\("|^url\('|^url\(|"\)$|'\)$|\)$)/gi, "")); //remove any url() wrapper. Note: some browsers include quotes, some don't. iw = image.naturalWidth; ih = image.naturalHeight; ratio = iw / ih; ew = e.offsetWidth; eh = e.offsetHeight; if (!iw || !ih) { console.log("getBGSize() failed; image hasn't loaded yet."); } if (size === "cover" || size === "contain") { if ((size === "cover") === (iw / ew > ih / eh)) { h = eh; w = eh * ratio; } else { w = ew; h = ew / ratio; } } else { //"auto" w = iw; h = ih; } size = Math.ceil(w) + "px " + Math.ceil(h) + "px"; if (setInPx) { e.style.backgroundSize = size; } } return size; } The only catch is that the image must be already loaded, otherwise it's impossible to figure out the native dimensions of the image (aspect ratio). While it's technically possible to add this functionality into CSSPlugin, it didn't seem advisable because it eats up a fair amount of kb and it's EXTREMELY uncommon for folks to want to animate to/from a background-size of cover or contain. So maybe 0.0001% of the audience would benefit but 100% would pay the kb price. Didn't seem worthwhile, so a helper function like this struck me as more appropriate. Feel free to chime in if you disagree. Happy tweening!
  15. Thanks for the feedback and suggestion(s), @Carl. In the CodePen below, I whipped together a plugin just for you that'll let you do stuff like this: gsap.fromTo(".photo", { backgroundSize: { size: "cover", scale: 3, // scale up the "cover" dimensions (pixels) nativeWidth:1200, nativeHeight:1200 } }, { backgroundSize: { size: "cover", nativeWidth:1200, nativeHeight:1200 }, duration: 2 }); https://codepen.io/GreenSock/pen/rNYxENg?editors=0010 Does that help? I added it to the helper functions page too.
  16. No, you can't alter the scrub value, but you could get a similar effect by simply tweening the scroll position to make things longer. Candid admission: usually we don't do "build-to-order" things in these forums, but I got curious and love a challenge, so as a courtesy I whipped together an example and even made the page draggable: https://codepen.io/GreenSock/pen/yLzQavG?editors=0010 Notice the buttons create a tween of the scroll position and they take 2 seconds to complete. Obviously you can tweak that to whatever you want. Does that help?
  17. Alright, you got me curious and I love a challenge, so I whipped up a draggable demo that even uses inertia and is configurable: https://codepen.io/GreenSock/pen/RwLepdQ?editors=0010 Is that kinda what you were looking for?
  18. Just search the forum for "whipped" and you'll find a bunch of helper functions "whipped together" by the Big Kahuna. https://greensock.com/forums/search/?page=1&type=forums_topic&nodes=11&q=whipped And yes, they should all be put into a blog post or the docs or something. Lots of cool stuff. ?
  19. Hi there! I would like to add a scalar tween to a component in React using GSAP, the component is a functional component. I've whipped up a quick example of how i think that could be implemented in React using GSAP in the attached codepen. (Type in some number values into the input and watch GSAP tween the number below the input) It's a bit dirty as i need to add a global object to the window on the first mount of the component, so that GSAP has an object it can freely mutate without React resetting it on each render. Is there are better way of allowing GSAP to perform a scalar transition within React? Thanks for any help you can give!
  20. I think this thread may help you: And here's another approach with a helper function I just whipped up for you: // speed can be positive or negative (in pixels per second) function verticalLoop(elements, speed) { elements = gsap.utils.toArray(elements); let firstBounds = elements[0].getBoundingClientRect(), lastBounds = elements[elements.length - 1].getBoundingClientRect(), top = firstBounds.top - firstBounds.height - Math.abs(elements[1].getBoundingClientRect().top - firstBounds.bottom), bottom = lastBounds.top, distance = bottom - top, duration = Math.abs(distance / speed), tl = gsap.timeline({repeat: -1}), plus = speed < 0 ? "-=" : "+=", minus = speed < 0 ? "+=" : "-="; elements.forEach(el => { let bounds = el.getBoundingClientRect(), ratio = Math.abs((bottom - bounds.top) / distance); if (speed < 0) { ratio = 1 - ratio; } tl.to(el, { y: plus + distance * ratio, duration: duration * ratio, ease: "none" }, 0); tl.fromTo(el, { y: minus + distance }, { y: plus + (1 - ratio) * distance, ease: "none", duration: (1 - ratio) * duration, immediateRender: false }, duration * ratio) }); return tl; } In action: https://codepen.io/GreenSock/pen/yLoprbe?editors=0010 Does that help?
  21. That behaviour sounds to me like the one discussed in another thread, where @GreenSock mentioned that this is not exactly the type of behaviour, ScrollTrigger was built for, but where he also whipped together a helper function for a scenario like that. Give it a shot and see if it fits your needs, too https://codepen.io/GreenSock/pen/wvMRroV
  22. It is an interesting challenge...I got sucked in and whipped together this solution: https://codepen.io/GreenSock/pen/powvxNx?editors=0010 It basically uses a "y" animation to fake the scroll on sections that are taller than the window height. Is that what you were looking for?
  23. Yeah, you need to be very careful because there are a lot of factors: Startup spike - when a page loads, the browser has to spend a LOT of resources on various things like layout, rendering, rasterizing, layerizing, executing the JS and looking for the best ways to optimize it (identifying "hot" functions), etc. So if, for example, you run some transform animations for 500ms on initial load, for example, it may artificially seem like it's a slow device because the browser is so busy doing all those other tasks, but those will settle down shortly and the JS/animations may run buttery smooth thereafter. Be careful about a simple gsap.ticker.frame / gsap.ticker.time because that doesn't compensate for the initial drop in FPS when a page initially loads and the browser is doing all that startup processing. That initial hit may contaminate the average. In other words, maybe it runs at 0.5fps for the first 1.5 seconds, and then 60fps thereafter, but if you check the fps at the 2-second point, it'll look pretty terrible, like 2fps or whatever. If you then shut off animations because you think it's a slow device, you totally miss the fact that it was actually starting to run at 60fps right before you measured. Refresh rate (hz) - Most devices target 60fps but some may run at 30hz, and others at 120. So if the device only refreshes its screen at 30hz, the browser will only run requestAnimationFrame() 30 times per second, thus if you have code that's verifying against a 60fps target, that device may "fail" even though it's not maxing the CPU out at all and is running perfectly "smooth" (according to its own specs). Competing resources - like Blake said, your test may happen to run at the very moment the device has a CPU spike due to some other process like pulling in and parsing data via Wi-Fi or performing a backup routine or some other expensive app is open in the background. I don't think there are any foolproof, reliable ways to calculate what you're asking in a very short amount of time but here's a helper function Just whipped up for you that might be helpful: function testPerformance(config) { let fps = config.fps || 45, duration = config.duration || 1, testElements = [], i = ("elementCount" in config) ? config.elementCount : 5, runTest = () => { let startFrame = gsap.ticker.frame; gsap.to(testElements, {rotation: 360, x: 100, y: 100, duration: duration, onComplete: () => { let measuredFPS = (gsap.ticker.frame - startFrame) / duration; testElements.forEach(el => el.parentNode.removeChild(el)); if (measuredFPS < fps) { config.onFail && config.onFail(measuredFPS); } else { config.onSuccess && config.onSuccess(measuredFPS); } } }); }, onLoad = () => config.delay ? gsap.delayedCall(config.delay, runTest) : runTest(); while (--i) { let el = document.createElement("div"); document.body.appendChild(el); testElements.push(el); } gsap.set(testElements, {position: "absolute", visibility: "hidden", width: 10, height: 10, top: 0, left: 0, x: 0}); // remove elements from document flow and make the invisible (document.readyState === "loading") ? window.addEventListener("load", onLoad) : onLoad(); } Usage looks like: testPerformance({ fps: 45, // minimum frames per second duration: 1, // how long to run the test (in seconds) delay: 0.5, // time (in seconds) between page load and test start (to give the browser time to settle down) elementCount: 5, // number of elements to animate during the test (rotation, x, and y) onSuccess: fps => console.log("success", fps), onFail: fps => console.log("fail", fps) }); You could crank up the elementCount to a super high amount if you want to see the fps drop. Not that you'd do that in your "real" project. Does that help?
  24. Hey @Umberto From what I can tell you from my experience with smooth-scrolling, position fixed elements most often (if not always) won't work the way you'd expect inside the element that is being translated up and down for the smooth-scrolling effect. This seems to be the case here too, so you will probably have to place your modal outside of your #content if you want the position fixed to apply. https://codepen.io/akapowl/pen/1893b89b6fb9a775989f1530f908dc30 On a further note; you seem to be using an older approach of the scrolltrigger-native smooth-scrolling. @GreenSock has whipped together a very helpful helper-function for scrolltrigger-native smooth-scrolling some time ago, that you might not be aware of yet - it will save you some troubles further down the road when it comes to pinning things, so you might want to consider taking a look at and using that one instead. You will find it on the .scrollerProxy() documentation page (first demo in there) - it won't change what I said above about position fixed elements though. Sidenote: Your demo was not working because you were missing a closing </div> tag in your html and were not loading the scripts from a valid ressource - I changed that in my pen above.
  25. Welcome to the forums, @Nzhiti! Since this is a somewhat common effect (a group of elements that seamlessly loop on the horizontal axis), I whipped together a helper function that makes it significantly easier. Here's a fork with it in place: https://codepen.io/GreenSock/pen/wvgOmBO?editors=0110 Here's the helper function isolated: /* This helper function makes a group of elements animate along the x-axis in a seamless, responsive loop. Features: - Uses xPercent so that even if the widths change (like if the window gets resized), it should still work in most cases. - When each item animates to the left or right enough, it will loop back to the other side - Optionally pass in a config object with values like "speed" (default: 1, which travels at roughly 100 pixels per second), paused (boolean), and repeat. - The returned timeline will have the following methods added to it: - next() - animates to the next element using a timeline.tweenTo() which it returns. You can pass in a vars object to control duration, easing, etc. - previous() - animates to the previous element using a timeline.tweenTo() which it returns. You can pass in a vars object to control duration, easing, etc. - toIndex() - pass in a zero-based index value of the element that it should animate to, and optionally pass in a vars object to control duration, easing, etc. - current() - returns the current index (if an animation is in-progress, it reflects the final index) - times - an Array of the times on the timeline where each element hits the "starting" spot. There's also a label added accordingly, so "label1" is when the 2nd element reaches the start. */ function horizontalLoop(items, config) { items = gsap.utils.toArray(items); config = config || {}; let tl = gsap.timeline({repeat: config.repeat, paused: config.paused, defaults: {ease: "none"}}), length = items.length, startX = items[0].offsetLeft, times = [], widths = [], xPercents = [], curIndex = 0, 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 totalWidth, curX, distanceToStart, distanceToLoop, item, i; // convert "x" to "xPercent" to make things responsive, and populate the widths/xPercents Arrays to make lookups faster. gsap.set(items, {xPercent: (i, el) => { let w = widths[i] = parseFloat(gsap.getProperty(el, "width", "px")); xPercents[i] = snap(parseFloat(gsap.getProperty(el, "x", "px")) / w * 100 + gsap.getProperty(el, "xPercent")); return xPercents[i]; }}); gsap.set(items, {x: 0}); totalWidth = items[length-1].offsetLeft + xPercents[length-1] / 100 * widths[length-1] - startX + items[length-1].offsetWidth * gsap.getProperty(items[length-1], "scaleX") for (i = 0; i < length; i++) { item = items[i]; curX = xPercents[i] / 100 * widths[i]; distanceToStart = item.offsetLeft + curX - startX; 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; } function toIndex(index, vars) { vars = vars || {}; let newIndex = gsap.utils.wrap(0, length, index), time = times[newIndex]; if (time > tl.time() !== index > curIndex) { // if we're wrapping the timeline's playhead, make the proper adjustments vars.modifiers = {time: gsap.utils.wrap(0, tl.duration())}; time += tl.duration() * (index > curIndex ? 1 : -1); } curIndex = newIndex; vars.overwrite = true; return tl.tweenTo(time, vars); } tl.next = vars => toIndex(curIndex+1, vars); tl.previous = vars => toIndex(curIndex-1, vars); tl.current = () => curIndex; tl.toIndex = (index, vars) => toIndex(index, vars); tl.times = times; return tl; } Usage const loop = horizontalLoop(".magnet", {paused: true}); next.addEventListener("click", () => loop.next({duration: 1, ease: "power1"})); prev.addEventListener("click", () => loop.previous({duration: 1, ease: "power1"})); Kinda fun, huh? And since I made all the movement based on xPercent, it's pretty much responsive, meaning you can alter the width and it should still work. here's a responsive version that makes the magnets always 20% of the width of that container (fitting nicely): https://codepen.io/GreenSock/pen/ZELPxWW?editors=0010 Does that help?
×
×
  • Create New...