Animating child elements in ScrollTrigger GSAP horizontal scroll - javascript

I have an svg which forms the basis of my horizontal scroller.
Within this svg, I have added the class .animate to the elements which I want to fade in up as the item comes into view. The .animate class for reference has been added to all the text items in the svg.
Currently, only the .animate elements that are in view initially fade in up. When I scroll down to continue the scroller, the other elements are static. They're not fading in or translating up or down in any way?
TL;DR, here is what I'm trying to achieve:
When the scroller pins in place, and the user continued to scroll down, start fading away .horizontalScroller__intro.
Once .horizontalScroller__intro has faded away, start the horizontal scroll for .horizontalScroller__items
Any elements with the class of .animate in my scroller will fade in up to its original position.
Note: I understand SO rules and preferences to post code here. But, my demo's contain a length SVG, which I cannot post here as it exceeds SO's character limit.
Here is a demo of my latest approach
From the scrollTrigger docs, containerAnimation is what helps achieve animations on horizontal scrollers, and is what I've tried to achieve.
However, in my demo above, I have the following issues:
.horizontalScroller__intro doesn't show initially, when it should, and should fade out on scroll.
The horizontal scroller doesn't work anymore
The .animate elements that are in view, do not fade in up
If I use timeline (see below snippet), then the intro fade out and scroller works. But, doesn't animate in the child elements, which is where I need containerAnimation
$(function() {
let container = document.querySelector(".horizontalScroller__items");
let tl = gsap.timeline({
scrollTrigger: {
trigger: ".horizontalScroller",
pin: ".horizontalScroller",
anticipatePin: 1,
scrub: true,
invalidateOnRefresh: true,
refreshPriority: 1,
end: '+=4000px',
markers: true,
}
});
tl.to('.horizontalScroller__intro', {
opacity: 0,
})
tl.to(container, {
x: () => -(container.scrollWidth - document.documentElement.clientWidth) + "px",
ease: "none",
})
});
I'm struggling to find a way in which I can make the intro fade in, the scroller scroll horizontally, and the .animate elements to fade in, or fade in up.
Edit:
#onkar ruikar, see notes based on your sandbox below:
When you scroll down and the comes into view, I want the initial .animate elements to scroll up into view (currently, once the text fade away, and then the horizontal scroller starts working, only then does the .animate that are suppose to be in view, fade in up
After the initial .animate elements have loaded, the next .animate elements that are part of the scroller, they do not fade in up. They're static. As each .animate element comes into view, then it should fade in up (I think it's currently triggering once, for all the elements).
See visual here:
In the above gif, you can see the first two text blocks are hidden, as soon as they're in view, I want them to fade up. Then the 3rd and 4th text blocks are static, when they should fade up as the user scrolls to that section.

You need to use onUpdate method on the scroll trigger.
onUpdate: self => console.log("progress", self.progress)
Based on the self.progress set opacity, x position etc.
Full demo on codesandbox. Click on "Open Sandbox" button on bottom right to see the code.
if ("scrollRestoration" in history) {
history.scrollRestoration = "manual";
}
$(function() {
let container = document.querySelector(".horizontalScroller__items");
let elements = gsap.utils.toArray(
document.querySelectorAll(".animate")
);
let intro = document.querySelector(".horizontalScroller__intro");
let svg = document.querySelector("svg");
let animDone = false;
window.scrollPercent = -1;
var scrollTween = gsap.to(container, {
ease: "none",
scrollTrigger: {
trigger: ".horizontalScroller",
pin: ".horizontalScroller",
anticipatePin: 1,
scrub: true,
invalidateOnRefresh: true,
refreshPriority: 1,
end: "+=600%",
markers: true,
onEnter: (self) => {
moveAnimate();
},
onLeaveBack: (self) => {
resetAnimate();
},
onUpdate: (self) => {
let p = self.progress;
if (p <= 0.25) {
let op = 1 - p / 0.23;
intro.style.opacity = op;
animDone = false;
}
if (p > 0.23) {
moveAnimate();
// we do not want to shift the svg by 100% to left
// want to shift it only by 100% - browser width
let scrollPercent =
(1 - window.innerWidth / svg.scrollWidth) * 100;
let shift = ((p - 0.22) * scrollPercent) / 0.78;
gsap.to(svg, {
xPercent: -shift
});
}
}
}
});
function resetAnimate() {
gsap.set(".animate", {
y: 150,
opacity: 0
});
}
resetAnimate();
function moveAnimate() {
for (let e of elements) {
if (ScrollTrigger.isInViewport(e, 0.4, true))
gsap.to(e, {
y: 0,
opacity: 1,
duration: 2
});
}
}
});
You need to set opacity 0 on .animate elements in CSS. And use end: '+=400%' instead of 4000px. Relative dimensions can be used in position based calculations easily.

