The reason you see the jump is that you simply divide the current window scroll position once the #next-prev
comes into view by 3 and set that as a top value for #scroll-up
... the window scroll when you reach #next-prev
will be larger than 0, hence the scroll divided by three will also be larger than zero, and #scroll-up
will immediately get a (large) negative top
value.
I don't know where you got the division by three from - that would create a slower scrolling of the element (aka. parallax effect) - which means that even if there was no jump and #scroll-up
started to scroll at the right point, it would still cover two thirds of #next-prev
once #next-prev
was fully in view... So I don't think you actually want the parallax effect...
What I think you want is:
The top
value of #scroll-up
needs to be zero until the first pixel of #next-prev
scrolls into view, and then grow gradually as #next-prev
comes "more" into view, making sure it stays exactly above #next-prev
... ie. the top of #scroll-up
needs to be 0 minus the number of pixels #next-prev
's top is from the bottom of the viewport - and of course always zero if #next-prev
is still completely below the viewport.
Here's a way to achieve that...
(function($) {
$(window).scroll(function() {
//Get the footer position relative to the document
var footer_offset = $("#footer").position().top;
//Get the window height (viewport height)
var window_height = $(window).height();
//Get the current scroll position
var window_scroll = $(window).scrollTop();
//Calculate the bottom of the window relative to the document
var window_bottom_offset = window_scroll + window_height;
//Calculate "how much" of the footer is above the bottom of the window
var footer_visible_pixels = window_bottom_offset - footer_offset;
//Default #semistuck's position to top: 0px
var semistuck_top = 0;
//But if the footer is above the bottom of the screen...
if (footer_visible_pixels > 0) {
//...move #semistuck up by the same amount of pixels
semistuck_top = -footer_visible_pixels;
}
//Actually move it
$("#semistuck").css('top', semistuck_top + 'px');
//Just to see each step of the calculation
console.log(footer_offset, window_height, window_scroll, window_bottom_offset, footer_visible_pixels, semistuck_top);
});
}(jQuery));
body,
html {
margin: 0;
}
#semistuck,
#loooooong,
#footer {
margin: 0;
padding: 4em 1em;
text-align: center;
box-sizing: border-box;
}
#semistuck {
position: fixed;
width: 50%;
height: 100%;
top: 0;
left: 0;
background: #afa;
}
#loooooong {
height: 2000px;
margin-left: 50%;
width: 50%;
background: repeating-linear-gradient(45deg, #606dbc, #606dbc 100px, #465298 100px, #465298 200px);
}
#footer {
background: #faa;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="semistuck">
This is stuck until the<br>footer scrolls into view
</div>
<div id="loooooong"></div>
<div id="footer">
When this gets into<br>view #semistuck will<br>get out of its way
</div>
Obviously the above code is way more verbose than needed, but I felt that very small increments in the calculation makes it clearer what happens....
Here's the same thing in a shorter form:
var footer_visible_pixels = $(window).scrollTop() + $(window).height() - $("#footer").position().top;
$("#semistuck").css('top', (footer_visible_pixels > 0 ? -footer_visible_pixels : 0) + 'px');
Hmm...
...come to think of it you may actually want the parallax effect, but just want the footer to be on top, so here's a slightly modified version which gives a somewhat cool effect:
(function($) {
$(window).scroll(function() {
//Get the footer position relative to the document
var footer_offset = $("#footer").position().top;
//Get the window height (viewport height)
var window_height = $(window).height();
//Get the current scroll position
var window_scroll = $(window).scrollTop();
//Calculate the bottom of the window relative to the document
var window_bottom_offset = window_scroll + window_height;
//Calculate "how much" of the footer is above the bottom of the window
var footer_visible_pixels = window_bottom_offset - footer_offset;
//Default #semistuck's position to top: 0px
var semistuck_top = 0;
//But if the footer is above the bottom of the screen...
if (footer_visible_pixels > 0) {
//...move #semistuck up by the same amount of pixels
semistuck_top = -footer_visible_pixels/3;
}
//Actually move it
$("#semistuck").css('top', semistuck_top + 'px');
//Just to see each step of the calculation
console.log(footer_offset, window_height, window_scroll, window_bottom_offset, footer_visible_pixels, semistuck_top);
});
}(jQuery));
body,
html {
margin: 0;
}
#semistuck,
#loooooong,
#footer {
margin: 0;
padding: 4em 1em;
text-align: center;
box-sizing: border-box;
}
#semistuck {
position: fixed;
width: 50%;
height: 100%;
top: 0;
left: 0;
background: #afa;
}
#loooooong {
height: 2000px;
margin-left: 50%;
width: 50%;
background: repeating-linear-gradient(45deg, #606dbc, #606dbc 100px, #465298 100px, #465298 200px);
}
#footer {
background: #faa;
position: relative;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="semistuck">
This is stuck until the<br>footer scrolls into view
</div>
<div id="loooooong"></div>
<div id="footer">
When this gets into<br>view #semistuck will<br>get out of its way
</div>
Only two things are changed compared to the original suggestion:
- I've added
position: relative
to the footer
- I divide the negative
top
of #semistuck
by three
Note
None of this will work on iPhones, iPads, and probably most other touch devices. They simply don't fire any scroll events during scroll, but only after. So if support for mobile devices is a concern, you should detect those and provide a fallback - the alternative is a very jumpy effect that happens only after scrolling ends. To the best of my knowledge there is no workaround, and depending on the scenario the best way to handle it may be to:
- just degrade to something simpler
- use one of the animated solutions provided in the other answers - but only on devices that do not support continuous scroll events