jenda Posted August 26, 2021 Share Posted August 26, 2021 Hello, I'm trying to have a section with horizontally scrolled elements by using ScrollTrigger. My inspiration comes from this forum thread. The container is hidden first and appears when you click on one of the links. There is a div (side-story-inside) which contains 3 or more divs which I would like to scroll horizontally so you could see all the content horizontally next to each other. I'm quite a new GSAP user that's why I'm asking to get help here. I would be very tahnkful for any help. See the Pen BaZNNQw by jankout (@jankout) on CodePen Link to comment Share on other sites More sharing options...
Cassie Posted August 26, 2021 Share Posted August 26, 2021 Hi Jenda - You currently don't have the content in the pop out sections laid out in a row - it's in a column. Styling and markup is key for scrollTriggered animations. In terms of approach - scrollTrigger calculates positions on load and you're trying to animate the width of hidden containers, so there's no width to measure - You will have to create the scrollTimeline after the section has been opened so scrollTrigger can get the right values.. You'll need to target the specific container when you open it - the way you're approaching it now is a common error, we outline it in this article You're getting all the containers and trying to use a single tween to animate them all at once. If you log out console.log(sideStoryContainer.offsetWidth)You'll see it's undefined. This is because sideStoryContainer is a collection of elements - not an element, so it doesn't have an offset width. gsap.registerPlugin(ScrollTrigger); let sideStoryContainer = document.getElementsByClassName("side-story-inside"); gsap.to(sideStoryContainer, { x: () => -(sideStoryContainer.scrollWidth - document.documentElement.clientWidth) + "px", ease: "none", scrollTrigger: { trigger: sideStoryContainer, invalidateOnRefresh: true, pin: true, scrub: 1, horizontal: true, end: () => "+=" + sideStoryContainer.offsetWidth } }); I hope these notes help! Anyone else is free to jump in to assist but this is quite a complex interaction you're attempting, and I'm afraid we don't have the capacity to sculpt custom solution in these forums. If you want to take these notes and give it a bash we'll be here to nudge you in the right direction though. 2 Link to comment Share on other sites More sharing options...
jenda Posted December 8, 2021 Author Share Posted December 8, 2021 Hey, thank you very much for your respond. I still have some questions. Unfortunately, it cannot get this to work yet. I think I have to change some CSS rules to fix it too - am I right? One of my questions is: where shall I put the timeline? As you wrote after this ScrollTrigger can recalculate the with of the new open content container. Link to comment Share on other sites More sharing options...
Cassie Posted December 8, 2021 Share Posted December 8, 2021 When you open a container, you'll need to create a new scrollTrigger for that container - so inside the function you're calling on click. On 8/26/2021 at 2:46 PM, Cassie said: scrollTrigger calculates positions on load and you're trying to animate the width of hidden containers, so there's no width to measure - You will have to create the scrollTimeline after the section has been opened so scrollTrigger can get the right values.. You'll need to target the specific container when you open it - the way you're approaching it now is a common error, we outline it in this article And yep, you'll need to adjust the CSS so the sections are laid out in a row. On 8/26/2021 at 2:46 PM, Cassie said: You currently don't have the content in the pop out sections laid out in a row - it's in a column. Styling and markup is key for scrollTriggered animations. If you have questions could you add an adjusted codepen so we can see what you've tried. Thanks! ✨ 1 Link to comment Share on other sites More sharing options...
jenda Posted December 9, 2021 Author Share Posted December 9, 2021 Thank you. I will try it and make another demo to post it here. Another question. Which is the best method to initiate gsap code to give gsap the right position of target elements? Sometimes I can see wrong position of the pined elements in my website. I guess it’s because of late loaded imagines that influence the whole height of the website. What would you recommend? Link to comment Share on other sites More sharing options...
OSUblake Posted December 9, 2021 Share Posted December 9, 2021 It sounds like you need to call ScrollTrigger.refresh() after your images load. https://greensock.com/docs/v3/Plugins/ScrollTrigger/static.refresh() Link to comment Share on other sites More sharing options...
jenda Posted December 15, 2021 Author Share Posted December 15, 2021 Another question - is it necessary that the scrolled part is absolute positioned? Link to comment Share on other sites More sharing options...
OSUblake Posted December 15, 2021 Share Posted December 15, 2021 What scrolled part you are referring to? Link to comment Share on other sites More sharing options...
jenda Posted January 6, 2022 Author Share Posted January 6, 2022 Hello, my code works but only one thing I need to improve - at the moment, the animation ends with end: "+=3000" altough when I scroll to the last div the main container stops to by pinned correctly earlier but its padding-bottom value is still too big so it takes time to reach the following element (especialy in the mobile version). I can understand that the end value influences the padding-bottom value. That's why I would like to calculate the horizontal scrolling area to get the right value for the end value Could anybody help me with this part, please? function openSideStory2(clickedId, parentId) { var contentId = '#'+clickedId; var parentContainerId = '#'+parentId; let mainContainer = contentId var elementSelector = contentId + "-wrapper > div"; var contentDivs = gsap.utils.toArray(elementSelector); gsap.to(contentDivs, { xPercent: -100 * (contentDivs.length - 1), ease: "none", scrollTrigger: { trigger: '#side-story-container-11530', end: "+=3000" , // "+=3000", () => "+=" + contentDivs.offsetWidth endTrigger: 'elementSelector:last-child', scrub: 0.4, pin: true, onComplete: function() { document.querySelector('.side-story-container').classList.remove('open-side-story'); document.querySelector('.side-story-box').classList.remove('active'); gsap.set(parentContainerId, {position: "static"}); }, } }); }; Link to comment Share on other sites More sharing options...
Cassie Posted January 6, 2022 Share Posted January 6, 2022 Heya! I think you've already got the solution there, it's commented out. Hard to be sure without seeing your CSS and HTML though. Could you pop together a codepen? Link to comment Share on other sites More sharing options...
jenda Posted January 6, 2022 Author Share Posted January 6, 2022 Unfortunately, it doesn’t work because the ofsetWidth is undefined then - if I use it without +=3000. Link to comment Share on other sites More sharing options...
jenda Posted January 6, 2022 Author Share Posted January 6, 2022 Here is the current CodePen Link to comment Share on other sites More sharing options...
Cassie Posted January 6, 2022 Share Posted January 6, 2022 Oh, ok! So you need to get the specific container that you're opening when you click. At the moment you're just grabbing all the content divs in the document.let contentDivs = gsap.utils.toArray(".side-story-inside > div"); You had some stray HTML tags and there are some odd things going on with your styling, but maybe this is closer to what you're wanting? See the Pen zYELYNQ?editors=0110 by GreenSock (@GreenSock) on CodePen Link to comment Share on other sites More sharing options...
jenda Posted January 6, 2022 Author Share Posted January 6, 2022 Ok. Thank you. Another question. Can I check if it's necessary to scroll the content divs as long as all of them are in the viewport? Link to comment Share on other sites More sharing options...
jenda Posted January 10, 2022 Author Share Posted January 10, 2022 Antoher question. Is it possible to activate the draggen gestures for mobile diveces to able this scrolling possibility too? Link to comment Share on other sites More sharing options...
OSUblake Posted January 10, 2022 Share Posted January 10, 2022 On 1/6/2022 at 10:31 AM, jenda said: Another question. Can I check if it's necessary to scroll the content divs as long as all of them are in the viewport? You'd have to check and see if the width is greater than the width of your container. 2 hours ago, jenda said: Antoher question. Is it possible to activate the draggen gestures for mobile diveces to able this scrolling possibility too? Here's a demo showing how get Draggable and ScrollTrigger to work together. See the Pen ZELQqeJ by GreenSock (@GreenSock) on CodePen 1 Link to comment Share on other sites More sharing options...
jenda Posted January 11, 2022 Author Share Posted January 11, 2022 thank you very much. Is it possible to check the width of the all scrolled divs? In my case, the pinned container has the same width like the main window (css width: 100%). So there is no difference that's why I cannot work with offsetWidthfor the pinned container. Instead of this, I need to get the width of the scrolled divs. My question is: how can I calculate it? In my ScrollTrigger, I use the calculate function to define the end of the animation end: () => { return `+=300 + ${100 * contentDivs.length}`},. If I understand it right something similar I could use to get the width of all scrolling div, right? Thank you for your patience. let mainContainer = contentId var elementSelector = contentId + "-wrapper > div" var contentDivs = gsap.utils.toArray(elementSelector) let offsetValue = 0 let scrubValue = 2 if(window.innerWidth >= 800){ offsetValue = 1 scrubValue = 0.4 } else if (window.innerWidth >= 1280) { offsetValue = 2 } let scrollTween = gsap.to(contentDivs, { xPercent: -100 * (contentDivs.length - offsetValue), ease: "none" }); let horizontalScroll = ScrollTrigger.create({ animation: scrollTween, trigger: mainContainer, end: () => { return `+=300 + ${100 * contentDivs.length}` }, // "+=3000" () => "+=" + contentDivs.offsetWidth scrub: scrubValue, pin: true, onComplete: function() { document.querySelector('.side-story-container').classList.remove('open-side-story'); document.querySelector('.side-story-box').classList.remove('active'); //ScrollTrigger.kill(); gsap.set(parentContainerId, {position: "static"}); }, }); // total scroll amount divided by the total distance that the sections move gives us the ratio we can apply to the pointer movement so that it fits. var dragRatio = mainContainer.offsetWidth / (window.innerWidth * (contentDivs.length - 1)); var drag = Draggable.create(".proxy", { trigger: mainContainer, type: "x", onPress() { this.startScroll = horizontalScroll.scroll(); }, onDrag() { horizontalScroll.scroll(this.startScroll - (this.x - this.startX) * dragRatio); // if you don't want it to lag at all while dragging (due to the 1-second scrub), uncomment the next line: //horizontalScroll.getTween().progress(1); } })[0]; Link to comment Share on other sites More sharing options...
OSUblake Posted January 11, 2022 Share Posted January 11, 2022 This post shows how to get the scrollWidth... Link to comment Share on other sites More sharing options...
jenda Posted January 11, 2022 Author Share Posted January 11, 2022 Thank you very much. Unfortunately, in my case, if I grab it with my mouse and drag it to move it I jump to the top of the page. Is it a common mistake of newbies like me? :) Link to comment Share on other sites More sharing options...
OSUblake Posted January 11, 2022 Share Posted January 11, 2022 Hard to say without seeing it. Can you make a demo? Link to comment Share on other sites More sharing options...
jenda Posted January 12, 2022 Author Share Posted January 12, 2022 At the end, it works. Thank you. My last question. How can I stop/kill the ScrollTrigger which I have in a function - the ScrollTrigger is as let with name horizontalScroll. I mean I would like to kill it out of the function with a click event. Link to comment Share on other sites More sharing options...
OSUblake Posted January 12, 2022 Share Posted January 12, 2022 Just call .kill() on it. You should probably also do the same for the Draggable. horizontalScroll.kill(); drag.kill(); Link to comment Share on other sites More sharing options...
jenda Posted January 12, 2022 Author Share Posted January 12, 2022 Thank you. But how can I call it outside of my function where I define the Draggable part? At the moment, I could do it for the ScrollTrigger like this. Unfortunatelly, I cannot assing any ID to the Draggable part I defined. Otherwise I could use the same logic like for the ScrollTrigger. Is there any possibility to call the Draggable variation out of the function? Here is my function with the trigger link. $(function() { $('a[data-side-story]').click(function (event) { event.preventDefault(); if (this.hash !== "") { var hashSideStory = this.hash; $(hashSideStory).addClass('active'); $(hashSideStory).parents('.side-story-container').addClass('open-side-story'); var clickedId = $(this).attr('data-id'); var parentId = $(this).attr('data-parent-id'); //console.log(parentId); let cleanSideStoryId = hashSideStory.replace('#', ''); openSideStory(cleanSideStoryId); } }); $('.side-story-trigger-close').click(function (event) { event.preventDefault(); let triggerId = $(this).closest('.side-story-box').attr('id') //console.log("id zum entfernen: " + triggerId); ScrollTrigger.getById(triggerId).kill(true); gsap.set('#'+triggerId, {clearProps: true}); //Draggable.getById(triggerId).kill(true); $('.side-story-box').removeClass('active'); $('.side-story-box').parents('.side-story-container').removeClass('open-side-story'); }); }); function openSideStory(cleanSideStoryId) { let mainContainer = "#"+cleanSideStoryId let containerInside = cleanSideStoryId + "-wrapper" let containerInsideId = document.getElementById(containerInside) let containerInsideWidth = document.getElementById(containerInside).offsetWidth let triggerButton = document.querySelector('.side-story-box.active .side-story-trigger-close') //console.log(triggerButton); let elementSelector = mainContainer + "-wrapper > div" let contentDivs = gsap.utils.toArray(elementSelector) let offsetValue = 0 let scrubValue = 2 if(window.innerWidth >= 800){ offsetValue = 1 scrubValue = 2 } else if (window.innerWidth >= 1280) { offsetValue = 2 } let contentDivsWidth = 0 contentDivs.forEach(e => { contentDivsWidth += e.offsetWidth } ); //console.log(containerInsideWidth); //console.log(contentDivsWidth); //console.log(containerInsideWidth < contentDivsWidth); if (containerInsideWidth > contentDivsWidth) { // do nothing } else { let scrollTween = gsap.to(contentDivs, { xPercent: -100 * (contentDivs.length - offsetValue), ease: "none" }); let horizontalScroll = ScrollTrigger.create({ animation: scrollTween, trigger: mainContainer, id: cleanSideStoryId, start: "top 20", end: () => { return `${100 * contentDivs.length}` }, // "+=3000" () => "+=" + contentDivs.offsetWidth scrub: scrubValue, pin: true, onLeave: function() { triggerButton.addEventListener("click", (e) => { triggerButton.click(); }); }, }); // total scroll amount divided by the total distance that the sections move gives us the ratio we can apply to the pointer movement so that it fits. var dragRatio = containerInsideWidth / (window.innerWidth * (contentDivs.length - 1)); var drag = Draggable.create(".side-story-proxy", { trigger: mainContainer, type: "x", onPress() { this.startScroll = horizontalScroll.scroll(); }, onDrag() { horizontalScroll.scroll(this.startScroll - (this.x - this.startX) * dragRatio); // if you don't want it to lag at all while dragging (due to the 1-second scrub), uncomment the next line: //horizontalScroll.getTween().progress(1); } })[0]; } }; Link to comment Share on other sites More sharing options...
OSUblake Posted January 12, 2022 Share Posted January 12, 2022 Draggable doesn't have a getById method. Perhaps you meant .get(). https://greensock.com/docs/v3/Plugins/Draggable/static.get() And if you need to access something created inside a function, you can just create the variable outside of it like this... let drag; $(function() { $('a[data-side-story]').click(function (event) { event.preventDefault(); if (this.hash !== "") { var hashSideStory = this.hash; $(hashSideStory).addClass('active'); $(hashSideStory).parents('.side-story-container').addClass('open-side-story'); var clickedId = $(this).attr('data-id'); var parentId = $(this).attr('data-parent-id'); //console.log(parentId); let cleanSideStoryId = hashSideStory.replace('#', ''); openSideStory(cleanSideStoryId); } }); $('.side-story-trigger-close').click(function (event) { event.preventDefault(); let triggerId = $(this).closest('.side-story-box').attr('id') //console.log("id zum entfernen: " + triggerId); ScrollTrigger.getById(triggerId).kill(true); gsap.set('#'+triggerId, {clearProps: true}); //Draggable.getById(triggerId).kill(true); drag.kill() $('.side-story-box').removeClass('active'); $('.side-story-box').parents('.side-story-container').removeClass('open-side-story'); }); }); function openSideStory(cleanSideStoryId) { let mainContainer = "#"+cleanSideStoryId let containerInside = cleanSideStoryId + "-wrapper" let containerInsideId = document.getElementById(containerInside) let containerInsideWidth = document.getElementById(containerInside).offsetWidth let triggerButton = document.querySelector('.side-story-box.active .side-story-trigger-close') //console.log(triggerButton); let elementSelector = mainContainer + "-wrapper > div" let contentDivs = gsap.utils.toArray(elementSelector) let offsetValue = 0 let scrubValue = 2 if(window.innerWidth >= 800){ offsetValue = 1 scrubValue = 2 } else if (window.innerWidth >= 1280) { offsetValue = 2 } let contentDivsWidth = 0 contentDivs.forEach(e => { contentDivsWidth += e.offsetWidth } ); //console.log(containerInsideWidth); //console.log(contentDivsWidth); //console.log(containerInsideWidth < contentDivsWidth); if (containerInsideWidth > contentDivsWidth) { // do nothing } else { let scrollTween = gsap.to(contentDivs, { xPercent: -100 * (contentDivs.length - offsetValue), ease: "none" }); let horizontalScroll = ScrollTrigger.create({ animation: scrollTween, trigger: mainContainer, id: cleanSideStoryId, start: "top 20", end: () => { return `${100 * contentDivs.length}` }, // "+=3000" () => "+=" + contentDivs.offsetWidth scrub: scrubValue, pin: true, onLeave: function() { triggerButton.addEventListener("click", (e) => { triggerButton.click(); }); }, }); // total scroll amount divided by the total distance that the sections move gives us the ratio we can apply to the pointer movement so that it fits. var dragRatio = containerInsideWidth / (window.innerWidth * (contentDivs.length - 1)); drag = Draggable.create(".side-story-proxy", { trigger: mainContainer, type: "x", onPress() { this.startScroll = horizontalScroll.scroll(); }, onDrag() { horizontalScroll.scroll(this.startScroll - (this.x - this.startX) * dragRatio); // if you don't want it to lag at all while dragging (due to the 1-second scrub), uncomment the next line: //horizontalScroll.getTween().progress(1); } })[0]; } }; 2 Link to comment Share on other sites More sharing options...
jenda Posted January 19, 2022 Author Share Posted January 19, 2022 Thank you very much. Hopefully the last question: is it possible to influnce the velocity/speed of scrub on the mobile devices? At the moment, I use scrub: 2 but the scroll speed is too fast still so I would like to have it more natural when I vertically scroll. All divs horizontally scroll by -100%. Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now