Related

JS/GSAP solution for infinite animation

I am trying to create a infinite star rain animation, all stars are SVG's.
I tried this to create the animation:
(function($) {
TweenMax.set(".astar", {
x:function(i) {
return i * 50;
}
});
TweenMax.to(".astar", 5, {
ease: Linear.easeNone,
x: "+=500", //move each box 500px to right
modifiers: {
x: function(x) {
return x % 500; //force x value to be between 0 and 500 using modulus
}
},
repeat: -1
});
})(jQuery);
The repeat process is not smooth as you can see on this Codepen:
https://codepen.io/daniellwdb/pen/NXogoB
Is there any JS or GSAP solution to make the animation smooth so that it will look like stars keep spawning from the left and move to the right?
With your current setup, I think the easiest way to pull this off would be to duplicate your starfield so that the beginning of your next loop is identical to the end of your first one. Let's say this is your starfield SVG:
|...o.|
|o....|
|..o..|
Your new "duplicated" starfield would essentially be:
|...o.|...o.|
|o....|o....|
|..o..|..o..|
So when you move that duplicated image from left to right 100%, what you see in the last "frame" is identical to what it will return to when it loops.
Here's a fiddle that shows this concept in action: https://jsfiddle.net/yarp4oLs/5/
I have two identical starfield images that are 200x200 (so 400x200 when side-by-side) and they are displayed in a "viewport" container that is 200x200. Then I just slide them to the left 200px and repeat. Instant stars!

How to animate nested content when animated parent slide moves into viewport, not using scroll

I'm looking to use javascript to animate the content of a nested DIV within an parent slide when the parent slide moves into the viewport.
At the moment, the content in the nested DIV only animates once a scroll command is also triggered after the parent slide moves onto the screen. I believe this is because the slide motion is animated and not scroll controlled.
The same issue is at play in this JSFiddle demo I created to explore the issue:
http://jsfiddle.net/9dz3ubL1/
(The animated movement of the slide from right to left in this demo has been created to test for this problem, to replicate the motion of the slide without scrolling; it is not actually a feature of the development proper).
My question is, how can I script for the animations to be triggered for each nested DIV, when each slide element moves into the viewport, without requiring a scroll function?
Thanks for any help. Here's the script I'm using to control opacity and other CSS stylings.
$(document).ready(function() {
/* Every time the window is scrolled ... */
$(window).scroll(function() {
/* Reveal hidden_header delayed */
$('.hidden_header').each(function(i) {
var center_of_object = $(this).offset().left + $(this).outerWidth();
var center_of_window = $(window).scrollLeft() + $(window).width();
/* If the object is completely visible in the window, fade it it */
if (center_of_window > center_of_object) {
$(this).animate({
'opacity': '1'
}, 500);
$(this).animate({
'right': '0'
}, 1500);
}
});
/* Reveal hidden_content delayed */
$('.hidden_content').each(function(i) {
var center_of_object = $(this).offset().left + $(this).outerWidth();
var center_of_window = $(window).scrollLeft() + $(window).width();
/* If the object is completely visible in the window, fade it it */
if (center_of_window > center_of_object) {
$(this).animate({
'opacity': '1'
}, 3000);
$(this).animate({
'bottom': '0'
}, 3500);
}
});
/* Reveal button delayed */
$('.button').each(function(i) {
var center_of_object = $(this).offset().left + $(this).outerWidth();
var center_of_window = $(window).scrollLeft() + $(window).width();
/* If the object is completely visible in the window, fade it it */
if (center_of_window > center_of_object) {
$(this).animate({
'opacity': '1'
}, 5000);
}
});
});
});
If your slide motion is animated fully (not incremental as it is in the jsfiddle you linked) then jQuery provides you with the ability to perform an action after your animation is complete.
http://api.jquery.com/animate/
Look at the options you can use for the animation function. One of them is called done. You can assign a function to the done option and that function will be called when your animation is complete.
Using one of your animates as an example, the syntax may look like this:
$(this).animate({
'opacity': '1'
}, {duration: 3000, done: function () {
//animate some stuff here
}};
Note that I just picked a random animation from your code. I'm not sure exactly when you want to perform the animation of the content, but you can use this technique anywhere you use a jQuery animate.
I've used this before to control nested animations in a slideshow format and it has worked very well! I hope this what you wanted.

Vibrating screen on scroll using transform: scale

I would like a zoom out effect for my header, what loads zoomed in, and on scroll it zoom out.
What I do is to increase the size with transform: scale(1.4) and on scroll I calculate a percentage from the scrollTop and header height and I multiply it with 0.4. The problem is that on scroll the screen starts to vibrate, the scale isn't smooth. Do you have any idea what's wrong with my code or can you tell me what's the best practice to achieve this?
jQuery(document).ready(function(){
function zoom_out() {
var page_header_height = jQuery('#page-header-custom').outerHeight();
var scroll_top = jQuery(window).scrollTop();
var zoom_multiplier = 0.4;
var multiplier = (zoom_multiplier*(1-((scroll_top-jQuery('#page-header-custom').offset().top)/page_header_height))) > 1 ? 1 : (zoom_multiplier*(1-((scroll_top-jQuery('#page-header-custom').offset().top)/page_header_height)));
if(multiplier <= 1) {
jQuery('#page-header-inner').stop(true, true).transition({ scale: 1/(1+multiplier), translate: '0, -50%' });
jQuery('#page-header-custom').stop(true, true).transition({
scale: 1+multiplier
});
}
}
zoom_out();
jQuery(window).on('scroll', function(){
zoom_out();
});
});
I created a JSFiddle to see it in action.
I've updated your Fiddle with smooth scaling using window.requestAnimationFrame. The scale animation is vibrating because you're triggering a translation on each scroll event. Think about it like this:
user scrolls
zoom_out() gets triggered and tells an element to transition it's transform properties. Your element is now transitioning at a certain speed: "length" / transitiontime.
More scroll events have passed and are all triggering zoom_out(). The next transition will probably happen at a different speed, resulting in 'vibrating' animation.
First you can get rid of jQuery's transition() method. If you fire the function at 60fps or close to 60fps it will appear to animate smoothly to the human eye, without the need of transitioning or animating.
if(multiplier <= 1) {
//jQuery('#page-header-inner').stop(true, true).transition({ scale: 1/(1+multiplier), translate: '0, -50%' });
//jQuery('#page-header-custom').stop(true, true).transition({ scale: 1+multiplier });
//becomes:
jQuery('#page-header-inner').css({ scale: 1/(1+multiplier), translate: '0, -50%' });
jQuery('#page-header-custom').css({ scale: 1+multiplier });
}
}
Getting the function triggered at ~60fps can be achieved in multiple ways:
Throttle your scroll event to 60fps.
Or use window.requestAnimationFrame like in the updated Fiddle
function zoom_out(){
//calculation code & setting CSS
window.requestAnimationFrame(zoom_out);
}
//trigger it once instead of the scroll event handler
window.requestAnimationFrame(zoom_out);

