├── .gitignore ├── .jshintrc ├── LICENSE ├── README.md ├── angular-parallax.js ├── angular-parallax.min.js ├── angular-parallax.min.js.map ├── bower.json ├── example └── index.html ├── gulpfile.js ├── package.json └── src ├── directives └── parallax.js ├── module.js └── services └── helper.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | .idea 15 | node_modules 16 | bower_components 17 | npm-debug.log 18 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Durated 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | angular-parallax 2 | ================ 3 | 4 | Lightweight and highly performant AngularJS directive for parallax scrolling. Script is just 1.6K and about 40K gzipped with dependencies. 5 | 6 | Uses `requestAnimationFrame` and `translate3d` for GPU accelerated, smooth transitions. 7 | 8 | Install 9 | ------- 10 | 11 | $ bower install ng-parallax 12 | 13 | Dependencies 14 | ------------ 15 | [AngularJS](https://github.com/angular/angular.js) and [angular-scroll](https://github.com/oblador/angular-scroll). 16 | 17 | Usage 18 | ----- 19 | 20 | ### Quickstart 21 | 22 | Include module and dependencies. 23 | ```html 24 | 25 | 26 | 27 | ``` 28 | 29 | Define transitions and expose to template. 30 | ```js 31 | angular.module('myApp', ['duParallax']). 32 | controller('MyCtrl', function($scope, parallaxHelper){ 33 | $scope.background = parallaxHelper.createAnimator(-0.3); 34 | } 35 | ); 36 | ``` 37 | 38 | Apply parallax scrolling with the `du-parallax` attribute, define `y` position with the transition named `background`. 39 | ```html 40 |
41 | 42 |
43 | ``` 44 | 45 | ### `createAnimator` 46 | Convenience method for creating animator closures. 47 | 48 | ```js 49 | parallaxHelper.createAnimator(easingFactor, max, min, offset); 50 | ``` 51 | 52 | ### Animatable attributes 53 | 54 | Attributes can be literals or a function called with a parameter object containing `scrollY`, `elemX`, `elemY`. The function should return the change in pixels relative to the objects current position if associated with y or x, otherwise the desired new value. 55 | 56 | * y 57 | * x 58 | * rotation 59 | * opacity 60 | * custom 61 | 62 | ```html 63 | 64 | ``` 65 | 66 | ### Custom animator 67 | 68 | The custom animator should return an object with camelCased CSS properties like this: 69 | 70 | ```js 71 | $scope.invertColors = function(elementPosition) { 72 | var factor = -0.4; 73 | var pos = Math.min(Math.max(elementPosition.elemY*factor, 0), 255); 74 | var bg = 255-pos; 75 | return { 76 | backgroundColor: 'rgb(' + bg + ', ' + bg + ', ' + bg + ')', 77 | color: 'rgb(' + pos + ', ' + pos + ', ' + pos + ')' 78 | }; 79 | } 80 | ``` 81 | ```html 82 |
[loads of text…]
83 | ``` 84 | 85 | 86 | Example 87 | ------- 88 | 89 | Check out [oblador.github.io/angular-parallax](http://oblador.github.io/angular-parallax/) or view the source at [example/index.html](https://github.com/oblador/angular-parallax/blob/master/example/index.html). 90 | 91 | Building 92 | -------- 93 | 94 | $ npm install 95 | $ gulp 96 | 97 | License 98 | -------- 99 | 100 | Licensed under the [MIT License](http://opensource.org/licenses/MIT) 101 | -------------------------------------------------------------------------------- /angular-parallax.js: -------------------------------------------------------------------------------- 1 | angular.module('duParallax', ['duScroll', 'duParallax.directive', 'duParallax.helper']); 2 | 3 | 4 | angular.module('duParallax.helper', []). 5 | factory('parallaxHelper', 6 | function() { 7 | function createAnimator (factor, max, min, offset) { 8 | return function(params) { 9 | var delta = factor*((offset || 0) + params.elemY); 10 | if(angular.isNumber(max) && delta > max) return max; 11 | if(angular.isNumber(min) && delta < min) return min; 12 | return delta; 13 | }; 14 | } 15 | return { 16 | createAnimator: createAnimator, 17 | background: createAnimator(-0.3, 150, -30, 50) 18 | }; 19 | }); 20 | 21 | 22 | angular.module('duParallax.directive', ['duScroll']). 23 | directive('duParallax', 24 | ["$rootScope", "$window", "$document", function($rootScope, $window, $document){ 25 | 26 | var test = angular.element('
')[0]; 27 | var prefixes = 'transform WebkitTransform MozTransform OTransform'.split(' '); //msTransform 28 | var transformProperty; 29 | for(var i = 0; i < prefixes.length; i++) { 30 | if(test.style[prefixes[i]] !== undefined) { 31 | transformProperty = prefixes[i]; 32 | break; 33 | } 34 | } 35 | 36 | //Skipping browsers withouth transform-support. 37 | //Could do fallback to margin or absolute positioning, but would most likely perform badly 38 | //so better UX would be to keep things static. 39 | if(!transformProperty){ 40 | return; 41 | } 42 | 43 | var translate3d = function(result){ 44 | if(!result.x && !result.y) return ''; 45 | return 'translate3d(' + Math.round(result.x) + 'px, ' + Math.round(result.y) + 'px, 0)'; 46 | }; 47 | 48 | var rotate = function(result) { 49 | if(!result.rotation) return ''; 50 | return ' rotate(' + (angular.isNumber(result.rotation) ? Math.round(result.rotation) + 'deg' : result.rotation) + ')'; 51 | }; 52 | 53 | var applyProperties = function(result, element) { 54 | element.style[transformProperty] = translate3d(result) + rotate(result); 55 | element.style.opacity = result.opacity; 56 | if(result.custom) { 57 | for(var property in result.custom) { 58 | element.style[property] = result.custom[property]; 59 | } 60 | } 61 | }; 62 | 63 | return{ 64 | scope : { 65 | y : '=', 66 | x : '=', 67 | rotation : '=', 68 | opacity : '=', 69 | custom : '=' 70 | }, 71 | link: function($scope, $element, $attr){ 72 | var element = $element[0]; 73 | var currentProperties; 74 | var inited = false; 75 | 76 | var onScroll = function(){ 77 | var scrollY = $document.scrollTop(); 78 | var rect = element.getBoundingClientRect(); 79 | if(!inited) { 80 | inited = true; 81 | angular.element($window).on('load', function init() { 82 | //Trigger the onScroll until position stabilizes. Don't know why this is needed. 83 | //TODO: Think of more elegant solution. 84 | var i = 0; 85 | var maxIterations = 10; 86 | var currentY = rect.top; 87 | var lastY; 88 | do { 89 | lastY = currentY; 90 | onScroll(); 91 | currentY = element.getBoundingClientRect().top; 92 | i++; 93 | } while(i < maxIterations && lastY !== currentY); 94 | }); 95 | } 96 | 97 | var param = { 98 | scrollY : scrollY, 99 | elemX: rect.left, 100 | elemY: rect.top 101 | }; 102 | 103 | var properties = { x : 0, y : 0, rotation : 0, opacity: 1, custom: undefined}; 104 | 105 | for(var key in properties){ 106 | if(angular.isFunction($scope[key])){ 107 | properties[key] = $scope[key](param); 108 | } else if($scope[key]){ 109 | properties[key] = $scope[key]; 110 | } 111 | } 112 | 113 | //Detect changes, if no changes avoid reflow 114 | var hasChange = angular.isUndefined(currentProperties); 115 | if(!hasChange) { 116 | for(key in properties){ 117 | if(properties[key] !== currentProperties[key]) { 118 | hasChange = true; 119 | break; 120 | } 121 | } 122 | } 123 | 124 | if(hasChange) { 125 | applyProperties(properties, element); 126 | currentProperties = properties; 127 | } 128 | }; 129 | 130 | $document.on('scroll touchmove', onScroll).triggerHandler('scroll'); 131 | 132 | $scope.$on('$destroy', function() { 133 | $document.off('scroll touchmove', onScroll); 134 | }); 135 | } 136 | }; 137 | }]); 138 | -------------------------------------------------------------------------------- /angular-parallax.min.js: -------------------------------------------------------------------------------- 1 | angular.module("duParallax",["duScroll","duParallax.directive","duParallax.helper"]),angular.module("duParallax.helper",[]).factory("parallaxHelper",function(){function r(r,o,a,t){return function(n){var e=r*((t||0)+n.elemY);return angular.isNumber(o)&&e>o?o:angular.isNumber(a)&&a>e?a:e}}return{createAnimator:r,background:r(-.3,150,-30,50)}}),angular.module("duParallax.directive",["duScroll"]).directive("duParallax",["$rootScope","$window","$document",function(r,o,a){for(var t,n=angular.element("
")[0],e="transform WebkitTransform MozTransform OTransform".split(" "),l=0;lo&&r!==t)}));var d={scrollY:t,elemX:u.left,elemY:u.top},f={x:0,y:0,rotation:0,opacity:1,custom:void 0};for(var s in f)angular.isFunction(r[s])?f[s]=r[s](d):r[s]&&(f[s]=r[s]);var m=angular.isUndefined(n);if(!m)for(s in f)if(f[s]!==n[s]){m=!0;break}m&&(c(f,e),n=f)};a.on("scroll touchmove",i).triggerHandler("scroll"),r.$on("$destroy",function(){a.off("scroll touchmove",i)})}}}}]); 2 | //# sourceMappingURL=angular-parallax.min.js.map -------------------------------------------------------------------------------- /angular-parallax.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"angular-parallax.min.js","sources":["module.js","helper.js","parallax.js"],"names":[],"mappings":"AAAA,QAAA,OAAA,cAAA,WAAA,uBAAA,sBCAA,QAAA,OAAA,wBACA,QAAA,iBACA,WACA,QAAA,GAAA,EAAA,EAAA,EAAA,GACA,MAAA,UAAA,GACA,GAAA,GAAA,IAAA,GAAA,GAAA,EAAA,MACA,OAAA,SAAA,SAAA,IAAA,EAAA,EAAA,EACA,QAAA,SAAA,IAAA,EAAA,EAAA,EACA,GAGA,OACA,eAAA,EACA,WAAA,GAAA,GAAA,IAAA,IAAA,OCbA,QAAA,OAAA,wBAAA,aACA,UAAA,cACA,aAAA,UAAA,YAAA,SAAA,EAAA,EAAA,GAKA,IAAA,GADA,GAFA,EAAA,QAAA,QAAA,eAAA,GACA,EAAA,oDAAA,MAAA,KAEA,EAAA,EAAA,EAAA,EAAA,OAAA,IACA,GAAA,SAAA,EAAA,MAAA,EAAA,IAAA,CACA,EAAA,EAAA,EACA,OAOA,GAAA,EAAA,CAIA,GAAA,GAAA,SAAA,GACA,MAAA,GAAA,GAAA,EAAA,EACA,eAAA,KAAA,MAAA,EAAA,GAAA,OAAA,KAAA,MAAA,EAAA,GAAA,SADA,IAIA,EAAA,SAAA,GACA,MAAA,GAAA,SACA,YAAA,QAAA,SAAA,EAAA,UAAA,KAAA,MAAA,EAAA,UAAA,MAAA,EAAA,UAAA,IADA,IAIA,EAAA,SAAA,EAAA,GAGA,GAFA,EAAA,MAAA,GAAA,EAAA,GAAA,EAAA,GACA,EAAA,MAAA,QAAA,EAAA,QACA,EAAA,OACA,IAAA,GAAA,KAAA,GAAA,OACA,EAAA,MAAA,GAAA,EAAA,OAAA,GAKA,QACA,OACA,EAAA,IACA,EAAA,IACA,SAAA,IACA,QAAA,IACA,OAAA,KAEA,KAAA,SAAA,EAAA,GACA,GACA,GADA,EAAA,EAAA,GAEA,GAAA,EAEA,EAAA,WACA,GAAA,GAAA,EAAA,YACA,EAAA,EAAA,uBACA,KACA,GAAA,EACA,QAAA,QAAA,GAAA,GAAA,OAAA,WAGA,GAGA,GAHA,EAAA,EACA,EAAA,GACA,EAAA,EAAA,GAEA,GACA,GAAA,EACA,IACA,EAAA,EAAA,wBAAA,IACA,UACA,EAAA,GAAA,IAAA,KAIA,IAAA,IACA,QAAA,EACA,MAAA,EAAA,KACA,MAAA,EAAA,KAGA,GAAA,EAAA,EAAA,EAAA,EAAA,SAAA,EAAA,QAAA,EAAA,OAAA,OAEA,KAAA,GAAA,KAAA,GACA,QAAA,WAAA,EAAA,IACA,EAAA,GAAA,EAAA,GAAA,GACA,EAAA,KACA,EAAA,GAAA,EAAA,GAKA,IAAA,GAAA,QAAA,YAAA,EACA,KAAA,EACA,IAAA,IAAA,GACA,GAAA,EAAA,KAAA,EAAA,GAAA,CACA,GAAA,CACA,OAKA,IACA,EAAA,EAAA,GACA,EAAA,GAIA,GAAA,GAAA,mBAAA,GAAA,eAAA,UAEA,EAAA,IAAA,WAAA,WACA,EAAA,IAAA,mBAAA","sourcesContent":["angular.module('duParallax', ['duScroll', 'duParallax.directive', 'duParallax.helper']);\n","angular.module('duParallax.helper', []).\nfactory('parallaxHelper',\n function() {\n function createAnimator (factor, max, min, offset) {\n return function(params) {\n var delta = factor*((offset || 0) + params.elemY);\n if(angular.isNumber(max) && delta > max) return max;\n if(angular.isNumber(min) && delta < min) return min;\n return delta;\n };\n }\n return {\n createAnimator: createAnimator,\n background: createAnimator(-0.3, 150, -30, 50)\n };\n});\n","angular.module('duParallax.directive', ['duScroll']).\ndirective('duParallax',\n function($rootScope, $window, $document){\n\n var test = angular.element('
')[0];\n var prefixes = 'transform WebkitTransform MozTransform OTransform'.split(' '); //msTransform\n var transformProperty;\n for(var i = 0; i < prefixes.length; i++) {\n if(test.style[prefixes[i]] !== undefined) {\n transformProperty = prefixes[i];\n break;\n }\n }\n\n //Skipping browsers withouth transform-support.\n //Could do fallback to margin or absolute positioning, but would most likely perform badly\n //so better UX would be to keep things static.\n if(!transformProperty){\n return;\n }\n\n var translate3d = function(result){\n if(!result.x && !result.y) return '';\n return 'translate3d(' + Math.round(result.x) + 'px, ' + Math.round(result.y) + 'px, 0)';\n };\n\n var rotate = function(result) {\n if(!result.rotation) return '';\n return ' rotate(' + (angular.isNumber(result.rotation) ? Math.round(result.rotation) + 'deg' : result.rotation) + ')';\n };\n\n var applyProperties = function(result, element) {\n element.style[transformProperty] = translate3d(result) + rotate(result);\n element.style.opacity = result.opacity;\n if(result.custom) {\n for(var property in result.custom) {\n element.style[property] = result.custom[property];\n }\n }\n };\n\n return{\n scope : {\n y : '=',\n x : '=',\n rotation : '=',\n opacity : '=',\n custom : '='\n },\n link: function($scope, $element, $attr){\n var element = $element[0];\n var currentProperties;\n var inited = false;\n\n var onScroll = function(){\n var scrollY = $document.scrollTop();\n var rect = element.getBoundingClientRect();\n if(!inited) {\n inited = true;\n angular.element($window).on('load', function init() {\n //Trigger the onScroll until position stabilizes. Don't know why this is needed.\n //TODO: Think of more elegant solution.\n var i = 0;\n var maxIterations = 10;\n var currentY = rect.top;\n var lastY;\n do {\n lastY = currentY;\n onScroll();\n currentY = element.getBoundingClientRect().top;\n i++;\n } while(i < maxIterations && lastY !== currentY);\n });\n }\n\n var param = {\n scrollY : scrollY,\n elemX: rect.left,\n elemY: rect.top\n };\n\n var properties = { x : 0, y : 0, rotation : 0, opacity: 1, custom: undefined};\n\n for(var key in properties){\n if(angular.isFunction($scope[key])){\n properties[key] = $scope[key](param);\n } else if($scope[key]){\n properties[key] = $scope[key];\n }\n }\n\n //Detect changes, if no changes avoid reflow\n var hasChange = angular.isUndefined(currentProperties);\n if(!hasChange) {\n for(key in properties){\n if(properties[key] !== currentProperties[key]) {\n hasChange = true;\n break;\n }\n }\n }\n\n if(hasChange) {\n applyProperties(properties, element);\n currentProperties = properties;\n }\n };\n\n $document.on('scroll touchmove', onScroll).triggerHandler('scroll');\n\n $scope.$on('$destroy', function() {\n $document.off('scroll touchmove', onScroll);\n });\n }\n };\n});\n"],"sourceRoot":"/source/"} -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-parallax", 3 | "version": "0.1.8", 4 | "main": "angular-parallax.min.js", 5 | "ignore": [ 6 | "**/.*", 7 | "node_modules", 8 | "bower_components", 9 | "test", 10 | "tests", 11 | "package.json", 12 | "src", 13 | "example", 14 | "gulpfile.js" 15 | ], 16 | "dependencies": { 17 | "angular": "^1.2.x", 18 | "angular-scroll": "^0.6.x" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular Parallax Demo 6 | 7 | 35 | 36 | 37 |
38 |

Responsive Angular.js Parallax Example

39 |

Bacon ipsum dolor sit amet sausage tail capicola ground round hamburger ham hock. Short ribs pig andouille meatball, pastrami tri-tip fatback ham hock shank kielbasa swine. Rump pancetta jerky kielbasa doner beef ribs tongue hamburger strip steak drumstick andouille shoulder shank flank. Swine drumstick meatball pig beef sausage strip steak.

40 | 41 | 42 | 43 |

Bacon strip steak ground round, tongue pastrami short ribs pork chop venison turducken sausage sirloin. Flank chicken pork chop capicola turkey turducken cow pork loin biltong meatball drumstick pancetta filet mignon ground round fatback. Ham hock jerky short ribs brisket. Meatloaf shoulder pork chop capicola, sirloin swine pig pork. Jerky ribeye hamburger pork loin sirloin kevin bresaola boudin chuck flank. Ham hock pork belly chicken jerky rump bresaola.

44 | 45 | 46 | 47 |

Shank fatback pastrami short loin, turkey jowl kielbasa ribeye chicken jerky drumstick flank ham. Swine shankle pork belly kielbasa shoulder flank jowl, sirloin doner. Kevin tri-tip bresaola leberkas. Swine ball tip cow strip steak. Ham filet mignon pork chop, pork fatback andouille pork loin shoulder jowl swine strip steak turducken prosciutto rump.

48 | 49 | 50 | 51 |

Tongue tri-tip pastrami, shoulder rump pork belly ground round. Ham hock chuck leberkas doner, strip steak corned beef tri-tip capicola. Rump turkey ham sausage shankle. Flank shankle pork chop ham hock. Shankle venison kielbasa, pancetta swine beef ball tip t-bone bacon hamburger ground round ribeye flank. Turducken bacon bresaola, chicken kevin boudin ball tip strip steak filet mignon pork turkey shank ground round. Kielbasa fatback prosciutto pork chop, jerky ground round leberkas boudin ball tip beef shankle shoulder swine brisket.

52 | 53 | 54 | 55 |

Shoulder cow tenderloin chuck, pork chop jerky doner leberkas. Chuck sausage hamburger, kevin beef pork chop pork shoulder ground round ball tip turducken flank. Bresaola tri-tip meatloaf, salami venison tail pig shank shankle jowl sausage brisket cow biltong turducken. Swine turducken hamburger ball tip short loin prosciutto kevin jowl tri-tip. Doner meatloaf pork brisket.

56 |
57 | 58 | 59 | 60 | 61 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var clean = require('gulp-rimraf'); 3 | var jshint = require('gulp-jshint'); 4 | var concat = require('gulp-concat'); 5 | var uglify = require('gulp-uglify'); 6 | var ngmin = require('gulp-ng-annotate'); 7 | var sourcemaps = require('gulp-sourcemaps'); 8 | 9 | var sources = [ 10 | 'src/module.js', 11 | 'src/services/helper.js', 12 | 'src/directives/parallax.js' 13 | ]; 14 | 15 | var targets = 'angular-parallax.{js,min.js,min.js.map}'; 16 | 17 | gulp.task('clean', function() { 18 | gulp.src(targets) 19 | .pipe(clean()); 20 | }); 21 | 22 | gulp.task('lint', function() { 23 | gulp.src(sources) 24 | .pipe(jshint()) 25 | .pipe(jshint.reporter('default')); 26 | }); 27 | 28 | gulp.task('compress', function() { 29 | //Development version 30 | gulp.src(sources) 31 | .pipe(concat('angular-parallax.js', { newLine: '\n\n' })) 32 | .pipe(ngmin()) 33 | .pipe(gulp.dest('./')); 34 | 35 | //Minified version 36 | gulp.src(sources) 37 | .pipe(sourcemaps.init()) 38 | .pipe(concat('angular-parallax.min.js', { newLine: '\n\n' })) 39 | .pipe(ngmin()) 40 | .pipe(uglify()) 41 | .pipe(sourcemaps.write('./')) 42 | .pipe(gulp.dest('./')); 43 | }); 44 | 45 | gulp.task('default', ['lint', 'clean', 'compress']); 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-parallax", 3 | "version": "0.1.8", 4 | "description": "Lightweight and performant parallax scrolling for angular.js", 5 | "keywords": ["angular", "parallax", "parallax-scrolling"], 6 | "main": "angular-parallax.min.js", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://git@github.com:oblador/angular-parallax.git" 10 | }, 11 | "bugs": { 12 | "url": "https://github.com/oblador/angular-parallax/issues" 13 | }, 14 | "scripts": { 15 | "test": "echo \"Error: no test specified\" && exit 1", 16 | "postinstall": "bower install" 17 | }, 18 | "author": "Joel Arvidsson", 19 | "license": "MIT", 20 | "devDependencies": { 21 | "gulp": "~3.8.0", 22 | "gulp-concat": "~2.3.4", 23 | "gulp-jshint": "~1.8.4", 24 | "gulp-ng-annotate": "~0.3.0", 25 | "gulp-rimraf": "~0.1.0", 26 | "gulp-sourcemaps": "^1.1.1", 27 | "gulp-uglify": "~0.3.1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/directives/parallax.js: -------------------------------------------------------------------------------- 1 | angular.module('duParallax.directive', ['duScroll']). 2 | directive('duParallax', 3 | function($rootScope, $window, $document, duParallaxTouchEvents){ 4 | 5 | var test = angular.element('
')[0]; 6 | var prefixes = 'transform WebkitTransform MozTransform OTransform'.split(' '); //msTransform 7 | var transformProperty; 8 | for(var i = 0; i < prefixes.length; i++) { 9 | if(test.style[prefixes[i]] !== undefined) { 10 | transformProperty = prefixes[i]; 11 | break; 12 | } 13 | } 14 | 15 | //Skipping browsers withouth transform-support. 16 | //Could do fallback to margin or absolute positioning, but would most likely perform badly 17 | //so better UX would be to keep things static. 18 | if(!transformProperty || (!duParallaxTouchEvents && 'ontouchstart' in window)) { 19 | return; 20 | } 21 | 22 | var translate3d = function(result) { 23 | if(!result.x && !result.y) return ''; 24 | return 'translate3d(' + Math.round(result.x) + 'px, ' + Math.round(result.y) + 'px, 0)'; 25 | }; 26 | 27 | var rotate = function(result) { 28 | if(!result.rotation) return ''; 29 | return ' rotate(' + (angular.isNumber(result.rotation) ? Math.round(result.rotation) + 'deg' : result.rotation) + ')'; 30 | }; 31 | 32 | var applyProperties = function(result, element) { 33 | element.style[transformProperty] = translate3d(result) + rotate(result); 34 | element.style.opacity = result.opacity; 35 | if(result.custom) { 36 | angular.extend(element.style, result.custom); 37 | } 38 | }; 39 | 40 | return{ 41 | scope : { 42 | y : '=', 43 | x : '=', 44 | rotation : '=', 45 | opacity : '=', 46 | custom : '=' 47 | }, 48 | link: function($scope, $element, $attr){ 49 | var element = $element[0]; 50 | var currentProperties; 51 | var inited = false; 52 | 53 | var onScroll = function(){ 54 | var scrollY = $document.scrollTop(); 55 | var rect = element.getBoundingClientRect(); 56 | if(!inited) { 57 | inited = true; 58 | angular.element($window).on('load', function init() { 59 | //Trigger the onScroll until position stabilizes. Don't know why this is needed. 60 | //TODO: Think of more elegant solution. 61 | var i = 0; 62 | var maxIterations = 10; 63 | var currentY = rect.top; 64 | var lastY; 65 | do { 66 | lastY = currentY; 67 | onScroll(); 68 | currentY = element.getBoundingClientRect().top; 69 | i++; 70 | } while(i < maxIterations && lastY !== currentY); 71 | }); 72 | } 73 | 74 | var param = { 75 | scrollY : scrollY, 76 | elemX: rect.left, 77 | elemY: rect.top 78 | }; 79 | 80 | var properties = { x : 0, y : 0, rotation : 0, opacity: 1, custom: undefined}; 81 | 82 | for(var key in properties){ 83 | if(angular.isFunction($scope[key])){ 84 | properties[key] = $scope[key](param); 85 | } else if($scope[key]){ 86 | properties[key] = $scope[key]; 87 | } 88 | } 89 | 90 | //Detect changes, if no changes avoid reflow 91 | var hasChange = angular.isUndefined(currentProperties); 92 | if(!hasChange) { 93 | for(key in properties){ 94 | if(properties[key] !== currentProperties[key]) { 95 | hasChange = true; 96 | break; 97 | } 98 | } 99 | } 100 | 101 | if(hasChange) { 102 | applyProperties(properties, element); 103 | currentProperties = properties; 104 | } 105 | }; 106 | 107 | $document.on('scroll touchmove', onScroll).triggerHandler('scroll'); 108 | 109 | $scope.$on('$destroy', function() { 110 | $document.off('scroll touchmove', onScroll); 111 | }); 112 | } 113 | }; 114 | }); 115 | -------------------------------------------------------------------------------- /src/module.js: -------------------------------------------------------------------------------- 1 | angular.module('duParallax', ['duScroll', 'duParallax.directive', 'duParallax.helper']).value('duParallaxTouchEvents', true); 2 | -------------------------------------------------------------------------------- /src/services/helper.js: -------------------------------------------------------------------------------- 1 | angular.module('duParallax.helper', []). 2 | factory('parallaxHelper', 3 | function() { 4 | function createAnimator (factor, max, min, offset) { 5 | return function(params) { 6 | var delta = factor*((offset || 0) + params.elemY); 7 | if(angular.isNumber(max) && delta > max) return max; 8 | if(angular.isNumber(min) && delta < min) return min; 9 | return delta; 10 | }; 11 | } 12 | return { 13 | createAnimator: createAnimator, 14 | background: createAnimator(-0.3, 150, -30, 50) 15 | }; 16 | }); 17 | --------------------------------------------------------------------------------