Vibrating screen on scroll using transform: scale - javascript

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);

Related

Animating child elements in ScrollTrigger GSAP horizontal scroll

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.

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.

Scrolling Opacity Shift To and From Targeted Element

I'm trying to get an overlay div's opacity to fade to black as you approach a targeted element in the middle of the page, and then fade back to transparent after that element exits the viewport.
(Broken) Example: https://jsfiddle.net/dtcgbxcn/3/
As you approach the 'blue' section, it should get darker. The page should be solid black before the blue section enters the viewport. Then, after the blue section exits the viewport, it begins to gradually fade out the opacity. By the time you reach the bottom of the page (or another targeted element), the overlay should be fully transparent again.
Note that, due to responsiveness, the height of any of these sections is indeterminate.
$(window).on('scroll', function() {
var st = $(this).scrollTop(),
offset = $('.blue').offset().top - $('.blue').height(),
opacity = st / offset;
_docHeight = $('.red').height() + $('.blue').height() + $('.yellow').height();
$('.overlay').height(_docHeight);
if (opacity > 2) {
opacity = 3 - opacity;
}
$('.overlay').css('opacity', opacity);
});
I have fiddled around with your example, Hopefully this is what you were looking for as far as functionality. It should be 100 opacity right before the blue appears, and 100% clear as the blue comes off the screen. I would prob warp this whole thing in a closure, and cache the selectors so you don't have to call $() every time, but other than that - this should work.
Your fiddle was a little different than your example above - but let me know if this is what you are looking for.
https://jsfiddle.net/gmydzzmf/1/
$(window).on('scroll', function() {
var st = $(this).scrollTop(),
win_height = $(window).height(),
offset = $('.two').offset().top - $('.two').height() - ( win_height / 2),
_docHeight = $('.one').height() + $('.two').height();
if (st<offset ){
// fading in
opacity = st/offset;
} else {
// fading out
opacity = ((_docHeight - st)/(win_height*2));
}
$('.overlay').height(_docHeight); //move this to resize event
$('.overlay').css('opacity', opacity);
});

Click through div and fade out on mouse over

I have a navbar on the top of the page, and when certain events run, I have a header that pops up for about 3 seconds. During this time you cannot click on the underlying nav links.
.alert-header{
pointer-events: none;
}
I tried doing a css transition, but pointer events are set to none. So I tried with jquery (assuming pointer events only affect css):
$(document).on('mouseenter', '.alert-header', function(){
$(this).animate({opacity: 0.2}, 'fast');
}).on('mouseleave', '.alert-header', function(){
$(this).animate({opacity: 1}, 'fast');
});
So, after doing that and testing it, I get the same result as with doing a css transition.
Is there a way where I can fade out the header to 0.2 opacity when the mouse moves over it, and be able to click on the underlying links?
Okay, I have figured out a way. First in the css I turn off pointer events for the div I want to click through:
.alert-header{
pointer-events: none;
}
Next I test the Y position of the mouse when it moves cursor and when the header is visible, if the Y position is less than the height of the header I make it fade, otherwise I fade it back to opaque.
var fadeRunning = false;
$(document).on('mousemove', function(e){
var header = $('.alert-header');
if(header.is(':visible')){
var mouseY = e.pageY;
var height = header.outerHeight();
if(mouseY <= height && !fadeRunning){
fadeRunning = true;
header.animate({opacity: 0.2}, 'fast', function(){fadeRunning=false});
}else{
if(!fadeRunning){
fadeRunning = true;
header.animate({opacity: 1}, 'fast', function(){fadeRunning=false});
}
}
}else{
header.css({opacity: 1});
}
});

Categories

Resources