Cross-dissolve transition for scrolled elements

I am creating a long single page website and using ScrollMagicJS v1.3.0 to trigger events and to make some elements sticky. I would like to create a variety of other transition effects as one scrolls down the page.
Here's a jsfiddle that replicates the horizontal scrolling of my site.
scrollControl = new ScrollMagic({
vertical: false,
});
var myScrollScene = new ScrollScene({
triggerHook: 0,
offset: 0,
triggerElement: '#shot-0-1',
duration: '100vw',
pushFollowers: true
})
.setPin('#shot-0-1')
.addTo(scrollControl);
For instance, I want to create fade-to-black, flare-to-white, and cross-dissolve transitions between pages.
I understand some of the basic principles of HTML5 transitions, how to make one image dissolve into another, but I haven't been able to figure out a clever way to do it using the ScrollMagic scrolling.
Things I've considered: The next page slides under the current page and then transitions from 1.0 to 0 opacity using ScrollMagic triggers?
But how to do it in a way non-hacky and consistent with ScrollMagic's framework?
This has been asked and answered in the ScrollMagic's issues section:
https://github.com/janpaepke/ScrollMagic/issues/269
here's a copy:
A common misconception is that you need to do everything with the ScrollMagic pin functionality.
If the content isn't moving within the scroll flow anyway (it stays in position and is faded out or moved to side or sth. like that) you can have it as "fixed" right from the beginning.
That saves a lot of work and confusion.
The only reason to use ScrollMagic's pinning functionality is when an element should sometimes scroll naturally with the DOM and sometimes it shouldn't.
So if you have elements that are in place and should just be replaced by others, have them fixed the whole time.
Like this: https://jsfiddle.net/janpaepke/6kyd6ss0/1/
If it is indeed a case were you should use ScrollMagic's pinning method, then do the animation inside of a wrapper, that you pin.
Like this: https://jsfiddle.net/janpaepke/6kyd6ss0/3/
 
