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