")).css("height",v()+"px"),o.after(P));var t=M.getBoundingClientRect(),e={width:p()+"px",position:"fixed",left:t.left+"px","z-index":j?j.get(O).zIndex-(U.zIndex||0):null};e["margin-"+R]=0,o.css(e)}function k(){H=!1,o.removeClass(N),o.attr("style",q.style),P&&P.remove()}function p(){return M.offsetWidth}function v(){return M.offsetHeight}function S(t){var e=0;if(t&&t.offsetParent)do{e+=t.offsetTop,t=t.offsetParent}while(t);return e}function b(t){return S(t)+t.clientHeight}function w(t){var e=0;if("top"===t&&U.top>0&&(e+=U.top),"bottom"===t&&U.bottom>0&&(e+=U.bottom),j){var n=j.index(O);"top"===t&&n>0&&j.range(0,n).forEach(function(n){n.isSticky()&&(e+=n.computedHeight(t))}),"bottom"===t&&n!==j.length()-1&&j.range(n+1,j.length()).forEach(function(n){n.isSticky()&&(e+=n.computedHeight(t))})}return e}function x(){return w("top")}function C(){return w("bottom")}function E(t){if(null===F&&(F=z()),F){var e=!(null===t||void 0===t),n=F.getBoundingClientRect(),o=e?S(F)+n.height-t:n.top-window.innerHeight+v();return Math.max(0,o+Q+L-w(R))}return 0}function $(t){if(null===F&&(F=z()),F){var e=!(null===t||void 0===t),n=F.getBoundingClientRect(),o=e?S(F)+n.height-t:n.bottom;return Math.max(0,Q+w(R)+v()+L-o)}return 0}function z(){var t,e=!1;return angular.isDefined(i.container)&&(angular.isString(i.container)?(-1===(t=i.container).indexOf(".")&&-1===t.indexOf("#")&&(t="#"+t),e=angular.element(B.querySelector(t))[0]):e=i.container),e}i=i||{};var I,D,P,H=!1,T=t[0].body,M=o[0],B=t[0].documentElement;angular.isFunction(i.event)||delete i.event,angular.forEach(a,function(t,e){angular.isUndefined(i[e])||""===i[e]?i[e]=t:i[e]&&!isNaN(i[e])&&(i[e]*=1)});var O=i.id,A=i.mediaQuery,N=i.stickyClass,Q=(i.usePlaceholder,i.offsetTop),L=i.offsetBottom,R=i.anchor.toLowerCase().trim(),Y=i.event,j=!1===i.stack?null:i.stack||n({zIndex:i.zIndex}),F=null,U={top:0,bottom:0},q={style:o.attr("style")||""};o.addClass("sticky-"+R);var W={};if(j){var G=j.add(O,W);O=G.id}return W.draw=function(t){var e=(t=t||{}).offset;e&&(U.top=e.top||0,U.bottom=e.bottom||0,U.zIndex=e.zIndex),!0===t.force&&k(),m()},W.anchor=function(){return R},W.isSticky=function(){return u()&&H||i.alwaysSticky},W.isEnabled=u,W.computedHeight=function(t,e){return"top"===t?Math.max(0,v()-$(e)+Q):"bottom"===t?Math.max(0,v()-E(e)+L):0},W.sticksAtPosition=s,W.destroy=function(){k(),j&&j.remove(O)},W}}]).constant("StickyStackDefaults",{id:null,enable:!0,mediaQuery:!1,stickyClass:"is-sticky",beforeStickyClass:"sticky-before",afterStickyClass:"sticky-after",usePlaceholder:!0,offsetTop:0,offsetBottom:0,anchor:"top",container:null,event:angular.noop,stack:null,defaultStack:"default-stack",collection:null,collectionParent:null,zIndex:1039}).provider("hlStickyElementCollection",function(){var t=0,e={collections:{},defaults:{checkDelay:20},elementsDefaults:{},$get:["$rootScope","$window","$document","$log","StickyStackDefaults","hlStickyElement","hlStickyStack","throttle",function(n,o,r,a,i,c,l,u){function s(){t++,h(),t>1||(k=u(h,e.defaults.checkDelay),p.on("resize",k),p.on("scroll",d),g=n.$on("$viewContentLoaded",k),y=n.$on("$includeContentLoaded",k),k())}function f(){--t>0||(p.off("resize",k),p.off("scroll",d),g(),y())}function d(){m()}function h(){m({force:!0})}function m(t){angular.forEach(e.collections,function(e){e.draw(t)})}var g,y,k,p=angular.element(o);return function(t){t&&angular.isObject(t)||(a.warn("Must supply an options object"),t={});var n=(t=angular.extend({},e.elementsDefaults,t)).name||i.defaultStack;if(e.collections[n])return e.collections[n];var o=l({name:n,zIndex:t.zIndex}),r=[],u={};return u.addElement=function(t,e){(e=e||{}).stack=o;var n=c(t,e);return r.push({stickyElement:n,element:t}),n},u.removeElement=function(t){for(var e,n=r.length;n--;)if(angular.isString(t)&&"#"+r[n].element.id===t||r[n].element===t){e=n;break}var o=r.splice(e,1)[0];return o&&o.stickyElement.destroy(),o},u.draw=function(e){var n={};if(t.parent){var o=l({name:t.parent,zIndex:t.zIndex});n.offset={top:o.heightCurrent("top"),zIndex:o.length()}}angular.extend(n,e||{}),angular.forEach(r,function(t){t.stickyElement.draw(n)})},u.destroy=function(){angular.forEach(angular.copy(r),function(t){u.removeElement(t)}),delete e.collections[n],f()},u.trackedElements=function(){return r},e.collections[n]=u,s(),u}}]};return e}).directive("hlSticky",["$log","$window","$document","hlStickyElementCollection",function(t,e,n,o){return{restrict:"A",scope:{container:"@",anchor:"@",stickyClass:"@",mediaQuery:"@",collection:"@",collectionParent:"@",offsetTop:"@",offsetBottom:"@",zIndex:"@",event:"&",usePlaceholder:"=?",enable:"=?",alwaysSticky:"=?",options:"=?"},link:function(t,e,n){e.addClass("hl-sticky");var r;t.options?r=t.options:(r={id:n.hlSticky,event:function(){var e=t.event();angular.isFunction(e)&&e.apply(null,arguments)}},angular.forEach(t,function(e,n){"event"!==n&&"$"!==n[0]&&angular.isDefined(e)&&(r[n]=t[n],r[n]&&!isNaN(r[n])&&(r[n]*=1))}));var a=o({name:r.collection,parent:r.collectionParent,zIndex:r.zIndex});a.addElement(e,r),t.$watch("options",function(t,e){t&&t!==e&&a.draw({force:!0})},!0),t.$watch("enable",function(e,n){e!==n&&(r.enable=t.enable,a.draw({force:!0}))}),t.$watch("alwaysSticky",function(e,n){e!==n&&(r.alwaysSticky=t.alwaysSticky,a.draw({force:!0}))}),t.$on("$destroy",function(){a.removeElement(e),a.trackedElements().length||a.destroy()})}}}]).factory("throttle",["$timeout",function(t){return function(e,n,o){var r=null;return o=o||{},function(){var o=this,a=arguments;t.cancel(r),r=t(function(){e.apply(o,a)},n,!1)}}}]);
\ No newline at end of file
diff --git a/js/angular-sticky.js b/js/angular-sticky.js
index abde951..8b3b0f2 100755
--- a/js/angular-sticky.js
+++ b/js/angular-sticky.js
@@ -116,6 +116,14 @@ angular.module('hl.sticky', [])
};
for (var i = 0; i < stack.length; i++) {
stick = stack[i];
+
+ if (i === 0) {
+ //first item in the stack, need to get scroll position for that item to start
+
+ }
+
+
+
// check if the sticky element sticks at the queried position minus 1 pixel if the position is at the same place
if (stick.sticksAtPosition(anchor, atAdjusted)) {
var stickyAnchor = stick.anchor();
@@ -139,7 +147,7 @@ angular.module('hl.sticky', [])
return stickyStack;
})
- .factory('hlStickyElement', function($document, $log, hlStickyStack, throttle, mediaQuery, StickyStackDefaults) {
+ .factory('hlStickyElement', function($document, $window, $log, hlStickyStack, throttle, mediaQuery, StickyStackDefaults) {
return function(element, options) {
options = options || {};
@@ -174,11 +182,13 @@ angular.module('hl.sticky', [])
var offsetTop = options.offsetTop;
var offsetBottom = options.offsetBottom;
var anchor = options.anchor.toLowerCase().trim();
+ var absoluteOffset = 0;
var event = options.event;
var stack = options.stack === false ? null : options.stack || hlStickyStack({zIndex:options.zIndex});
var container = null;
+ var scrollerContainer = null;
var globalOffset = {
top: 0,
bottom: 0
@@ -231,13 +241,15 @@ angular.module('hl.sticky', [])
return false;
}
function sticksAtPositionTop(scrolledDistance) {
- scrolledDistance = scrolledDistance !== undefined ? scrolledDistance : window.pageYOffset || bodyEl.scrollTop;
- var scrollTop = scrolledDistance - (documentEl.clientTop || 0);
+ scrolledDistance = scrolledDistance !== undefined ? scrolledDistance : getPageScrolled();
+ var scrollTop = scrolledDistance + ( Math.max(getScrollerOffset(), 0) + getScrollerScrolled());
+ absoluteOffset = scrollTop - stickyLinePositionTop();
return scrollTop >= stickyLinePositionTop();
}
function sticksAtPositionBottom(scrolledDistance) {
- scrolledDistance = scrolledDistance !== undefined ? scrolledDistance : (window.pageYOffset || bodyEl.scrollTop);
- var scrollBottom = scrolledDistance + window.innerHeight;
+ scrolledDistance = scrolledDistance !== undefined ? scrolledDistance : getPageScrolled();
+ var scrollBottom = scrolledDistance + window.innerHeight - Math.max(getScrollerBottomOffset(), 0) + getScrollerScrolled();
+ absoluteOffset = scrollBottom - stickyLinePositionBottom();
return scrollBottom <= stickyLinePositionBottom();
}
function matchesMediaQuery() {
@@ -245,6 +257,7 @@ angular.module('hl.sticky', [])
}
function render() {
+ var offsetCalc = 0;
var shouldStick = sticksAtPosition(anchor);
if (angular.isDefined(options.enable) && !options.enable) {
@@ -273,10 +286,60 @@ angular.module('hl.sticky', [])
if (_isSticking) {
// update the top offset at an already sticking element
if (anchor === 'top') {
- element.css('top', (offsetTop + _stackOffset(anchor) - containerBoundsBottom()) + 'px');
+
+ if (options.useAbsolutePosition) {
+ if (containerBoundsBottom() <= 0) {
+ offsetCalc = absoluteOffset;
+ element.css({
+ 'transform': "translateY(" + offsetCalc + "px)"
+ });
+ }
+ } else {
+ offsetCalc = offsetTop + _stackOffset(anchor) - containerBoundsBottom() + Math.max(getScrollerOffset(), 0);
+ element.css('top', (offsetCalc) + 'px');
+ }
+
}
else if (anchor === 'bottom') {
- element.css('bottom', (offsetBottom + _stackOffset(anchor) - containerBoundsTop()) + 'px');
+ console.log('el',options.offsetBottom);
+
+ var offsetCalcA = offsetBottom + _stackOffset(anchor) - containerBoundsTop() + Math.max(getScrollerBottomOffset(), 0);
+ console.log('stock:', offsetCalcA);
+
+ var offsetCalcB = absoluteOffset;
+ console.log('cust:', offsetCalcB);
+
+ console.log('stackOffsetT', _stackOffset('top'));
+ console.log('stackOffsetB', _stackOffset('bottom'));
+ console.log('------');
+
+
+
+
+ if (options.useAbsolutePosition) {
+ if (containerBoundsTop() <= 0) {
+ element.css({
+ "transform": ""
+ });
+ var elRect = nativeEl.getBoundingClientRect();
+ var containerRect = container.getBoundingClientRect();
+ var space = containerRect.top - elRect.top + offsetBottom + _stackOffset(anchor) + Math.max(getScrollerBottomOffset(), 0) + getScrollerScrolled();
+ console.log('space', space);
+
+ offsetCalc = space;
+ // scrolledDistance = scrolledDistance !== undefined ? scrolledDistance : getPageScrolled();
+ // var scrollBottom = scrolledDistance + window.innerHeight - Math.max(getScrollerBottomOffset(), 0) + getScrollerScrolled();
+
+ // offsetCalc = absoluteOffset;
+ element.css({
+ 'transform': "translateY(" + offsetCalc + "px)"
+ });
+ }
+ } else {
+ offsetCalc = offsetBottom + _stackOffset(anchor) - containerBoundsTop() + Math.max(getScrollerBottomOffset(), 0);
+ element.css('bottom', (offsetCalc) + 'px');
+ }
+
}
element.css('width', elementWidth() + 'px');
}
@@ -316,27 +379,43 @@ angular.module('hl.sticky', [])
}
+ function stickElementAbsolute() {
+
+ // element.css({
+ // 'position': 'absolute',
+ // 'left': 0,
+ // 'top': getScrollerScrolled()
+ // });
+ }
+
function stickElement() {
_isSticking = true;
-
+ var css = {};
element.addClass(stickyClass);
- // create placeholder to avoid jump
- if (options.usePlaceholder) {
- placeholder = placeholder || angular.element('
');
- placeholder.css('height', elementHeight() + 'px');
- element.after(placeholder);
- }
+ if (options.useAbsolutePosition) {
+ css = {
+ 'z-index': stack ? stack.get(id).zIndex - (globalOffset.zIndex || 0) : null,
+ 'transform': "translateY(" + absoluteOffset + "px)"
+ }
+ } else {
+ // create placeholder to avoid jump
+ if (options.usePlaceholder) {
+ placeholder = placeholder || angular.element('
');
+ placeholder.css('height', elementHeight() + 'px');
+ element.after(placeholder);
+ }
- var rect = nativeEl.getBoundingClientRect();
- var css = {
- 'width': elementWidth() + 'px',
- 'position': 'fixed',
- 'left': rect.left + 'px',
- 'z-index': stack ? stack.get(id).zIndex - (globalOffset.zIndex || 0) : null
- };
+ var rect = nativeEl.getBoundingClientRect();
+ css = {
+ 'width': elementWidth() + 'px',
+ 'position': 'fixed',
+ 'left': rect.left + 'px',
+ 'z-index': stack ? stack.get(id).zIndex - (globalOffset.zIndex || 0) : null,
+ };
- css['margin-' + anchor] = 0;
+ css['margin-' + anchor] = 0;
+ }
element.css(css);
}
function unstickElement() {
@@ -422,32 +501,110 @@ angular.module('hl.sticky', [])
return 0;
}
- // @todo dffgdg
+ // returns a positive pixel count if it's before the item
function containerBoundsTop(scrolledDistance) {
if (container === null) {
- container = options.container !== undefined ? angular.isString(options.container) ? angular.element(documentEl.querySelector('#' + options.container))[0] : options.container : false;
+ container = getContainer();
}
if (container) {
var hasScrollDistance = !(scrolledDistance === null || scrolledDistance === undefined);
var containerRect = container.getBoundingClientRect();
- var containerBottom = !hasScrollDistance ? containerRect.top - window.innerHeight + elementHeight() : (_getTopOffset(container) + containerRect.height) - scrolledDistance;
- return Math.max(0, (containerBottom + offsetTop + offsetBottom) - (_stackOffset(anchor)));
+ var containerBottom = !hasScrollDistance
+ ? containerRect.top - window.innerHeight + elementHeight()
+ : (_getTopOffset(container) + containerRect.height) - scrolledDistance;
+ // return Math.max(0, containerBottom - (offsetTop + _stackOffset(anchor)));
+ return Math.max(0, (containerBottom + offsetTop + _stackOffset(anchor)) - Math.max(getScrollerBottomOffset(), 0) );
}
return 0;
}
function containerBoundsBottom(scrolledDistance) {
if (container === null) {
- container = options.container !== undefined ? angular.isString(options.container) ? angular.element(documentEl.querySelector('#' + options.container))[0] : options.container : false;
+ container = getContainer();
}
if (container) {
var hasScrollDistance = !(scrolledDistance === null || scrolledDistance === undefined);
var containerRect = container.getBoundingClientRect();
- var containerBottom = !hasScrollDistance ? containerRect.bottom : (_getTopOffset(container) + containerRect.height) - scrolledDistance;
- return Math.max(0, (offsetTop + _stackOffset(anchor) + elementHeight() + offsetBottom) - containerBottom);
+ var containerBottom = !hasScrollDistance
+ ? containerRect.bottom
+ : (_getTopOffset(container) + containerRect.height) - scrolledDistance;
+ return Math.max(0, (offsetTop + _stackOffset(anchor) + elementHeight() + offsetBottom) - (containerBottom - Math.max(getScrollerOffset(), 0)) );
}
return 0;
}
+ function getContainer() {
+ var selector, el = false;
+
+ if (angular.isDefined(options.container)) {
+ if (angular.isString(options.container)) {
+ selector = options.container;
+ if (selector.indexOf(".") === -1 && selector.indexOf("#") === -1) {
+ selector = "#" + selector;
+ }
+ el = angular.element(documentEl.querySelector(selector))[0];
+ } else {
+ el = options.container;
+ }
+ }
+ return el;
+ }
+
+ function getPageScrolled() {
+ return window.pageYOffset || documentEl.scrollTop || bodyEl.scrollTop;
+ }
+
+ function getScrollerContainer() {
+ var selector, el = false;
+
+ if (angular.isDefined(options.scrollerContainer)) {
+ if (angular.isFunction(options.scrollerContainer)) {
+
+ } else if (angular.isString(options.scrollerContainer)) {
+ selector = options.scrollerContainer;
+ if (selector.indexOf(".") === -1 && selector.indexOf("#") === -1) {
+ selector = "#" + selector;
+ }
+ el = angular.element(documentEl.querySelector(selector))[0];
+ } else {
+ el = options.scrollerContainer;
+ }
+ }
+ return el;
+ }
+
+ function getScrollerScrolled() {
+ var el, scrolled = 0, pixels = 0;
+
+ el = getScrollerContainer();
+ if (el) {
+ scrolled = el.scrollTop;
+ }
+ return scrolled;
+ }
+
+ function getScrollerOffset() {
+ var el, offset = 0;
+
+ el = getScrollerContainer();
+ if (el) {
+ offset = _getTopOffset(el) - getPageScrolled();
+ }
+
+ return offset;
+ }
+
+ function getScrollerBottomOffset() {
+ var el = false, bottom = 0;
+ el = getScrollerContainer();
+ if (el) {
+ var bound = el.getBoundingClientRect();
+ bottom = getScrollerOffset() + bound.height;
+ bottom = window.innerHeight - bottom;
+ }
+
+ return bottom;
+ }
+
var $api = {};
if (stack) {
@@ -545,7 +702,7 @@ angular.module('hl.sticky', [])
// bind events
throttledResize = throttle(resize, $stickyElement.defaults.checkDelay);
windowEl.on('resize', throttledResize);
- windowEl.on('scroll', drawEvent);
+ $window.addEventListener('scroll', drawEvent, true);
unbindViewContentLoaded = $rootScope.$on('$viewContentLoaded', throttledResize);
unbindIncludeContentLoaded = $rootScope.$on('$includeContentLoaded', throttledResize);
@@ -563,6 +720,7 @@ angular.module('hl.sticky', [])
// unbind events
windowEl.off('resize', throttledResize);
windowEl.off('scroll', drawEvent);
+ $window.removeEventListener('scroll', drawEvent, true);
unbindViewContentLoaded();
unbindIncludeContentLoaded();
}
@@ -679,6 +837,7 @@ angular.module('hl.sticky', [])
restrict: 'A',
scope: {
container: '@',
+ scrollerContainer: '@',
anchor: '@',
stickyClass: '@',
mediaQuery: '@',
@@ -689,6 +848,7 @@ angular.module('hl.sticky', [])
zIndex: '@',
event: '&',
usePlaceholder: '=?',
+ useAbsolutePosition: '=?',
enable: '=?',
alwaysSticky: '=?',
options: '=?'
diff --git a/tests/angular-sticky.spec.js b/tests/angular-sticky.spec.js
index a9d9ab9..052a2d6 100644
--- a/tests/angular-sticky.spec.js
+++ b/tests/angular-sticky.spec.js
@@ -719,10 +719,7 @@ describe('angular-sticky', function() {
it('should handle multiple sticky elements with different values for the option parameter', function() {
var options1 = { enable: true };
var options2 = { enable: true };
- compileSticky(templateMultipleStickyElements, null, [options1, options2]);
-
- var stickyElement1 = angular.element(element[0].querySelector('#sticky1'));
- var sticky1 = hlStickyElement(stickyElement1, options1);
+ compileSticky(templateMultipleStickyElements, options1);
var stickyElement2 = angular.element(element[0].querySelector('#sticky2'));
var sticky2 = hlStickyElement(stickyElement2, options2);
@@ -737,11 +734,11 @@ describe('angular-sticky', function() {
expect(stickyElement2).not.toBeSticky();
options1.enable = false;
- drawAt(40, sticky1);
- expect(stickyElement1).not.toBeSticky();
+ drawAt(40, sticky);
+ expect(stickyElement).not.toBeSticky();
options2.enable = true;
- drawAt(40, sticky2);
+ drawAt(90, sticky2);
expect(stickyElement2).toBeSticky();
});