Here's the solution I settled on.
scrollControl = new ScrollMagic({
vertical: false,
});
vw = $(window).width();
console.log("width:" + vw + "px");
// pin frame 2
var myScrollScene = new ScrollScene({
triggerHook: 0,
triggerElement: '#shot-2',
// This pin is considerably longer than average
offset: 0,
// duration = stickyLength + disolve_duration
duration: 1.5 * vw + 'px'
})
.setPin('#content-2', {
pushFollowers: false
})
.addTo(scrollControl)
.addIndicators({
zindex: 100,
suffix: 'pin2'
});
// move frame 3 up early and pin it
var myScrollScene = new ScrollScene({
triggerHook: 0,
triggerElement: '#shot-2',
offset: 0,
// duartion = 1.5, but why?
duration: 1.5 * vw + 'px'
// the faux pin doesn't actually expand the container the way SM does
// so the results are a little strange
})
.on("start end", function (e) {
$('#content-3').css({left: 0, position:'fixed'});
})
.on("enter leave", function (e) {
$('#content-3').css({left: 0, position:'relative'});
})
.addTo(scrollControl)
.addIndicators({
zindex: 100,
suffix: 'pin3faux'
});
var dissolve = TweenMax.to('#content-2', 1, {
autoAlpha: 0
});
// dissolve frame 2 to frame 3
var myScrollScene = new ScrollScene({
triggerHook: 0,
// Note that though we are fading frame 2, we are
// using the arrival of frame 3 the trigger
triggerElement: '#shot-2',
// The sets the rapidity of the dissolve
// offset = stickyLength
offset: 0.33 * vw + 'px',
// The sets the rapidity of the dissolve
duration: 1 * vw + 'px'
})
.setTween(dissolve)
.addTo(scrollControl)
.addIndicators({
zindex: 100,
suffix: 'dissolve'
});
I used a pushFollowers: false on a pin and z-index to slide the next frame (also pinned) behind the first. Then a Tween to dissolve into the second frame. The result is a nice cinematic dissolve feature with adjustable duration.
Hope it is useful to others.
https://jsfiddle.net/wmodes/b4gdxeLn/

Slide boxes with margin-left check if overslided

I made a simple content/box slider which uses the following javascript:
$('#left').click(function () {
$('#videos').animate({
marginLeft: '-=800px'
}, 500);
});
$('#right').click(function () {
$('#videos').animate({
marginLeft: '+=800px'
}, 500);
});
Here is the demo: http://jsfiddle.net/tjset/2/
What I want to do and I can't figure out how to show and hide arrows(left and right box) as the all the boxes slided.
So I clicked 4 time to the LEFT and slided all the boxes! then hide "left" so that you can't give more -800px
What can I do?
What you can do is check after the animation completes to see if the margin-left property is smaller or larger than the bounds of the video <div>. If it is, depending on which navigation button was clicked, hide the appropriate navigation link.
Check out the code below:
$('#left').click(function () {
// reset the #right navigation button to show
$('#right').show();
$('#videos').animate({
marginLeft: '-=800px'
}, 500, 'linear', function(){
// grab the margin-left property
var mLeft = parseInt($('#videos').css('marginLeft'));
// store the width of the #video div
// invert the number since the margin left is a negative value
var videoWidth = $('#videos').width() * -1;
// if the left margin that is set is less than the videoWidth var,
// hide the #left navigation. Otherwise, keep it shown
if(mLeft < videoWidth){
$('#left').hide();
} else {
$('#left').show();
}
});
});
// do similar things if the right button is clicked
$('#right').click(function () {
$('#left').show();
$('#videos').animate({
marginLeft: '+=800px'
}, 500, 'linear', function(){
var mRight = parseInt($('#videos').css('marginLeft'));
if(mRight > 100){
$('#right').hide();
} else {
$('#right').show();
}
});
});
Check out the jsfiddle:
http://jsfiddle.net/dnVYW/1/
There are many jQuery plugins for this. First determine how many results there are, then determine how many you want visible, then use another variable to keep track with how many are hidden to the left and how many are hidden to the right. So...
var total = TOTAL_RESULTS;
var leftScrolled = 0;
var rightScrolled = total - 3; // minus 3, since you want 3 displayed at a time.
instead of using marginLeft I would wrap all of these inside of a wrapper and set the positions to absolute. Then animate using "left" property or "right". There's a lot of code required to do this, well not MUCH, but since there are many plugins, I think you'd be better off searching jquery.com for a plugin and look for examples on how to do this. marginLeft is just not the way to go, since it can cause many viewing problems depending on what version of browser you are using.

Categories

Resources