Skip to content

Instantly share code, notes, and snippets.

@sdougbrown
Last active July 5, 2016 16:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sdougbrown/664c992d8e342946de0e948351cbceab to your computer and use it in GitHub Desktop.
Save sdougbrown/664c992d8e342946de0e948351cbceab to your computer and use it in GitHub Desktop.
Sticky Elements By Waypoint (jQuery, requestAnimationFrame) -- scrolljack without jacking scroll
// Sticky
//
// A sticky element that determines its state by another
// element's position in the document.
(function ($, requestAnimationFrame) {
var pluginName = 'sticky';
var pluginDefaults = {
waypoint: '.js-waypoint',
includeWaypointHeight: false,
pastWaypointClass: 'is-stuck'
};
function Plugin(element, options) {
this.element = element;
this.settings = $.extend({}, pluginDefaults, options);
this._defaults = pluginDefaults;
this._name = pluginName;
this.init();
}
$.extend(Plugin.prototype = {
init: function () {
var waypoint = this.settings.waypoint;
this.$win = $(window);
this.$el = $(this.element);
this.$wp = (typeof waypoint === 'string') ? $(waypoint) : waypoint;
this.__state__ = {
waypointPosition: this.getWaypointPosition(),
lastKnownScrollTop: 0,
isPastWaypoint: false,
isStuck: false,
isBusy: false
};
// only proceed if the waypoint is valid
if (this.waypoint() > 0) {
// fire scroll event immediately on init
// this prevents strange behaviour when loading
// partway down the page
this.onScroll();
this.$win.bind('scroll', this.onScroll.bind(this));
}
return this;
},
stick: function () {
this.$el.addClass(this.settings.pastWaypointClass);
this.isStuck(true);
},
unstick: function () {
this.$el.removeClass(this.settings.pastWaypointClass);
this.isStuck(false);
},
waypoint: function (check) {
if (check) {
this.__state__.waypointPosition = this.getWaypointPosition();
}
return this.__state__.waypointPosition;
},
isBusy: function (value) {
if (arguments.length > 0) {
this.__state__.isBusy = Boolean(value);
}
return this.__state__.isBusy;
},
isPast: function (state) {
if (arguments.length > 0) {
this.__state__.isPastWaypoint = Boolean(state);
}
return this.__state__.isPastWaypoint;
},
isStuck: function (state) {
if (arguments.length > 0) {
this.__state__.isStuck = Boolean(state);
}
return this.__state__.isStuck;
},
lastScrollTop: function (value) {
if (arguments.length > 0) {
this.__state__.lastKnownScrollTop = value;
}
return this.__state__.lastKnownScrollTop;
},
getWaypointPosition: function () {
// abort if no waypoint
if (!this.$wp[0]) {
return null;
}
var position = this.$wp.offset().top;
var checkHeight = this.settings.includeWaypointHeight;
return (checkHeight) ? this.$wp.height() + position : position;
},
comparePosition: function () {
var isPastWaypoint = this.isPast(this.lastScrollTop() > this.waypoint());
var isStuck = this.isStuck();
// past the waypoint and not stuck
// -> make sticky
if (isPastWaypoint && !isStuck) {
this.stick();
}
// not past the waypoint, stuck
// -> unstick
if (!isPastWaypoint && isStuck) {
this.unstick();
}
this.isBusy(false);
},
onScroll: function () {
this.lastScrollTop(this.$win.scrollTop());
if (!this.isBusy()) {
requestAnimationFrame(this.comparePosition.bind(this));
}
this.isBusy(true);
}
});
$.fn[pluginName] = function (options) {
return this.each(function () {
if (!$.data(this, pluginName)) {
$.data(this, pluginName,
new Plugin(this, options));
}
});
};
})(window.jQuery, window.requestAnimationFrame);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment