diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..d755f12 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,14 @@ +{ + "eqeqeq": true, + "newcap": true, + "nonew": true, + "undef": true, + "unused": true, + "freeze": true, + "globals": { + "window": false, + "document": false, + "console": false, + "angular": false + } +} \ No newline at end of file diff --git a/LICENSE b/LICENSE index ba9db63..1bd4ed8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014 David Oliveros +Copyright (c) 2015 David Oliveros Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/bower.json b/bower.json index 25e2462..3a39c3d 100644 --- a/bower.json +++ b/bower.json @@ -1,12 +1,12 @@ { "name": "angular-sticky", - "version": "1.7.5", + "version": "1.7.6", "homepage": "https://github.com/d-oliveros/angular-sticky", "authors": [ "David Oliveros " ], "description": "A simple, pure javascript (No jQuery required!) AngularJS directive to make elements stick when scrolling down.", - "main": "sticky.js", + "main": "lib/sticky.js", "dependencies": { "angular": "^1.2.0", "matchmedia": "~0.2.0" diff --git a/dist/sticky.min.js b/dist/sticky.min.js new file mode 100644 index 0000000..ab8e677 --- /dev/null +++ b/dist/sticky.min.js @@ -0,0 +1 @@ +!function(){"use strict";var t=angular.module("sticky",[]);t.directive("sticky",function(){function t(t,o,e){function s(){g.offsetWidth=u.offsetWidth}function n(){h.off("scroll",i),h.off("resize",s),p&&w.removeClass(p)}function i(){var t,o,e,s;(!l||x("("+l+")").matches)&&("top"===T?(s=window.pageYOffset||m.scrollTop,t=s-(m.clientTop||0),o=t>=C):(e=window.pageYOffset+window.innerHeight,o=C>=e),o&&!b?r():!o&&b&&a())}function r(){var t,e;t=o[0].getBoundingClientRect(),e=t.left,g.offsetWidth=u.offsetWidth,b=!0,p&&(w.addClass(p),o.addClass(d)),o.css("width",u.offsetWidth+"px").css("position","fixed").css(T,k+"px").css("margin-top",0),"bottom"===T&&o.css("margin-bottom",0)}function a(){o.attr("style",o.initialStyle),b=!1,p&&w.removeClass(p),d&&o.removeClass(d),o.css("width",g.offsetWidth+"px").css("top",g.top).css("position",g.position).css("margin-top",g.marginTop)}function c(t){var o=0;if(t.offsetParent)do o+=t.offsetTop,t=t.offsetParent;while(t);return o}function f(t){return t.offsetTop+t.clientHeight}var l,d,p,u,h,w,m,g,y,v,b,C,k,T,W,x;switch(v=!1,b=!1,x=window.matchMedia,h=angular.element(window),w=angular.element(document.body),u=o[0],m=document.documentElement,l=e.mediaQuery||!1,d=e.stickyClass||"",p=e.bodyClass||"",y=o.attr("style"),k="string"==typeof e.offset?parseInt(e.offset.replace(/px;?/,"")):0,T="string"==typeof e.anchor?e.anchor.toLowerCase().trim():"top",g={top:o.css("top"),width:o.css("width"),position:o.css("position"),marginTop:o.css("margin-top"),cssLeft:o.css("left")},T){case"top":case"bottom":break;default:console.log("Unknown anchor "+T+", defaulting to top"),T="top"}h.on("scroll",i),h.on("resize",t.$apply.bind(t,s)),t.$on("$destroy",n),W=c(u),t.$watch(function(){return b?W:W="top"===T?c(u):f(u)},function(t,o){(t!==o||void 0===C)&&(C=t-k,i())})}return{restrict:"A",link:t}}),window.matchMedia=window.matchMedia||function(){var t="angular-sticky: This browser does not support matchMedia, therefore the minWidth option will not work on this browser. Polyfill matchMedia to fix this issue.";return window.console&&console.warn&&console.warn(t),function(){return{matches:!0}}}()}(); \ No newline at end of file diff --git a/lib/sticky.js b/lib/sticky.js new file mode 100644 index 0000000..d006306 --- /dev/null +++ b/lib/sticky.js @@ -0,0 +1,216 @@ +(function () { + 'use strict'; + + var module = angular.module('sticky', []); + + // Directive: sticky + // + module.directive('sticky', function() { + return { + restrict: 'A', // this directive can only be used as an attribute. + link: linkFn + }; + + function linkFn($scope, $elem, $attrs) { + var mediaQuery, stickyClass, bodyClass, elem, $window, $body, + doc, initialCSS, initialStyle, isPositionFixed, isSticking, + stickyLine, offset, anchor, prevOffset, matchMedia; + + isPositionFixed = false; + isSticking = false; + + matchMedia = window.matchMedia; + + // elements + $window = angular.element(window); + $body = angular.element(document.body); + elem = $elem[0]; + doc = document.documentElement; + + // attributes + mediaQuery = $attrs.mediaQuery || false; + stickyClass = $attrs.stickyClass || ''; + bodyClass = $attrs.bodyClass || ''; + + initialStyle = $elem.attr('style'); + + offset = typeof $attrs.offset === 'string' ? + parseInt($attrs.offset.replace(/px;?/, '')) : + 0; + + anchor = typeof $attrs.anchor === 'string' ? + $attrs.anchor.toLowerCase().trim() + : 'top'; + + // initial style + initialCSS = { + top: $elem.css('top'), + width: $elem.css('width'), + position: $elem.css('position'), + marginTop: $elem.css('margin-top'), + cssLeft: $elem.css('left') + }; + + switch (anchor) { + case 'top': + case 'bottom': + break; + default: + console.log('Unknown anchor '+anchor+', defaulting to top'); + anchor = 'top'; + break; + } + + + // Listeners + // + $window.on('scroll', checkIfShouldStick); + $window.on('resize', $scope.$apply.bind($scope, onResize)); + $scope.$on('$destroy', onDestroy); + + function onResize() { + initialCSS.offsetWidth = elem.offsetWidth; + } + + function onDestroy() { + $window.off('scroll', checkIfShouldStick); + $window.off('resize', onResize); + + if ( bodyClass ) { + $body.removeClass(bodyClass); + } + } + + + // Watcher + // + prevOffset = _getTopOffset(elem); + + $scope.$watch( function() { // triggered on load and on digest cycle + if ( isSticking ) return prevOffset; + + prevOffset = + (anchor === 'top') ? + _getTopOffset(elem) : + _getBottomOffset(elem); + + return prevOffset; + + }, function(newVal, oldVal) { + if ( newVal !== oldVal || typeof stickyLine === 'undefined' ) { + stickyLine = newVal - offset; + checkIfShouldStick(); + } + }); + + + // Methods + // + function checkIfShouldStick() { + var scrollTop, shouldStick, scrollBottom, scrolledDistance; + + if ( mediaQuery && !matchMedia('('+mediaQuery+')').matches) + return; + + if ( anchor === 'top' ) { + scrolledDistance = window.pageYOffset || doc.scrollTop; + scrollTop = scrolledDistance - (doc.clientTop || 0); + shouldStick = scrollTop >= stickyLine; + } else { + scrollBottom = window.pageYOffset + window.innerHeight; + shouldStick = scrollBottom <= stickyLine; + } + + // Switch the sticky mode if the element crosses the sticky line + if ( shouldStick && !isSticking ) + stickElement(); + + else if ( !shouldStick && isSticking ) + unstickElement(); + } + + function stickElement() { + var rect, absoluteLeft; + + rect = $elem[0].getBoundingClientRect(); + absoluteLeft = rect.left; + + initialCSS.offsetWidth = elem.offsetWidth; + + isSticking = true; + + if ( bodyClass ) { + $body.addClass(bodyClass); + $elem.addClass(stickyClass); + } + + $elem + .css('width', elem.offsetWidth+'px') + .css('position', 'fixed') + .css(anchor, offset+'px') + .css('margin-top', 0); + + if ( anchor === 'bottom' ) { + $elem.css('margin-bottom', 0); + } + } + + function unstickElement() { + //$elem[0].removeAttribute("style"); + $elem.attr('style', $elem.initialStyle); + isSticking = false; + + if ( bodyClass ) { + $body.removeClass(bodyClass); + } + + if ( stickyClass ) { + $elem.removeClass(stickyClass); + } + + $elem + .css('width', initialCSS.offsetWidth+'px') + .css('top', initialCSS.top) + .css('position', initialCSS.position) + .css('margin-top', initialCSS.marginTop); + } + + function _getTopOffset (element) { + var pixels = 0; + + if (element.offsetParent) { + do { + pixels += element.offsetTop; + element = element.offsetParent; + } while (element); + } + + return pixels; + } + + function _getBottomOffset (element) { + return element.offsetTop + element.clientHeight; + } + } + + }); + + // Shiv: matchMedia + // + window.matchMedia = window.matchMedia || (function() { + var warning = 'angular-sticky: This browser does not support '+ + 'matchMedia, therefore the minWidth option will not work on '+ + 'this browser. Polyfill matchMedia to fix this issue.'; + + if ( window.console && console.warn ) { + console.warn(warning); + } + + return function() { + return { + matches: true + }; + }; + }()); + +}()); diff --git a/sticky.js b/sticky.js deleted file mode 100644 index c036b00..0000000 --- a/sticky.js +++ /dev/null @@ -1,178 +0,0 @@ -(function () { - - var module = angular.module('sticky', []); - - // Shiv: matchMedia - // - window.matchMedia || (window.matchMedia = function() { - window.console && console.warn && console.warn('angular-sticky: This browser does not support matchMedia, '+ - 'therefore the minWidth option will not work on this browser. '+ - 'Polyfill matchMedia to fix this issue.'); - return function() { - return { - matches: true - }; - }; - }()); - - // Directive: sticky - // - module.directive('sticky', function() { - - var linkFn = function(scope , elem, attrs) { - var mediaQuery = scope.mediaQuery || null, - stickyClass = scope.stickyClass || '', - bodyClass = scope.bodyClass || '', - - $elem = elem, elem = $elem[0], - $window = angular.element(window), - $body = angular.element(document.body), - doc = document.documentElement, - - initial = { - top: $elem.css('top'), - width: $elem.css('width'), - position: $elem.css('position'), - marginTop: $elem.css('margin-top'), - }, - - isPositionFixed = false, - isSticking = false, - stickyLine; - - var offset = typeof scope.offset === 'string' - ? parseInt( scope.offset.replace(/px;?/, '') ) - : 0; - - var anchor = typeof scope.anchor === 'string' - ? scope.anchor.toLowerCase().trim() - : 'top'; - switch (anchor) { - case 'top': - case 'bottom': - break; - default: - console.log('Unknown anchor ' + anchor + ', defaulting to top'); - anchor = 'top'; - break; - } - - // Watchers - // - var prevOffset = _getTopOffset(elem); - - scope.$watch( function() { - if ( isSticking ) return prevOffset; - - prevOffset = - (anchor == 'top') - ? _getTopOffset(elem) - : _getBottomOffset(elem); - return prevOffset; - - }, function(newVal, oldVal) { - if ( newVal !== oldVal || typeof stickyLine === 'undefined' ) { - stickyLine = newVal - offset; - checkIfShouldStick(); - } - }); - - // checks if the window has passed the sticky line - function checkIfShouldStick() { - if ( mediaQuery && !matchMedia('('+mediaQuery+')').matches) return; - - if (anchor == 'top') { - var scrollTop = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0); - var shouldStick = scrollTop >= stickyLine; - } else { - var scrollBottom = window.pageYOffset + window.innerHeight; - var shouldStick = scrollBottom <= stickyLine; - } - - // Switch the sticky modes if the element has crossed the sticky line - if ( shouldStick && !isSticking ) - stickElement(); - - else if ( !shouldStick && isSticking ) - unstickElement(); - } - - function stickElement() { - initial.offsetWidth = elem.offsetWidth; - isSticking = true; - bodyClass && $body.addClass(bodyClass); - stickyClass && $elem.addClass(stickyClass); - - $elem - .css('width', elem.offsetWidth+'px') - .css('position', 'fixed') - .css(anchor, offset+'px') - .css('margin-top', 0); - - if (anchor == 'bottom') { - $elem.css('margin-bottom', 0); - } - }; - - function unstickElement() { - isSticking = false; - bodyClass && $body.removeClass(bodyClass); - stickyClass && $elem.removeClass(stickyClass); - - $elem - .css('width', initial.offsetWidth+'px') - .css('top', initial.top) - .css('position', initial.position) - .css('margin-top', initial.marginTop); - }; - - function _getTopOffset (element) { - var pixels = 0; - - if (element.offsetParent) { - do { - pixels += element.offsetTop; - element = element.offsetParent; - } while (element); - } - - return pixels; - } - - function _getBottomOffset (element) { - return element.offsetTop + element.clientHeight; - } - - - // Listeners - // - $window.on('scroll', checkIfShouldStick); - $window.on('resize', scope.$apply.bind(scope, onResize)); - scope.$on('$destroy', onDestroy); - - function onResize() { - initial.offsetWidth = elem.offsetWidth; - }; - - function onDestroy() { - $window.off('scroll', checkIfShouldStick); - $window.off('resize', onResize); - }; - }; - - - // Directive definition - // - return { - scope: { - anchor: '@', // top or bottom - offset: '@', // offset from the top/bottom window edge - mediaQuery: '@', // minimum width required for sticky to come in - stickyClass: '@', // class to be applied to the element on sticky - bodyClass: '@' // class to be applied to the body on sticky - }, - restrict: 'A', // sticky can only be used as an ('A') attribute. - link: linkFn - }; - }); -}()); diff --git a/sticky.min.js b/sticky.min.js deleted file mode 100644 index d41a735..0000000 --- a/sticky.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(){var a=angular.module("sticky",[]);window.matchMedia||(window.matchMedia=function(){window.console&&console.warn&&console.warn("angular-sticky: This browser does not support matchMedia, therefore the minWidth option will not work on this browser. Polyfill matchMedia to fix this issue.");return function(){return{matches:true}}}());a.directive("sticky",function(){var b=function(d,x,r){var t=d.mediaQuery||null,i=d.stickyClass||"",u=d.bodyClass||"",c=x,x=c[0],o=angular.element(window),s=angular.element(document.body),z=document.documentElement,f={top:c.css("top"),width:c.css("width"),position:c.css("position"),marginTop:c.css("margin-top")},e=false,y=false,w;var k=typeof d.offset==="string"?parseInt(d.offset.replace(/px;?/,"")):0;var m=typeof d.anchor==="string"?d.anchor.toLowerCase().trim():"top";switch(m){case"top":case"bottom":break;default:console.log("Unknown anchor "+m+", defaulting to top");m="top";break}var h=l(x);d.$watch(function(){if(y){return h}h=(m=="top")?l(x):n(x);return h},function(B,A){if(B!==A||typeof w==="undefined"){w=B-k;p()}});function p(){if(t&&!matchMedia("("+t+")").matches){return}if(m=="top"){var C=(window.pageYOffset||z.scrollTop)-(z.clientTop||0);var A=C>=w}else{var B=window.pageYOffset+window.innerHeight;var A=B<=w}if(A&&!y){q()}else{if(!A&&y){v()}}}function q(){f.offsetWidth=x.offsetWidth;y=true;u&&s.addClass(u);i&&c.addClass(i);c.css("width",x.offsetWidth+"px").css("position","fixed").css(m,k+"px").css("margin-top",0);if(m=="bottom"){c.css("margin-bottom",0)}}function v(){y=false;u&&s.removeClass(u);i&&c.removeClass(i);c.css("width",f.offsetWidth+"px").css("top",f.top).css("position",f.position).css("margin-top",f.marginTop)}function l(A){var B=0;if(A.offsetParent){do{B+=A.offsetTop;A=A.offsetParent}while(A)}return B}function n(A){return A.offsetTop+A.clientHeight}o.on("scroll",p);o.on("resize",d.$apply.bind(d,g));d.$on("$destroy",j);function g(){f.offsetWidth=x.offsetWidth}function j(){o.off("scroll",p);o.off("resize",g)}};return{scope:{anchor:"@",offset:"@",mediaQuery:"@",stickyClass:"@",bodyClass:"@"},restrict:"A",link:b}})}()); diff --git a/demo.html b/test/demo.html similarity index 81% rename from demo.html rename to test/demo.html index b81b749..535063c 100644 --- a/demo.html +++ b/test/demo.html @@ -1,7 +1,7 @@ - Angular-Sticky Demo v1.7.5 + Angular-Sticky Demo v1.7.6