├── .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 |
--------------------------------------------------------------------------------