Javascript / jQuery sticky without using css position: fixed - javascript

I'm looking for a Javascript/jQuery plugin for sticky header that will not switch the element's style to position fixed. Usually, I'm working with this one http://stickyjs.com/ and it works fine.
I'm working on a website with jQuery animation and one of my div has a sticky header with width:100%. But when I move it to the left (for example), the width:100% is now based on the window's width and not his container.
So, is there an existing plugin that does the same thing as the others but keep the position: relative and calculate the scrollTop to apply as the margin-top for my sticky header?

EDIT 2021
My answer below is pretty old. I would not recommend to use any JS code for that anymore but simply use CSS with position: sticky; which is much easier to implement, but also much more performant/smooth (note: Internet Explorer doesn't support position: sticky;).
Original answer
So, I solved my problem! Here's my javascript code:
You have to set top:0px as default
function relative_sticky(id, topSpacing){
if(!topSpacing){ var topSpacing = 0; }
var el_top = parseFloat(document.getElementById(id).getBoundingClientRect().top);
el_top = el_top - parseFloat(document.getElementById(id).style.top);
el_top = el_top * (-1);
el_top = el_top + topSpacing;
if(el_top > 0){
document.getElementById(id).style.top = el_top + "px";
} else{
document.getElementById(id).style.top = "0px";
}
}
window.onscroll = function(){
relative_sticky("your_element_id", 10);
}
It's not very smooth in IE, so I add a delay:
var delay = (function(){
var timer = 0;
return function(callback, ms){
clearTimeout (timer);
timer = setTimeout(callback, ms);
};
})();
window.onscroll = function(){
if(BrowserDetect.browser == "Explorer" || BrowserDetect.browser == "Mozilla"){
// or your own way to detect browser
delay(function(){
relative_sticky("admin_users_head", 31);
}, 200);
}
else{
relative_sticky("admin_users_head", 31);
}
}

Related

Scroll Behaviour Smooth Alternative for Safari

I am building a site and want to use some anchor links. On all browsers other than Safari, the scroll-behaviour: smooth; works. I understand that this is not supported on Safari.
I have seen people mention:
import smoothscroll from 'smoothscroll-polyfill';
smoothscroll.polyfill();
However, I am not entirely sure how to implement it.
If someone can explain how to use this, or even a JS function that will give the smooth effect, it would be grately appreciated.
Safari Doesn't support scroll-behaviour: smooth so we need some custom javascript to achieve the same effect.
function SmoothVerticalScrolling(e, time, where) {
var eTop = e.getBoundingClientRect().top;
var eAmt = eTop / 100;
var curTime = 0;
while (curTime <= time) {
window.setTimeout(SVS_B, curTime, eAmt, where);
curTime += time / 100;
}
}
function SVS_B(eAmt, where) {
if(where == "center" || where == "")
window.scrollBy(0, eAmt / 2);
if (where == "top")
window.scrollBy(0, eAmt);
}
SmoothVerticalScrolling(myelement, 275, "center");

Image animate down javascript (no jQuery)

I know how to fix the animation that goes down only when the image is showing up in the window with jQuery, but now I want to do that with JavaScript. Struggling with that. The image must be fluently go down (+50px for 1.6 seconds). Have googling around, but most of them are done with jQuery I suggest and that is not what I want. Furtermore the animation should start when the scrollTop is between 600px and 800px.
function scrollFunction() {
var animate = document.getElementById("picture");
var position = 0;
var top = 0;
var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
if(scrollTop > 600 && scrollTop < 800){
position++;
animate.style.top = position + "50px";
} else {
stop();
}
}
function stop() {
clearTimeout(animate);
}
#picture {
width: 100%;
height: auto;
top: -5px;
position: relative;
display: block;
margin-left: auto;
margin-right: auto;
margin-bottom: 20px;
}
<div class="col-sm-12 col-xs-12">
<h1 class="responsive-h1">Mi<span class="logo-orange"> Pad2</span></h1>
<p class="edition-title above-text-black">Black Edition</p>
<img src="Img/picture.jpg" id="picture"/>
</div>
jsFiddle : https://jsfiddle.net/n1q3fy8w/
Javascript
var imgSlide = document.getElementById('slidedown-image');
var slideDown = setInterval(function() {
var topVal = parseInt(window.getComputedStyle(imgSlide).top, 10);
imgSlide.style.top = (topVal + 1) + "px";
}, 15);
setTimeout(function( ) { clearInterval( slideDown ); }, 1600);
You get the element first, after that you setup a setInterval which will basically move our img downwards, we then also set a setTimeout which after 1600ms remvoes the slideDown interval and the image stops. Your image however may need position: absolute.
The above answer will only work in Chrome, this however should work in all browswers
jsFiddle : https://jsfiddle.net/n1q3fy8w/1/
javascript
var imgSlide = document.getElementById('slidedown-image');
var slideDown = setInterval(function() {
var topVal = parseInt(imgSlide.style.top, 10);
imgSlide.style.top = (topVal + 1) + "px";
}, 15);
setTimeout(function( ) { clearInterval( slideDown ); }, 1600);
Ok so getComputedStyle only works in chrome, so to get this to work on all other browsers, you have to specifically set the css property on the element and not via CSS.
When you use javascript to access an element and change its style like so element.style.bottom = '150px' the .style gets you all of the css values for your inline styles on that element, so any css changes on an element that is done via a .css/.less file you can't access via javascript.
So all the above code does is we set a top: 0 on the element itself and in our code we use imageSlide.style.top instead of chrome's window.getComputedStyle
Have you considered using a CSS transition? if you are changing the value of top you should be able to add transition: top 1.6s in your css (to picture). (Then the vendor prefixed versions when you get it working)

