Banging my head trying to sort out the correct logic for adding simple parallax behavior.
I would like to have a number of elements on a page which start out with their top offset a certain distance (e.g. 300px). Then as you scroll down the page, once the top of the element is revealed it will slowly shift upwards (tied to scroll) until the top of element reaches middle of viewport at which time it's top offset is 0 and it remains in place.
I tried using third party script (Scroll Magic, Stellar, etc), but when I couldn't get it right now I'm trying custom code:
https://jsfiddle.net/louiswalch/5bxz8fku/1/
var $Window = $(window);
var offset_amount = 400;
var window_height = $Window.height();
var window_half = (window_height/2);
var sections = $('SECTION.reveal');
sections.each(function() {
var element = $(this);
// Make sure we always start with the right offset
element.css({top: offset_amount});
$Window.bind('scroll', function() {
var viewport_top = $Window.scrollTop();
var viewport_middle = viewport_top + (window_height/2)
var viewport_bottom = viewport_top + window_height;
var element_top = element.offset().top;
if (element_top > viewport_top && element_top <= viewport_bottom) {
var distance_to_middle = (element_top - viewport_middle);
var amount_to_middle = (distance_to_middle / window_half);
console.log(amount_to_middle);
if (amount_to_middle >= 0) {
element.css({top: (offset_amount * amount_to_middle)+ 'px'});
} else {
// ? Lock to end position ?
}
}
});
});
jsBin demo 1. (margin space effect on both enter and exit)
jsBin demo 2. (preserve 0 margin once touched)
Instead of targeting the section
elements, (create and) target their first child elements,
otherwise you'll create a concurrency mess trying to get the top position but simultaneously modifying it.
Also, you cannot rely on fixed 300px margin (i.e: if window height is less than 500px, you're already missing 100px). That space can vary when the screen height is really small, so you also need to find the idealMarg
value.
var $win = $(window),
$rev = $('.reveal'),
winH2 = 0,
winSt = 0;
function reveal() {
winSt = $win.scrollTop();
winH2 = $win.height()/2;
$rev.each(function(i, el){
var y = el.getBoundingClientRect().top,
toMiddleMax = Math.max(0, y-winH2),
idealMarg = Math.min(300, toMiddleMax),
margMin = Math.min(idealMarg, idealMarg * (toMiddleMax/winH2));
$(">div", this).css({transform: "translateY("+ margMin +"px)"});
});
}
$win.on({"load resize scroll" : reveal});
*{box-sizing:border-box; -webkit-box-sizing:border-box;}
html, body{height:100%; margin:0;}
section > div{
padding: 40px;
min-height: 100vh;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<section>
<div style="background-color:red">1</div>
</section>
<section class="reveal">
<div style="background-color: yellow">2</div>
</section>
<section class="reveal">
<div style="background-color: orange">3</div>
</section>
<section class="reveal">
<div style="background-color: pink">4</div>
</section>
I've used in HTML just a <div>
logically, that has to be the one and only first child of a section
parent.
You're welcome to tweak the above code to make it more performant.