The behaviour you want is doable, but requires some basic understanding of mathematics, data binding, and event listening. What you basically want is to check, for each scroll
event fired on the window
object, that:
- The shorter of the two elements,
#leftcontent
vs #rightcontent
, has its bottom touching the bottom of the viewport
- If yes, the shorter element will have a fix position so that it "sticks" to the bottom of the viewport
- If no, the shorter element will be unfixed
The position fixing can be done by adding a .fixed
class, which tells the browser to use position: fixed; bottom: 0;
on the element. Now the interesting part: how do you know if the element has touched the bottom? The trick is simple:
- You calculate the bottom of the viewport from the top. This is just the viewport height + the viewport scroll from the top of the document.
- You calculate the bottom of the element from the top. This is the element's offset + the element's height.
When the value in point #1 is more than point #2, you know that you have scrolled beyond the bottom of the element. With that in mind, all you need is some logic to determine which of the two element is the shorter one: this is already covered in another StackOverflow question, so we can repurpose that logic for our use.
The last bit is that we only want to store the element's offset once: we use jQuery's .data()
method to store the left + top offsets, and its height.
Here is a code that can be used:
function isBottom(el) {
// Cache element
var $el = $(el);
// Store element's offset and height once only
var elOffsetTop = $el.offset().top;
var elOffsetLeft = $el.offset().left;
var elHeight = $el.height();
if ($el.data('offsetTop') === void 0)
$el.data('offsetTop', elOffsetTop);
if ($el.data('offsetLeft') === void 0)
$el.data('offsetLeft', elOffsetLeft);
if ($el.data('height') === void 0)
$el.data('height', elHeight);
// Check if element is at bottom of viewport
var viewportBottom = $(window).height() + $(window).scrollTop();
var elementBottom = $el.data('offsetTop') + $el.data('height');
return viewportBottom > elementBottom;
}
$(window).on('scroll', function() {
// Get the shorter of the two elements
// From: https://stackoverflow.com/a/13319029/395910
var shortest = [].reduce.call($('#leftcontent, #rightcontent'), function(sml, cur) {
return $(sml).height() < $(cur).height() ? sml : cur;
});
// If element is bottom, add the class 'fixed'
$(shortest)
.toggleClass('fixed', isBottom(shortest))
.css('left', isBottom(shortest) ? $(shortest).data('offsetLeft') : 'auto');
});
Note that we need to assign a left
property, because if the right column is the shorter one, we need to know how far it is originally offset from the left, so that position: fixed
will position it correctly.
The .fixed
class is dead simple:
#header {
// Other styles
// Add z-index so the fixed content does not overlay header
z-index: 1;
}
.fixed {
position: fixed;
bottom: 0;
z-index: 1;
}
Pro-tip: you might want to consider throttling/debouncing your scroll
handler callback, for performance reasons.
See proof-of-concept example below:
for(var i=1000;i--;){
$('#rightcontent').append(i+ ' ');
$('#leftcontent').append("i ");
}
function isBottom(el) {
// Cache element
var $el = $(el);
// Store element's offset and height once only
var elOffsetTop = $el.offset().top;
var elOffsetLeft = $el.offset().left;
var elHeight = $el.height();
if ($el.data('offsetTop') === void 0)
$el.data('offsetTop', elOffsetTop);
if ($el.data('offsetLeft') === void 0)
$el.data('offsetLeft', elOffsetLeft);
if ($el.data('height') === void 0)
$el.data('height', elHeight);
// Check if element is at bottom of viewport
var viewportBottom = $(window).height() + $(window).scrollTop();
var elementBottom = $el.data('offsetTop') + $el.data('height');
return viewportBottom > elementBottom;
}
$(window).on('scroll', function() {
// Get the shorter of the two elements
// From: https://stackoverflow.com/a/13319029/395910
var shortest = [].reduce.call($('#leftcontent, #rightcontent'), function(sml, cur) {
return $(sml).height() < $(cur).height() ? sml : cur;
});
// If element is bottom, add the class 'fixed'
$(shortest)
.toggleClass('fixed', isBottom(shortest))
.css('left', isBottom(shortest) ? $(shortest).data('offsetLeft') : 'auto');
});
@font-face {
font-family: Gill Sans MT;
src: url("Gill Sans MT.ttf");
}
body{
margin-top: 0px;
margin-left: 0px;
margin-right: 0px;
margin-bottom: 0px;
}
#header{
position: fixed;
text-align: center;
font-size: 32px;
background: #f4f4e6;
padding-top: 2px;
min-width: 100%;
min-height: 28px;
z-index: 2;
}
#content{
padding-top: 38px;
text-align: center;
}
#footer{
position: fixed;
text-align: center;
background: #f4f4e6;
min-width: 100%;
font-style: italic;
font-family: Gill Sans MT;
letter-spacing: -1;
}
#rightcontent{
float: right;
max-width: 55%;
padding-bottom: 20px;
}
#leftcontent{
padding-bottom: 20px;
max-width: 45%;
}
.fixed {
position: fixed;
bottom: 0;
z-index: 1;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="header">
Worki do odkurzaczy
</div>
<script>
document.write("<div id=content width=" + document.documentElement.clientWidth + "px height=" + (document.documentElement.clientHeight - 64) + "px style=padding-bottom:20px;min-height:" + (document.documentElement.clientHeight - 52) + "px; data-scroll-offset=28>");
</script>
<div id="rightcontent" name="target">
</div>
<div id="leftcontent">
</div>
<script>
document.write("<div id=footer style=top:" + (document.documentElement.clientHeight - 22) + "px;>");
//IDK WHY ON LOCAL TEST IS HERE -32px
</script>