diff --git a/demo/less/demo.less b/demo/less/demo.less index 574154c..7da7406 100644 --- a/demo/less/demo.less +++ b/demo/less/demo.less @@ -35,6 +35,8 @@ @import "demos/container-bottom/scaffolding"; +@import "demos/container-scrolling/scaffolding"; + @import "demos/sticky-stack/scaffolding"; @import "demos/sticky-stack-bottom/scaffolding"; diff --git a/demo/less/demos/container-scrolling/scaffolding.less b/demo/less/demos/container-scrolling/scaffolding.less new file mode 100644 index 0000000..62818cc --- /dev/null +++ b/demo/less/demos/container-scrolling/scaffolding.less @@ -0,0 +1,33 @@ +.demo-container-scrolling { + + .hl-sticky { + .sticky-element-default(); + } + + .scrolling-container { + height: 500px; + overflow-y: scroll; + + + .box-shadow(inset 0 0 20px 0px fade(@gray-base, 20%)); + + .divider { + .divider-default(400px); + } + .divider-scrolling-small { + .divider-default(200px); + } + .divider-scrolling-big { + .divider-default(600px); + } + + #sticky-container, #sticky-container-bottom { + position: relative; + background-color: @gray-lighter; + .box-shadow(inset 0 0 20px 0px fade(@gray-base, 20%)); + } + + } + +} + diff --git a/demo/less/demos/sticky-stack-bottom/scaffolding.less b/demo/less/demos/sticky-stack-bottom/scaffolding.less index f1ca3f2..3e66cda 100644 --- a/demo/less/demos/sticky-stack-bottom/scaffolding.less +++ b/demo/less/demos/sticky-stack-bottom/scaffolding.less @@ -12,4 +12,8 @@ .divider { .divider-default(); } + + #container-stack-bottom { + .box-shadow(inset 0 0 20px 0px fade(@gray-base, 20%)); + } } diff --git a/demo/less/demos/sticky-stack/scaffolding.less b/demo/less/demos/sticky-stack/scaffolding.less index 0133169..5e49bd6 100644 --- a/demo/less/demos/sticky-stack/scaffolding.less +++ b/demo/less/demos/sticky-stack/scaffolding.less @@ -9,4 +9,8 @@ .divider { .divider-default(); } + + #stack-container { + .box-shadow(inset 0 0 20px 0px fade(@gray-base, 20%)); + } } diff --git a/demo/less/mixins/sticky-elements.less b/demo/less/mixins/sticky-elements.less index 15f577d..eeab95e 100644 --- a/demo/less/mixins/sticky-elements.less +++ b/demo/less/mixins/sticky-elements.less @@ -20,4 +20,5 @@ .sticky-element(@minHeight); .sticky-element-shadow(); background-color: @gray-dark; + opacity: 0.8; } diff --git a/demo/views/demos/container-bottom.html b/demo/views/demos/container-bottom.html index 410380c..069bf77 100644 --- a/demo/views/demos/container-bottom.html +++ b/demo/views/demos/container-bottom.html @@ -27,4 +27,4 @@

Scr - \ No newline at end of file + diff --git a/demo/views/demos/container-scrolling.html b/demo/views/demos/container-scrolling.html new file mode 100644 index 0000000..e92e3a2 --- /dev/null +++ b/demo/views/demos/container-scrolling.html @@ -0,0 +1,63 @@ +
+

Scrolling Container

+

+ Scrolling within a div. + Setting a container is done by adding a scroller-container attribute the id of an element. + It will check how much the scroll-body-container has scrolled rather than the page. +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

Attached to Bottom

+
+
+
Space before the scrolling container.
+
+
+
+
I'm just here to give the sticky elements some space.
+
+ First element. +
+
...
+
...
+
+ I'll be sticky, but only for a short while. Once the grey box in the background disappears if you scroll too far, the sticky element goes with it. +
+
...
+
...
+
+ Three +
+
...
+
...
+
+
+
+
Space after the scrolling container.
+
+
diff --git a/demo/views/demos/sticky-stack.html b/demo/views/demos/sticky-stack.html index c34d912..85d8a28 100644 --- a/demo/views/demos/sticky-stack.html +++ b/demo/views/demos/sticky-stack.html @@ -46,12 +46,14 @@

Service: Sticky stack

options: { anchor: "top", offsetTop: 30, + container: 'stack-container', } }, { description: 'I\'ll append myself to "Element 1"', options: { anchor: "top", + container: 'stack-container', } }, { @@ -59,12 +61,14 @@

Service: Sticky stack

options: { anchor: "top", offsetTop: 30, + container: 'stack-container', } }, { description: 'I\'m the last item in the sticky stack', options: { anchor: "top", + container: 'stack-container', } } ]; @@ -73,7 +77,7 @@