Synchronized scrolling using jQuery?

I am trying to implement synchronized scrolling for two DIV with the following code.
DEMO
$(document).ready(function() {
$("#div1").scroll(function () {
$("#div2").scrollTop($("#div1").scrollTop());
});
$("#div2").scroll(function () {
$("#div1").scrollTop($("#div2").scrollTop());
});
});
#div1 and #div2 is having the very same content but different sizes, say
#div1 {
height : 800px;
width: 600px;
}
#div1 {
height : 400px;
width: 200px;
}
With this code, I am facing two issues.
1) Scrolling is not well synchronized, since the divs are of different sizes. I know, this is because, I am directly setting the scrollTop value. I need to find the percentage of scrolled content and calculate corresponding scrollTop value for the other div. I am not sure, how to find the actual height and current scroll position.
2) This issue is only found in firefox. In firefox, scrolling is not smooth as in other browsers. I think this because the above code is creating a infinite loop of scroll events.
I am not sure, why this is only happening with firefox. Is there any way to find the source of scroll event, so that I can resolve this issue.
Any help would be greatly appreciated.
You can use element.scrollTop / (element.scrollHeight - element.offsetHeight) to get the percentage (it'll be a value between 0 and 1). So you can multiply the other element's (.scrollHeight - .offsetHeight) by this value for proportional scrolling.
To avoid triggering the listeners in a loop you could temporarily unbind the listener, set the scrollTop and rebind again.
var $divs = $('#div1, #div2');
var sync = function(e){
var $other = $divs.not(this).off('scroll'), other = $other.get(0);
var percentage = this.scrollTop / (this.scrollHeight - this.offsetHeight);
other.scrollTop = percentage * (other.scrollHeight - other.offsetHeight);
// Firefox workaround. Rebinding without delay isn't enough.
setTimeout( function(){ $other.on('scroll', sync ); },10);
}
$divs.on( 'scroll', sync);
http://jsfiddle.net/b75KZ/5/
Runs like clockwork (see DEMO)
$(document).ready(function(){
var master = "div1"; // this is id div
var slave = "div2"; // this is other id div
var master_tmp;
var slave_tmp;
var timer;
var sync = function ()
{
if($(this).attr('id') == slave)
{
master_tmp = master;
slave_tmp = slave;
master = slave;
slave = master_tmp;
}
$("#" + slave).unbind("scroll");
var percentage = this.scrollTop / (this.scrollHeight - this.offsetHeight);
var x = percentage * ($("#" + slave).get(0).scrollHeight - $("#" + slave).get(0).offsetHeight);
$("#" + slave).scrollTop(x);
if(typeof(timer) !== 'undefind')
clearTimeout(timer);
timer = setTimeout(function(){ $("#" + slave).scroll(sync) }, 200)
}
$('#' + master + ', #' + slave).scroll(sync);
});
This is what I'm using. Just call the syncScroll(...) function with the two elements you want to synchronize. I found pawel's solution had issues with continuing to slowly scroll after the mouse or trackpad was actually done with the operation.
See working example here.
// Sync up our elements.
syncScroll($('.scroll-elem-1'), $('.scroll-elem-2'));
/***
* Synchronize Scroll
* Synchronizes the vertical scrolling of two elements.
* The elements can have different content heights.
*
* #param $el1 {Object}
* Native DOM element or jQuery selector.
* First element to sync.
* #param $el2 {Object}
* Native DOM element or jQuery selector.
* Second element to sync.
*/
function syncScroll(el1, el2) {
var $el1 = $(el1);
var $el2 = $(el2);
// Lets us know when a scroll is organic
// or forced from the synced element.
var forcedScroll = false;
// Catch our elements' scroll events and
// syncronize the related element.
$el1.scroll(function() { performScroll($el1, $el2); });
$el2.scroll(function() { performScroll($el2, $el1); });
// Perform the scroll of the synced element
// based on the scrolled element.
function performScroll($scrolled, $toScroll) {
if (forcedScroll) return (forcedScroll = false);
var percent = ($scrolled.scrollTop() /
($scrolled[0].scrollHeight - $scrolled.outerHeight())) * 100;
setScrollTopFromPercent($toScroll, percent);
}
// Scroll to a position in the given
// element based on a percent.
function setScrollTopFromPercent($el, percent) {
var scrollTopPos = (percent / 100) *
($el[0].scrollHeight - $el.outerHeight());
forcedScroll = true;
$el.scrollTop(scrollTopPos);
}
}
If the divs are of equal sizes then this code below is a simple way to scroll them synchronously:
scroll_all_blocks: function(e) {
var scrollLeft = $(e.target)[0].scrollLeft;
var len = $('.scroll_class').length;
for (var i = 0; i < len; i++)
{
$('.scroll_class')[i].scrollLeft = scrollLeft;
}
}
Here im using horizontal scroll, but you can use scrollTop here instead. This function is call on scroll event on the div, so the e will have access to the event object.
Secondly, you can simply have the ratio of corresponding sizes of the divs calculated to apply in this line $('.scroll_class')[i].scrollLeft = scrollLeft;
I solved the sync scrolling loop problem by setting the scroll percentage to fixed-point notation: percent.toFixed(0), with 0 as the parameter. This prevents mismatched fractional scrolling heights between the two synced elements, which are constantly trying to "catch up" with each other. This code will let them catch up after at most a single extra step (i.e., the second element may continue to scroll an extra pixel after the user stops scrolling). Not a perfect solution or the most sophisticated, but certainly the simplest I could find.
var left = document.getElementById('left');
var right = document.getElementById('right');
var el2;
var percentage = function(el) { return (el.scrollTop / (el.scrollHeight - el.offsetHeight)) };
function syncScroll(el1) {
el1.getAttribute('id') === 'left' ? el2 = right : el2 = left;
el2.scrollTo( 0, (percentage(el1) * (el2.scrollHeight - el2.offsetHeight)).toFixed(0) ); // toFixed(0) prevents scrolling feedback loop
}
document.getElementById('left').addEventListener('scroll',function() {
syncScroll(this);
});
document.getElementById('right').addEventListener('scroll',function() {
syncScroll(this);
});
I like pawel's clean solution but it lacks something I need and has a strange scrolling bug where it continues to scroll and my plugin will work on multiple containers not just two.
http://www.xtf.dk/2015/12/jquery-plugin-synchronize-scroll.html
Example & demo: http://trunk.xtf.dk/Project/ScrollSync/
Plugin: http://trunk.xtf.dk/Project/ScrollSync/jquery.scrollSync.js
$('.scrollable').scrollSync();
If you don't want proportional scrolling, but rather to scroll an equal amount of pixels on each field, you could add the value of change to the current value of the field you're binding the scroll-event to.
Let's say that #left is the small field, and #right is the bigger field.
var oldRst = 0;
$('#right').on('scroll', function () {
l = $('#left');
var lst = l.scrollTop();
var rst = $(this).scrollTop();
l.scrollTop(lst+(rst-oldRst)); // <-- like this
oldRst = rst;
});
https://jsfiddle.net/vuvgc0a8/1/
By adding the value of change, and not just setting it equal to #right's scrollTop(), you can scroll up or down in the small field, regardless of its scrollTop() being less than the bigger field. An example of this is a user page on Facebook.
This is what I needed when I came here, so I thought I'd share.
From the pawel solution (first answer).
For the horizzontal synchronized scrolling using jQuery this is the solution:
var $divs = $('#div1, #div2'); //only 2 divs
var sync = function(e){
var $other = $divs.not(this).off('scroll');
var other = $other.get(0);
var percentage = this.scrollLeft / (this.scrollWidth - this.offsetWidth);
other.scrollLeft = percentage * (other.scrollWidth - other.offsetWidth);
setTimeout( function(){ $other.on('scroll', sync ); },10);
}
$divs.on('scroll', sync);
JSFiddle
An other solution for multiple horizontally synchronized divs is this, but it works for divs with same width.
var $divs = $('#div1, #div2, #div3'); //multiple divs
var sync = function (e) {
var me = $(this);
var $other = $divs.not(me).off('scroll');
$divs.not(me).each(function (index) {
$(this).scrollLeft(me.scrollLeft());
});
setTimeout(function () {
$other.on('scroll', sync);
}, 10);
}
$divs.on('scroll', sync);
NB: Only for divs with same width
JSFiddle

how to build gmail like menu header

how can i build fixed menu like gmail menu. i have tried css, but the div stays in middle, it doesnt come up like the gmail menu does on scroll.
open in large image
i have tried using css property, following is some example code (not real code):
.menu {
position:fixed;
height: 36px;
background-color:#fff;
}
You need to use javascript to check the scrollTop and set the position of your menu to fixed if if the scrollTop is more than the height of your header.
function getScrollTop() {
if(typeof pageYOffset!= 'undefined') {
//most browsers
return pageYOffset;
}
else {
var b = document.body; //IE 'quirks'
var d = document.documentElement; //IE with doctype
d = (d.clientHeight) ? d : b;
return d.scrollTop;
}
}
function onScroll() {
var menu = document.getElementById('divMyMenu');
var headerAndNavHeight = document.getElementById('divHeader').clientHeight
+ document.getElementById('tsMain').clientHeight;
if (getScrollTop() < headerAndNavHeight) {
menu.style.top = headerAndNavHeight + 'px';
menu.style.position = 'absolute';
}
else {
menu.style.top = '0px';
menu.style.position = 'fixed';
}
}
A good and easy to use jQuery Plugin for this is Waypoints
Here you can see a working example:
http://imakewebthings.github.com/jquery-waypoints/sticky-elements/
Position fixed alone is not enough to achieve this effect. Also, position:fixed does not work in IE7 or below, so you'll probably want to have fallback.
You need to use javascript (jQuery makes it easy) to dynamically change the position of the element based upon how far scrolled down the page you are.
Look into .scrollTop()
http://api.jquery.com/scrollTop/
var scrollTop = $(window).scrollTop();
May be this is what you are looking for
http://blog.geotitles.com/2011/10/creating-top-fixed-menu-bar-with-css3-buttons-found-in-gmail/
Here is a very simple trick to implement your requirement explained with example and a link to download.
http://itswadesh.wordpress.com/2012/02/24/google-like-top-bar-with-drop-down-menu-using-html-css-and-jquery/

scroll then snap to top

Just wondering if anyone has an idea as to how I might re-create a nav bar style that I saw a while ago, I just found the site I saw it on, but am not sure how they might have gotten there. Basically want it to scroll with the page then lock to the top...
http://lesscss.org/
Just do a quick "view source" on http://lesscss.org/ and you'll see this:
window.onscroll = function () {
if (!docked && (menu.offsetTop - scrollTop() < 0)) {
menu.style.top = 0;
menu.style.position = 'fixed';
menu.className = 'docked';
docked = true;
} else if (docked && scrollTop() <= init) {
menu.style.position = 'absolute';
menu.style.top = init + 'px';
menu.className = menu.className.replace('docked', '');
docked = false;
}
};
They're binding to the onscroll event for the window, this event is triggered when the window scrolls. The docked flag is set to true when the menu is "locked" to the top of the page, the menu is set to position:fixed at the same time that that flag is set to true. The rest is just some simple "are we about to scroll the menu off the page" and "are we about back where we started" position checking logic.
You have to be careful with onscroll events though, they can fire a lot in rapid succession so your handler needs to be pretty quick and should precompute as much as possible.
In jQuery, it would look pretty much the same:
$(window).scroll(function() {
// Pretty much the same as what's on lesscss.org
});
You see this sort of thing quite often with the "floating almost fixed position vertical toolbar" things such as those on cracked.com.
mu is too short answer is working, I'm just posting this to give you the jquery script!
var docked = false;
var menu = $('#menu');
var init = menu.offset().top;
$(window).scroll(function()
{
if (!docked && (menu.offset().top - $("body").scrollTop() < 0))
{
menu.css({
position : "fixed",
top: 0,
});
docked = true;
}
else if(docked && $("body").scrollTop() <= init)
{
menu.css({
position : "absolute",
top: init + 'px',
});
docked = false;
}
});
Mu's answer got me far. I tried my luck with replicationg lesscss.org's approach but ran into issues on browser resizing and zooming. Took me a while to find out how to react to that properly and how to reset the initial position (init) without jQuery or any other library.
Find a preview on JSFiddle: http://jsfiddle.net/ctietze/zeasg/
So here's the plain JavaScript code in detail, just in case JSFiddle refuses to work.
Reusable scroll-then-snap menu class
Here's a reusable version. I put the scrolling checks into a class because the helper methods involved cluttered my main namespace:
var windowScrollTop = function () {
return window.pageYOffset;
};
var Menu = (function (scrollOffset) {
var Menu = function () {
this.element = document.getElementById('nav');
this.docked = false;
this.initialOffsetTop = 0;
this.resetInitialOffsetTop();
}
Menu.prototype = {
offsetTop: function () {
return this.element.offsetTop;
},
resetInitialOffsetTop: function () {
this.initialOffsetTop = this.offsetTop();
},
dock: function () {
this.element.className = 'docked';
this.docked = true;
},
undock: function () {
this.element.className = this.element.className.replace('docked', '');
this.docked = false;
},
toggleDock: function () {
if (this.docked === false && (this.offsetTop() - scrollOffset() < 0)) {
this.dock();
} else if (this.docked === true && (scrollOffset() <= this.initialOffsetTop)) {
this.undock();
}
}
};
return Menu;
})(windowScrollTop);
var menu = new Menu();
window.onscroll = function () {
menu.toggleDock();
};
Handle zoom/page resize events
var updateMenuTop = function () {
// Shortly dock to reset the initial Y-offset
menu.undock();
menu.resetInitialOffsetTop();
// If appropriate, undock again based on the new value
menu.toggleDock();
};
var zoomListeners = [updateMenuTop];
(function(){
var w = window,
d = document,
e = d.documentElement,
g = d.getElementsByTagName('body')[0];
var lastWidth = 0;
function pollZoomFireEvent() {
var widthNow = w.innerWidth || e.clientWidth || g.clientWidth;
if (lastWidth == widthNow) {
return;
}
lastWidth = widthNow;
// Length changed, user must have zoomed, invoke listeners.
for (i = zoomListeners.length - 1; i >= 0; --i) {
zoomListeners[i]();
}
}
setInterval(pollZoomFireEvent, 100);
})();
Sounds like an application of Jquery ScrollTop and some manipulation of CSS properties of the navbar element. So for example, under certain scroll conditions the navbar element is changed from absolute positioning with calculated co-ordinates to fixed positioning.
http://api.jquery.com/scrollTop/
The effect you describe would usually start with some type of animation, like in TheDeveloper's answer. Default animations typically slide an element around by changing its position over time or fade an element in/out by changing its opacity, etc.
Getting the "bouce back" or "snap to" effect usually involves easing. All major frameworks have some form of easing available. It's all about personal preference; you can't really go wrong with any of them.
jQuery has easing plugins that you could use with the .animate() function, or you can use jQueryUI.
MooTools has easing built in to the FX class of the core library.
Yahoo's YUI also has easing built in.
If you can remember what site it was, you could always visit it again and take a look at their source to see what framework and effect was used.

Categories

Resources