Jump to content
GreenSock

jenda

ScrollTrigger horizontal 2

Recommended Posts

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

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.
 

  • Like 2
Link to comment
Share on other sites

  • 3 months later...

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

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!

  • Like 1
Link to comment
Share on other sites

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

Another question - is it necessary that the scrolled part is absolute positioned?

Link to comment
Share on other sites

What scrolled part you are referring to?

Link to comment
Share on other sites

  • 3 weeks later...

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

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

Unfortunately, it doesn’t work because the ofsetWidth is undefined then - if I use it without +=3000

Link to comment
Share on other sites

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

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

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

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

 

  • Like 1
Link to comment
Share on other sites

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

This post shows how to get the scrollWidth...

 

 

Link to comment
Share on other sites

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

Hard to say without seeing it. Can you make a demo?

 

Link to comment
Share on other sites

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

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

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

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];
		}
	
	
};

 

  • Like 2
Link to comment
Share on other sites

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

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 account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×