Service: Sticky stack

-
+
Current stack height: {{stackHeightCurrent}}px
diff --git a/demo/views/header.html b/demo/views/header.html index 8dee30a..6e435a5 100644 --- a/demo/views/header.html +++ b/demo/views/header.html @@ -28,6 +28,7 @@
  • Events
  • Container
  • Container bottom
  • +
  • Scrolling Container
  • Sticky stack
  • @@ -43,4 +44,4 @@
    - \ No newline at end of file + diff --git a/dist/angular-sticky.js b/dist/angular-sticky.js index 037250c..da1fe55 100644 --- a/dist/angular-sticky.js +++ b/dist/angular-sticky.js @@ -2,7 +2,7 @@ * angular-sticky-plugin * https://github.com/harm-less/angular-sticky - * Version: 0.5.0 - 2018-10-25 + * Version: 0.5.0 - 2020-01-14 * License: MIT */ 'use strict'; @@ -432,7 +432,7 @@ angular.module('hl.sticky', []) // @todo dffgdg 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); @@ -444,7 +444,7 @@ angular.module('hl.sticky', []) } 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); @@ -455,6 +455,23 @@ angular.module('hl.sticky', []) 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; + } + var $api = {}; if (stack) { diff --git a/dist/angular-sticky.min.js b/dist/angular-sticky.min.js index c675357..5b4202f 100644 --- a/dist/angular-sticky.min.js +++ b/dist/angular-sticky.min.js @@ -2,7 +2,7 @@ * angular-sticky-plugin * https://github.com/harm-less/angular-sticky - * Version: 0.5.0 - 2018-10-25 + * Version: 0.5.0 - 2020-01-14 * License: MIT */ -"use strict";angular.module("hl.sticky",[]).factory("mediaQuery",function(){return{matches:function(t){return t&&(matchMedia("("+t+")").matches||matchMedia(t).matches)}}}).factory("hlStickyStack",["$document","StickyStackDefaults",function(t,e){function n(t){var n=(t=t||{}).name||e.defaultStack;if(r[n])return r[n];var a=t.zIndex||e.zIndex,i=[],c={};return c.options=t,c.stackName=n,c.add=function(t,e){return angular.isString(t)&&""!==t||(t=c.length()),e.id=t,e.zIndex=a,i.push(e),a-=1,e},c.get=function(t){for(var e=0;e=c()}function h(t){return(t=void 0!==t?t:window.pageYOffset||M.scrollTop)+window.innerHeight<=l()}function m(){return!1===Q||r.matches(Q)}function g(){var t=f(Y);angular.isDefined(i.enable)&&!i.enable&&(t=!1),angular.isDefined(i.alwaysSticky)&&i.alwaysSticky&&(t=!0),t&&!T?(k(),j({event:"stick"})):!t&&T&&(p(),j({event:"unstick"})),y(),T&&("top"===Y?o.css("top",L+C(Y)-I()+"px"):"bottom"===Y&&o.css("bottom",R+C(Y)-z()+"px"),o.css("width",v()+"px"))}function y(){var t=null;switch(o.removeClass(i.beforeStickyClass),o.removeClass(i.afterStickyClass),Y){case"top":T?I()>0&&(o.addClass(i.afterStickyClass),t="after"):(o.addClass(i.beforeStickyClass),t="before");break;case"bottom":z()>0?(o.addClass(i.beforeStickyClass),t="before"):T||(o.addClass(i.afterStickyClass),t="after")}"before"!==t&&o.removeClass(i.beforeStickyClass),"after"!==t&&o.removeClass(i.afterStickyClass)}function k(){T=!0,o.addClass(O),i.usePlaceholder&&((H=H||angular.element("
    ")).css("height",S()+"px"),o.after(H));var t=B.getBoundingClientRect(),e={width:v()+"px",position:"fixed",left:t.left+"px","z-index":q?q.get(N).zIndex-(U.zIndex||0):null};e["margin-"+Y]=0,o.css(e)}function p(){T=!1,o.removeClass(O),o.attr("style",W.style),H&&H.remove()}function v(){return B.offsetWidth}function S(){return B.offsetHeight}function b(t){var e=0;if(t&&t.offsetParent)do{e+=t.offsetTop,t=t.offsetParent}while(t);return e}function w(t){return b(t)+t.clientHeight}function C(t){var e=0;if("top"===t&&U.top>0&&(e+=U.top),"bottom"===t&&U.bottom>0&&(e+=U.bottom),q){var n=q.index(N);"top"===t&&n>0&&q.range(0,n).forEach(function(n){n.isSticky()&&(e+=n.computedHeight(t))}),"bottom"===t&&n!==q.length()-1&&q.range(n+1,q.length()).forEach(function(n){n.isSticky()&&(e+=n.computedHeight(t))})}return e}function x(){return C("top")}function E(){return C("bottom")}function $(t,e){return"top"===t?Math.max(0,S()-I(e)+L):"bottom"===t?Math.max(0,S()-z(e)+R):0}function z(t){if(null===F&&(F=void 0!==i.container&&(angular.isString(i.container)?angular.element(A.querySelector("#"+i.container))[0]:i.container)),F){var e=!(null===t||void 0===t),n=F.getBoundingClientRect(),o=e?b(F)+n.height-t:n.top-window.innerHeight+S();return Math.max(0,o+L+R-C(Y))}return 0}function I(t){if(null===F&&(F=void 0!==i.container&&(angular.isString(i.container)?angular.element(A.querySelector("#"+i.container))[0]:i.container)),F){var e=!(null===t||void 0===t),n=F.getBoundingClientRect(),o=e?b(F)+n.height-t:n.bottom;return Math.max(0,L+C(Y)+S()+R-o)}return 0}i=i||{};var D,P,H,T=!1,M=t[0].body,B=o[0],A=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 N=i.id,Q=i.mediaQuery,O=i.stickyClass,L=(i.usePlaceholder,i.offsetTop),R=i.offsetBottom,Y=i.anchor.toLowerCase().trim(),j=i.event,q=!1===i.stack?null:i.stack||n({zIndex:i.zIndex}),F=null,U={top:0,bottom:0},W={style:o.attr("style")||""};o.addClass("sticky-"+Y);var G={};if(q){var J=q.add(N,G);N=J.id}return G.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&&p(),g()},G.anchor=function(){return Y},G.isSticky=s,G.isEnabled=u,G.computedHeight=$,G.sticksAtPosition=f,G.destroy=function(){p(),q&&q.remove(N)},G}}]).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||(p=u(h,e.defaults.checkDelay),v.on("resize",p),v.on("scroll",d),y=n.$on("$viewContentLoaded",p),k=n.$on("$includeContentLoaded",p),p())}function f(){--t>0||(v.off("resize",p),v.off("scroll",d),y(),k())}function d(){m()}function h(){m({force:!0})}function m(t){angular.forEach(e.collections,function(e){e.draw(t)})}function g(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}var y,k,p,v=angular.element(o);return g}]};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 +"use strict";angular.module("hl.sticky",[]).factory("mediaQuery",function(){return{matches:function(t){return t&&(matchMedia("("+t+")").matches||matchMedia(t).matches)}}}).factory("hlStickyStack",["$document","StickyStackDefaults",function(t,e){var n=t[0].documentElement,o={};return function(t){var r=(t=t||{}).name||e.defaultStack;if(o[r])return o[r];var a=t.zIndex||e.zIndex,i=[],c={};return c.options=t,c.stackName=r,c.add=function(t,e){return angular.isString(t)&&""!==t||(t=c.length()),e.id=t,e.zIndex=a,i.push(e),a-=1,e},c.get=function(t){for(var e=0;e=c()}function d(t){return(t=void 0!==t?t:window.pageYOffset||T.scrollTop)+window.innerHeight<=l()}function h(){return!1===A||r.matches(A)}function m(){var t=s(R);angular.isDefined(i.enable)&&!i.enable&&(t=!1),angular.isDefined(i.alwaysSticky)&&i.alwaysSticky&&(t=!0),t&&!H?(y(),Y({event:"stick"})):!t&&H&&(k(),Y({event:"unstick"})),g(),H&&("top"===R?o.css("top",Q+w(R)-$()+"px"):"bottom"===R&&o.css("bottom",L+w(R)-E()+"px"),o.css("width",p()+"px"))}function g(){var t=null;switch(o.removeClass(i.beforeStickyClass),o.removeClass(i.afterStickyClass),R){case"top":H?$()>0&&(o.addClass(i.afterStickyClass),t="after"):(o.addClass(i.beforeStickyClass),t="before");break;case"bottom":E()>0?(o.addClass(i.beforeStickyClass),t="before"):H||(o.addClass(i.afterStickyClass),t="after")}"before"!==t&&o.removeClass(i.beforeStickyClass),"after"!==t&&o.removeClass(i.afterStickyClass)}function y(){H=!0,o.addClass(N),i.usePlaceholder&&((P=P||angular.element("
    ")).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(); });