├── .gitignore
├── .jshintrc
├── LICENSE
├── README.md
├── bower.json
├── demo
├── app.js
├── index.html
└── styles.css
├── dist
└── angular-pretty-load.min.js
├── gulpfile.js
├── karma.conf.js
├── package.json
├── src
├── directive.js
└── index.js
└── tests
└── test-example.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | bower_components
3 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "browser": true,
3 | "esnext": true,
4 | "bitwise": false,
5 | "camelcase": true,
6 | "curly": false,
7 | "eqeqeq": true,
8 | "immed": true,
9 | "indent": 2,
10 | "latedef": true,
11 | "newcap": true,
12 | "noarg": true,
13 | "quotmark": "single",
14 | "regexp": true,
15 | "undef": true,
16 | "unused": true,
17 | "strict": false,
18 | "trailing": true,
19 | "smarttabs": true,
20 | "globals": {
21 | "angular": false
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Platanus
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, 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,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Angular Pretty Load directive
2 | ============
3 |
4 | Load your images in style using Angular: add an overlay before and during the image load, with the **exact same position and dimensions** of the image.
5 |
6 | | Without Angular Pretty Load | With Angular Pretty Load |
7 | | ------------------------------ | --------------------------- |
8 | |
|
|
9 |
10 | ## Angular Pretty is no longer maintained
11 |
12 | We will leave the Issues open as a discussion forum only.
13 | We do not guarantee a response from us in the Issues.
14 | We are no longer accepting pull requests.
15 |
16 | ## Usage
17 |
18 | ### Installation
19 |
20 | Just use Bower.
21 |
22 | ```
23 | bower install angular-pretty-load --save
24 | ```
25 |
26 | Then, inject it into your application:
27 |
28 | ```
29 | angular.module('MyApp', ['platanus.prettyLoad']);
30 | ```
31 |
32 | ### Directive in html template
33 |
34 | In order to use the most basic mode, you should specify both the width and height of the image in your CSS
35 |
36 | ```html
37 |
38 | ```
39 |
40 | #### Unknown image size
41 |
42 | If one or both dimensions are not specified in your CSS, but the API you're consuming or the server behind can provide the original size use this:
43 |
44 | ```html
45 |
49 | ```
50 |
51 | The directive will not override properties given to the image (`width: 100%`), but it will complete both width and height based on the original image ratio.
52 |
53 | #### Overlay Color
54 |
55 | You can set a common overlay color for all images:
56 |
57 | ```css
58 | .pretty-load-overlay {
59 | background-color: #333;
60 | }
61 | ```
62 |
63 | or customize it for every image:
64 |
65 | ```html
66 |
71 | ```
72 |
73 | #### Overlay Animation
74 |
75 | You have total control on how to handle the CSS transition from the overlay to the final image.
76 |
77 | This CSS will give you the same results as the demo:
78 |
79 | ```css
80 | .pretty-load-overlay {
81 | opacity: 0;
82 | }
83 |
84 | .pretty-load-loading .pretty-load-overlay {
85 | opacity: 1;
86 | transition: opacity 0.5s ease;
87 | }
88 |
89 | .pretty-load-completed .pretty-load-overlay {
90 | opacity: 0;
91 | transition: opacity 1.7s ease;
92 | }
93 | ```
94 |
95 | ### CSS Classes
96 |
97 | The directive wraps the image inside a div element. This container will have the following classes applied according to the state of the image inside:
98 |
99 | - `.pretty-load-init`: added when the directive is initialized
100 | - `.pretty-load-loading`: added when the directive is initialized and removed when the image finishes loading
101 | - `.pretty-load-completed`: added when the image finishes loading
102 |
103 | These are used in our example CSS and you can use them to control additional (for example, a spinner icon) or create more complex transitions between these states.
104 |
105 | * Note: `angular-pretty-load` does not handle lazy loading. You would have to use an additional library for that.
106 |
107 | ## Contributing
108 |
109 | 1. Fork it
110 | 2. Create your feature branch (`git checkout -b my-new-feature`)
111 | 3. Commit your changes (`git commit -am 'Add some feature'`)
112 | 4. Push to the branch (`git push origin my-new-feature`)
113 | 5. Create new Pull Request
114 |
115 | ## Credits
116 |
117 | Thank you [contributors](https://github.com/platanus/angular-pretty-load/graphs/contributors)!
118 |
119 |
120 |
121 | angular-pretty-load is maintained by [platanus](http://platan.us).
122 |
123 | ## License
124 |
125 | © 2015 Platanus, SpA. It is free software and may be redistributed under the terms specified in the LICENSE file.
126 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-pretty-load",
3 | "version": "0.0.7",
4 | "homepage": "https://github.com/platanus/angular-pretty-load",
5 | "authors": [
6 | "Platanus"
7 | ],
8 | "license": "MIT",
9 | "ignore": [
10 | "**/.*",
11 | "node_modules",
12 | "bower_components",
13 | "test",
14 | "tests"
15 | ],
16 | "dependencies": {
17 | "angular": "^1.3.16",
18 | "angular-mocks": "^1.3.16"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/demo/app.js:
--------------------------------------------------------------------------------
1 | angular
2 | .module('prettyLoadDemo', ['platanus.prettyLoad'])
3 |
4 | angular
5 | .module('prettyLoadDemo')
6 | .controller('DemoController', DemoController);
7 |
8 | function DemoController() {
9 | this.images = [
10 | {
11 | width: 1032,
12 | height: 774,
13 | color: '#DB9C49',
14 | url: 'https://i.ytimg.com/vi/m8rVErdtODA/maxresdefault.jpg'
15 | },
16 | {
17 | width: 680,
18 | height: 200,
19 | color: '#E3CCBA',
20 | url: 'http://thedaoofdragonball.com/wp-content/uploads/2013/01/google-glass-dbz-scouter-goku-vegeta.jpg'
21 | },
22 | {
23 | width: 535,
24 | height: 301,
25 | color: '#0F31DC',
26 | url: 'http://www.filmofilia.com/wp-content/uploads/2012/11/dragonball-z-kai.jpg'
27 | },
28 | {
29 | width: 640,
30 | height: 360,
31 | color: '#5E94A0',
32 | url: 'http://i.ytimg.com/vi/EA27-QHe7MA/maxresdefault.jpg'
33 | },
34 | {
35 | width: 300,
36 | height: 450,
37 | color: '#2C2529',
38 | url: 'http://cdn.fontmeme.com/images/Dragon-Ball-Z-TV-Series.jpg'
39 | }
40 | ];
41 | }
42 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Angular Pretty Load Test
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
![]()
29 |
30 |
31 |
32 |
![]()
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/demo/styles.css:
--------------------------------------------------------------------------------
1 | .column-1, .column-2 {
2 | width: 49%;
3 | float: left;
4 | padding: 0.5%;
5 | }
6 |
7 |
8 | .pretty-load-overlay {
9 | opacity: 0;
10 | }
11 |
12 | .pretty-load-loading .pretty-load-overlay {
13 | opacity: 1;
14 | transition: opacity 0.5s ease;
15 | }
16 |
17 | .pretty-load-completed .pretty-load-overlay {
18 | opacity: 0;
19 | transition: opacity 1.7s ease;
20 | }
21 |
22 | .column-1 img {
23 | width: 100%;
24 | }
25 |
26 | .column-2 img {
27 | width: 256px;
28 | height: 144px;
29 | }
30 |
31 | .column-2 .pretty-load-overlay {
32 | background-color: #5E94A0;
33 | }
34 |
--------------------------------------------------------------------------------
/dist/angular-pretty-load.min.js:
--------------------------------------------------------------------------------
1 | !function(){"use strict";angular.module("platanus.prettyLoad",[])}();
2 | !function(){"use strict";function e(){return{restrict:"A",link:function(e,d,l){var c={referenceWidth:l.prettyLoadWidth,referenceHeight:l.prettyLoadHeight,referenceColor:l.prettyLoadColor,shouldSetImageWidth:0==d[0].width,shouldSetImageHeight:0==d[0].height},h=d,s=t(h),u=i(s,c.referenceColor);o(h,c),a(u,h),r(s,h),n(e,h,u,c)}}}function t(e){var t="";e.wrap(t);var i=e.parent();return i.css({position:"relative"}),i}function i(e,t){var i='',r=angular.element(i);return e.append(r),r.css({position:"absolute"}),t&&r.css({backgroundColor:t}),r}function r(e,t){setTimeout(function(){e.addClass(c),l(t,function(){e.removeClass(c),e.addClass(h)})},0)}function n(e,t,i,r){var n=function(){o(t,r),a(i,t)};window.addEventListener("resize",n),e.$on("$destroy",function(){window.removeEventListener("resize",n)})}function o(e,t){var i=e[0];if(t.shouldSetImageWidth&&t.shouldSetImageHeight)i.width=t.referenceWidth,i.height=t.referenceHeight;else if(t.shouldSetImageWidth||t.shouldSetImageHeight){var r=t.referenceWidth/t.referenceHeight;t.shouldSetImageWidth&&(i.width=i.clientHeight*r),t.shouldSetImageHeight&&(i.height=i.clientWidth/r)}}function a(e,t){e.css({top:"0px",left:"0px",width:t[0].clientWidth+"px",height:t[0].clientHeight+"px"})}function d(e){return e.complete?0!==e.naturalWidth:!1}function l(e,t){var i=setInterval(function(){d(e[0])&&(clearInterval(i),t.call())},200)}var c="pretty-load-loading",h="pretty-load-completed";angular.module("platanus.prettyLoad").directive("prettyLoad",e)}();
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp'),
2 | uglify = require('gulp-uglify'),
3 | jshint = require('gulp-jshint'),
4 | concat = require('gulp-concat'),
5 | bump = require('gulp-bump'),
6 | notify = require('gulp-notify'),
7 | git = require('gulp-git'),
8 | size = require('gulp-size'),
9 | ngannotate = require('gulp-ng-annotate'),
10 | npm = require('npm');
11 |
12 | var paths = {
13 | src: ['./src/index.js','./src/*.js'],
14 | dist: ['./dist/*.js'],
15 | };
16 |
17 | var sourceMin = 'angular-pretty-load.min.js';
18 |
19 | gulp.task('lint', function() {
20 | return gulp.src(paths.src)
21 | .pipe(jshint('.jshintrc'))
22 | .pipe(jshint.reporter('jshint-stylish'));
23 | });
24 |
25 | gulp.task('build', ['lint'], function() {
26 | return gulp.src(paths.src)
27 | .pipe(ngannotate())
28 | .pipe(uglify())
29 | .pipe(concat(sourceMin))
30 | .pipe(size())
31 | .pipe(gulp.dest('dist'))
32 | .pipe(notify('Build finished'));
33 | });
34 |
35 | gulp.task('bump', function () {
36 | return gulp.src(['./bower.json', './package.json'])
37 | .pipe(bump({type: gulp.env.type}))
38 | .pipe(gulp.dest('./'));
39 | });
40 |
41 | gulp.task('publish-git', ['bump'], function () {
42 | var pkg = require('./package.json');
43 | var msg = 'Bumps version '+pkg.version;
44 | gulp.src('./*.json')
45 | .pipe(git.add())
46 | .pipe(git.commit(msg));
47 | setTimeout(function () {
48 | git.tag('v'+pkg.version, msg, function(){
49 | git.push('origin', 'master', { args: '--tags' }, function(){});
50 | });
51 | }, 1000);
52 | });
53 |
54 | gulp.task('publish-npm', ['publish-git'], function() {
55 | npm.load({}, function(error) {
56 | if (error) return console.error(error);
57 | npm.commands.publish(['.'], function(error) {
58 | if (error) return console.error(error);
59 | });
60 | });
61 | });
62 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // Generated on Wed Nov 26 2014 10:45:59 GMT-0300 (CLST)
3 |
4 | module.exports = function(config) {
5 | config.set({
6 |
7 | // base path that will be used to resolve all patterns (eg. files, exclude)
8 | basePath: '',
9 |
10 |
11 | // frameworks to use
12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
13 | frameworks: ['jasmine'],
14 |
15 |
16 | // list of files / patterns to load in the browser
17 | files: [
18 | './bower_components/angular/angular.js',
19 | './bower_components/angular-mocks/angular-mocks.js',
20 | './src/index.js',
21 | './src/*.js',
22 | './tests/*.js'
23 | ],
24 |
25 |
26 | // list of files to exclude
27 | exclude: [
28 | ],
29 |
30 |
31 | // preprocess matching files before serving them to the browser
32 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
33 | preprocessors: {
34 | },
35 |
36 |
37 | // test results reporter to use
38 | // possible values: 'dots', 'progress'
39 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter
40 | reporters: ['progress'],
41 |
42 |
43 | // web server port
44 | port: 9876,
45 |
46 |
47 | // enable / disable colors in the output (reporters and logs)
48 | colors: true,
49 |
50 |
51 | // level of logging
52 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
53 | logLevel: config.LOG_INFO,
54 |
55 |
56 | // enable / disable watching file and executing tests whenever any file changes
57 | autoWatch: true,
58 |
59 |
60 | // start these browsers
61 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
62 | browsers: ['PhantomJS'],
63 |
64 |
65 | // Continuous Integration mode
66 | // if true, Karma captures browsers, runs the tests and exits
67 | singleRun: false
68 | });
69 | };
70 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-pretty-load",
3 | "description": "",
4 | "version": "0.0.7",
5 | "devDependencies": {
6 | "jasmine-core": "^2.2.0",
7 | "karma": "^0.12.31",
8 | "karma-jasmine": "^0.3.5",
9 | "karma-phantomjs-launcher": "^0.1.4",
10 | "gulp": "^3.8.10",
11 | "gulp-bump": "^0.1.11",
12 | "gulp-concat": "^2.4.3",
13 | "gulp-git": "^0.5.6",
14 | "gulp-jshint": "^1.9.0",
15 | "gulp-notify": "^2.1.0",
16 | "gulp-size": "^1.2.0",
17 | "gulp-uglify": "^1.1.0",
18 | "jshint-stylish": "^1.0.0",
19 | "gulp-ng-annotate": "^1.0.0",
20 | "npm": "^2.10.1"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/directive.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | 'use strict';
3 |
4 | var INIT_CLASS = 'pretty-load-init';
5 | var LOADING_CLASS = 'pretty-load-loading';
6 | var COMPLETED_CLASS = 'pretty-load-completed';
7 |
8 | angular
9 | .module('platanus.prettyLoad')
10 | .directive('prettyLoad', prettyLoad);
11 |
12 | /* ngInject */
13 | function prettyLoad() {
14 |
15 | return {
16 | restrict: 'A',
17 |
18 |
19 | link: function(_scope, _element, _attributes) {
20 | var settings = {
21 | referenceWidth: _attributes.prettyLoadWidth,
22 | referenceHeight: _attributes.prettyLoadHeight,
23 | referenceColor: _attributes.prettyLoadColor,
24 | shouldSetImageWidth: _element[0].width == 0,
25 | shouldSetImageHeight: _element[0].height == 0
26 | };
27 |
28 | var image = _element;
29 | var wrapper = createWrapper(image);
30 | var overlay = createOverlay(wrapper, settings.referenceColor);
31 |
32 | setImageSize(image, settings);
33 | setOverlayPositionAndSize(overlay, image);
34 |
35 | monitorLoading(wrapper, image);
36 | watchResizeEvent(_scope, image, overlay, settings);
37 | }
38 | }
39 | }
40 |
41 | //// Private Methods
42 |
43 | function createWrapper(_image) {
44 | var html = "";
45 | _image.wrap(html);
46 |
47 | var wrapper = _image.parent();
48 |
49 | wrapper.css({
50 | position: "relative"
51 | });
52 |
53 | return wrapper;
54 | }
55 |
56 | function createOverlay(_wrapper, _referenceColor) {
57 | var html = '';
58 | var overlay = angular.element(html);
59 |
60 | _wrapper.append(overlay);
61 |
62 | overlay.css({
63 | position: 'absolute'
64 | });
65 |
66 | if(_referenceColor)
67 | overlay.css({
68 | backgroundColor: _referenceColor
69 | });
70 |
71 | return overlay;
72 | }
73 |
74 | function monitorLoading(wrapper, image) {
75 | setTimeout(function() {
76 | wrapper.addClass(LOADING_CLASS);
77 |
78 | whenImageIsLoaded(image, function() {
79 | wrapper.removeClass(LOADING_CLASS);
80 | wrapper.addClass(COMPLETED_CLASS);
81 | });
82 | }, 0);
83 | }
84 |
85 | function watchResizeEvent(_scope, _image, _overlay, _settings) {
86 | var callback = function() {
87 | setImageSize(_image, _settings);
88 | setOverlayPositionAndSize(_overlay, _image);
89 | }
90 |
91 | window.addEventListener('resize', callback);
92 |
93 | _scope.$on('$destroy', function() {
94 | window.removeEventListener('resize', callback)
95 | });
96 | }
97 |
98 | function setImageSize(_image, _settings) {
99 | var imageNode = _image[0];
100 |
101 | // should set both sides
102 | if(_settings.shouldSetImageWidth && _settings.shouldSetImageHeight) {
103 | imageNode.width = _settings.referenceWidth;
104 | imageNode.height = _settings.referenceHeight;
105 | }
106 | // should set 1 side
107 | else if(_settings.shouldSetImageWidth || _settings.shouldSetImageHeight) {
108 | var ratio = _settings.referenceWidth / _settings.referenceHeight;
109 |
110 | if(_settings.shouldSetImageWidth) imageNode.width = imageNode.clientHeight * ratio;
111 | if(_settings.shouldSetImageHeight) imageNode.height = imageNode.clientWidth / ratio;
112 | }
113 | };
114 |
115 | function setOverlayPositionAndSize(_overlay, _image) {
116 | _overlay.css({
117 | top: '0px',
118 | left: '0px',
119 | width: _image[0].clientWidth + 'px',
120 | height: _image[0].clientHeight + 'px'
121 | });
122 | }
123 |
124 | function checkLoaded(_image) {
125 | if (!_image.complete) return false;
126 | if (_image.naturalWidth === 0) return false;
127 | return true;
128 | };
129 |
130 | function whenImageIsLoaded(_image, _callback) {
131 | var checkInterval = setInterval(function(){
132 | if ( checkLoaded(_image[0]) ) {
133 | clearInterval(checkInterval);
134 | _callback.call();
135 | }
136 | }, 200);
137 | };
138 |
139 | })();
140 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | 'use strict';
3 |
4 | angular.module('platanus.prettyLoad', [])
5 |
6 | })();
7 |
--------------------------------------------------------------------------------
/tests/test-example.js:
--------------------------------------------------------------------------------
1 | describe('Tests', function(){
2 | beforeEach(function(){
3 | // ...
4 | });
5 |
6 | it('should do something', function(){
7 | expect(true).toBe(true);
8 | });
9 | });
10 |
--------------------------------------------------------------------------------