├── .bowerrc ├── .editorconfig ├── .ember-cli ├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── .watchmanconfig ├── LICENSE.md ├── README.md ├── addon ├── components │ ├── lazy-background-image.js │ └── lazy-image.js ├── lib │ └── cache.js └── mixins │ ├── image-load.js │ └── lazy-image.js ├── app ├── components │ ├── lazy-background-image.js │ └── lazy-image.js └── templates │ └── components │ ├── lazy-background-image.hbs │ └── lazy-image.hbs ├── bower.json ├── config ├── ember-try.js └── environment.js ├── ember-cli-build.js ├── index.js ├── package.json ├── testem.json ├── tests ├── .jshintrc ├── dummy │ ├── app │ │ ├── app.js │ │ ├── components │ │ │ └── .gitkeep │ │ ├── controllers │ │ │ └── .gitkeep │ │ ├── helpers │ │ │ └── .gitkeep │ │ ├── index.html │ │ ├── models │ │ │ └── .gitkeep │ │ ├── router.js │ │ ├── routes │ │ │ ├── .gitkeep │ │ │ └── index.js │ │ ├── styles │ │ │ ├── .gitkeep │ │ │ └── app.css │ │ ├── templates │ │ │ ├── application.hbs │ │ │ ├── components │ │ │ │ └── .gitkeep │ │ │ └── index.hbs │ │ └── views │ │ │ └── .gitkeep │ ├── config │ │ └── environment.js │ └── public │ │ ├── crossdomain.xml │ │ └── robots.txt ├── helpers │ ├── resolver.js │ └── start-app.js ├── index.html ├── test-helper.js └── unit │ └── components │ └── lazy-image-test.js └── vendor └── lazy-image └── lazy-image.css /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "analytics": false 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.js] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.hbs] 21 | insert_final_newline = false 22 | indent_style = space 23 | indent_size = 2 24 | 25 | [*.css] 26 | indent_style = space 27 | indent_size = 2 28 | 29 | [*.html] 30 | indent_style = space 31 | indent_size = 2 32 | 33 | [*.{diff,md}] 34 | trim_trailing_whitespace = false 35 | -------------------------------------------------------------------------------- /.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | /bower_components 10 | 11 | # misc 12 | /.sass-cache 13 | /connect.lock 14 | /coverage/* 15 | /libpeerconnection.log 16 | npm-debug.log 17 | testem.log 18 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "-Promise" 6 | ], 7 | "browser": true, 8 | "boss": true, 9 | "curly": true, 10 | "debug": false, 11 | "devel": true, 12 | "eqeqeq": true, 13 | "evil": true, 14 | "forin": false, 15 | "immed": false, 16 | "laxbreak": false, 17 | "newcap": true, 18 | "noarg": true, 19 | "noempty": false, 20 | "nonew": false, 21 | "nomen": false, 22 | "onevar": false, 23 | "plusplus": false, 24 | "regexp": false, 25 | "undef": true, 26 | "sub": true, 27 | "strict": false, 28 | "white": false, 29 | "eqnull": true, 30 | "esnext": true, 31 | "unused": true 32 | } 33 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | tests/ 3 | tmp/ 4 | dist/ 5 | 6 | .bowerrc 7 | .editorconfig 8 | .ember-cli 9 | .travis.yml 10 | .npmignore 11 | **/.gitkeep 12 | bower.json 13 | ember-cli-build.js 14 | Brocfile.js 15 | testem.json 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | - "0.12" 5 | 6 | sudo: false 7 | 8 | cache: 9 | directories: 10 | - node_modules 11 | 12 | env: 13 | matrix: 14 | - EMBER_TRY_SCENARIO=ember-release 15 | - EMBER_TRY_SCENARIO=ember-beta 16 | - EMBER_TRY_SCENARIO=ember-canary 17 | 18 | matrix: 19 | fast_finish: true 20 | allow_failures: 21 | - env: EMBER_TRY_SCENARIO=ember-beta 22 | - env: EMBER_TRY_SCENARIO=ember-canary 23 | 24 | before_install: 25 | - "npm config set spin false" 26 | - "npm install -g npm@^2" 27 | 28 | install: 29 | - npm install -g bower 30 | - npm install 31 | - bower install 32 | 33 | script: 34 | - ember try $EMBER_TRY_SCENARIO test 35 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp"] 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ember-lazy-image 2 | 3 | [![Build Status](https://travis-ci.org/twokul/ember-lazy-image.svg)](https://travis-ci.org/twokul/ember-lazy-image) 4 | 5 | `ember-lazy-image` is a component that allows you to gracefully handle image loading. 6 | 7 | Component will load images lazily, only if they appeared in the view port. 8 | This optimization brings page load time down. 9 | 10 | Default loading placeholder is stolen from [aurer](https://github.com/aurer) and his awesome [codepen](http://codepen.io/aurer/pen/jEGbA). 11 | 12 | ### Installation 13 | 14 | From inside your ember-cli project, run the following: 15 | 16 | ```bash 17 | ember install ember-lazy-image 18 | ``` 19 | 20 | #### Installation for ember-cli 0.1.5 - 0.2.2 21 | 22 | ```bash 23 | ember install:addon ember-lazy-image 24 | ``` 25 | 26 | #### Installation without ember-cli 27 | 28 | ```bash 29 | npm install ember-lazy-image --save 30 | ``` 31 | 32 | ### lazy-image 33 | 34 | ```hbs 35 | {{lazy-image url='http://my-valid-url.com/foo.jpg'}} 36 | ``` 37 | 38 | Component will wait until the image is loaded and while waiting it will show default 39 | loading placeholder (see above). 40 | 41 | You can customize `loading` placeholder by passing it as an parameter: 42 | 43 | ```hbs 44 | {{#lazy-image url='http://my-valid-url.com/foo.jpg'}} 45 | 46 | {{/lazy-image}} 47 | ``` 48 | 49 | You can also define the fallback if the image failed to load. By default, component will render 50 | `Image failed to load` text. 51 | 52 | You can customize `error` text by passing it as an parameter: 53 | 54 | ```hbs 55 | {{lazy-image url='http://my-not-valid-url.com/foo.jpg' errorText='Something went wrong.'}} 56 | ``` 57 | 58 | ### `width`, `height` and `data-*` attributes 59 | 60 | Lazy Image supports `width`, `height` and `data-*` attribute bindings. 61 | 62 | ```hbs 63 | {{lazy-image url='http://my-valid-url.com/foo.jpg' width=400 height=400 data-foo-bar="my-foo-bar"}} 64 | {{lazy-image url='http://my-valid-url.com/foo.jpg' width=400 height=400 data-foo-bar=foo.bar.path}} 65 | ``` 66 | 67 | ### `class` attribute 68 | 69 | You can also pass class names for the image element. 70 | 71 | ```hbs 72 | {{lazy-image url='http://my-valid-url.com/foo.jpg' class='foo-bar baz-bar'}} 73 | ``` 74 | 75 | ### `alt` attribute 76 | 77 | You can pass the alt attribute to the component and it will be rendered on the image element 78 | 79 | ```hbs 80 | {{lazy-image url='http://my-valid-url.com/foo.jpg' alt='foo description'}} 81 | ``` 82 | 83 | ### ember-in-viewport options 84 | 85 | Lazy Image uses [ember-in-viewport](https://github.com/dockyard/ember-in-viewport/) for viewport detection. Due to the way listeners and `requestAnimationFrame` is setup, you'll have to override the `ember-in-viewport` options by creating `app/components/lazy-image.js`: 86 | 87 | ```js 88 | // app/components/lazy-image.js 89 | 90 | import Ember from 'ember'; 91 | import LazyImage from 'ember-lazy-image/components/lazy-image'; 92 | 93 | export default LazyImage.extend({ 94 | viewportOptionsOverride: Ember.on('didInsertElement', function() { 95 | Ember.setProperties(this, { 96 | viewportUseRAF : true, 97 | viewportSpy : false, 98 | viewportRefreshRate : 150, 99 | viewportTolerance: { 100 | top : 50, 101 | bottom : 50, 102 | left : 20, 103 | right : 20 104 | } 105 | }); 106 | }) 107 | }); 108 | ``` 109 | 110 | See [Advanced usage (options)](https://github.com/dockyard/ember-in-viewport/tree/1.0.0#advanced-usage-options) for more in detail `ember-in-viewport` options. 111 | 112 | The use of `threshold` is deprecated in favor of `viewportTolerance`. 113 | 114 | ### Experimental `lazy-background-image` 115 | 116 | This is an experimental component that will add `background-image` style attribute to a component's `div`. It also 117 | sets `min-height` attribute to `270px` so the image is visible. You should be able to overwrite it by using `lazy-background-image` class. 118 | 119 | ## Installation 120 | 121 | * `git clone` this repository 122 | * `npm install` 123 | * `bower install` 124 | 125 | ## Running 126 | 127 | * `ember server` 128 | * Visit your app at http://localhost:4200. 129 | 130 | ## Running Tests 131 | 132 | * `ember test` 133 | * `ember test --server` 134 | 135 | ## Building 136 | 137 | * `ember build` 138 | 139 | For more information on using ember-cli, visit [http://www.ember-cli.com/](http://www.ember-cli.com/). 140 | -------------------------------------------------------------------------------- /addon/components/lazy-background-image.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import ImageLoadMixin from '../mixins/image-load'; 3 | import LazyImageMixin from '../mixins/lazy-image'; 4 | import InViewportMixin from 'ember-in-viewport'; 5 | 6 | const { htmlSafe } = Ember.String; 7 | const { computed, Component } = Ember; 8 | 9 | export default Component.extend(InViewportMixin, ImageLoadMixin, LazyImageMixin, { 10 | attributeBindings: ['width', 'height', 'style'], 11 | 12 | style: computed('lazyUrl', 'opacity', function() { 13 | const opacity = this.get('opacity'); 14 | const style = opacity ? `background-image: linear-gradient(rgba(0, 0, 0, ${opacity}), rgba(0, 0, 0, ${opacity})), url(${this.get('lazyUrl')})` : `background-image: url(${this.get('lazyUrl')})`; 15 | 16 | return htmlSafe(style); 17 | }), 18 | 19 | classNames: ['lazy-background-image'], 20 | 21 | _setupAttributes() { 22 | const component = this; 23 | const keys = Object.keys || Ember.keys; 24 | 25 | keys(component).forEach((key) => { 26 | if (key.substr(0, 5) === 'data-' && !key.match(/Binding$/)) { 27 | component.get('attributeBindings').pushObject(key); 28 | } 29 | }); 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /addon/components/lazy-image.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import ImageLoadMixin from '../mixins/image-load'; 3 | import LazyImageMixin from '../mixins/lazy-image'; 4 | import InViewportMixin from 'ember-in-viewport'; 5 | 6 | const { on, get, set, computed, Component } = Ember; 7 | 8 | export default Component.extend(InViewportMixin, ImageLoadMixin, LazyImageMixin, { 9 | classNames: ['lazy-image-container'], 10 | 11 | concatenatedProperties: ['class'], 12 | 13 | class: ['lazy-image'], 14 | 15 | _classJoin: on('init', function() { 16 | const classArray = get(this, 'class'); 17 | set(this, 'class', classArray.join(' ')); 18 | }), 19 | 20 | _setupAttributes() { 21 | const img = this.$('img'); 22 | const component = this; 23 | const keys = Object.keys || Ember.keys; 24 | 25 | keys(component).forEach((key) => { 26 | if (key.substr(0, 5) === 'data-' && !key.match(/Binding$/)) { 27 | img.attr(key, component.get(key)); 28 | } 29 | }); 30 | }, 31 | 32 | useDimensionsAttrs: computed('width', 'height', function() { 33 | return !this.get('width') || !this.get('height') ? false : true; 34 | }) 35 | }); 36 | -------------------------------------------------------------------------------- /addon/lib/cache.js: -------------------------------------------------------------------------------- 1 | import StorageObject from 'ember-local-storage/session/object'; 2 | 3 | export default StorageObject.extend({ 4 | storageKey: 'ember-lazy-images' 5 | }); -------------------------------------------------------------------------------- /addon/mixins/image-load.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | const { on, set, run, Mixin, computed, getWithDefault } = Ember; 4 | 5 | export default Mixin.create({ 6 | loaded: false, 7 | errorThrown: false, 8 | 9 | classNameBindings: ['loaded', 'errorThrown'], 10 | 11 | defaultErrorText: computed('errorText', function() { 12 | return getWithDefault(this, 'errorText', 'Image failed to load'); 13 | }), 14 | 15 | _resolveImage: on('didRender', function() { 16 | const component = this; 17 | const image = component.$('img'); 18 | const isCached = image[0].complete; 19 | 20 | if (!isCached) { 21 | image.one('load', () => { 22 | image.off('error'); 23 | run.schedule('afterRender', component, () => set(component, 'loaded', true)); 24 | }); 25 | 26 | image.one('error', () => { 27 | image.off('load'); 28 | run.schedule('afterRender', component, () => set(component, 'errorThrown', true)); 29 | }); 30 | } else { 31 | run.schedule('afterRender', component, () => set(component, 'loaded', true)); 32 | } 33 | }) 34 | }); 35 | -------------------------------------------------------------------------------- /addon/mixins/lazy-image.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Cache from '../lib/cache'; 3 | 4 | const { on, get, set, Mixin, computed, setProperties } = Ember; 5 | const dasherize = Ember.String.dasherize; 6 | 7 | export default Mixin.create({ 8 | didInsertElement() { 9 | setProperties(this, { 10 | viewportScrollSensitivity: 20, 11 | viewportListeners: [ 12 | { context: window, event: 'scroll.scrollable' }, 13 | { context: window, event: 'resize.resizable' }, 14 | { context: document, event: 'touchmove.scrollable' } 15 | ] 16 | }); 17 | 18 | this._super(...arguments); 19 | }, 20 | 21 | _cache: Cache.create(), 22 | 23 | lazyUrl: null, 24 | 25 | handleDidRender: on('didRender', function() { 26 | this._setupAttributes(); 27 | }), 28 | 29 | handleImageUrl: on('didInitAttrs', function() { 30 | this._setImageUrl(); 31 | }), 32 | 33 | _setImageUrl: on('didEnterViewport', function() { 34 | const url = get(this, 'url'); 35 | const cache = get(this, '_cache'); 36 | const lazyUrl = get(this, 'lazyUrl'); 37 | const cacheKey = get(this, '_cacheKey'); 38 | const viewportEntered = get(this, 'viewportEntered'); 39 | 40 | if (cacheKey && get(cache, cacheKey)) { 41 | set(this, 'lazyUrl', url); 42 | } 43 | 44 | if (viewportEntered && lazyUrl === null) { 45 | set(this, 'lazyUrl', url); 46 | 47 | if (cacheKey) { 48 | set(cache, cacheKey, true); 49 | } 50 | } 51 | }), 52 | 53 | _cacheKey: computed('url', function() { 54 | var url = this.get('url'); 55 | var key; 56 | 57 | if (url) { 58 | key = dasherize(url.replace(/^http[s]?\:\/\/|\.|\//g, '')); 59 | } 60 | 61 | if (key) { 62 | return key; 63 | } 64 | }) 65 | }); 66 | -------------------------------------------------------------------------------- /app/components/lazy-background-image.js: -------------------------------------------------------------------------------- 1 | import LazyBackgroundImage from 'ember-lazy-image/components/lazy-background-image'; 2 | export default LazyBackgroundImage; 3 | -------------------------------------------------------------------------------- /app/components/lazy-image.js: -------------------------------------------------------------------------------- 1 | import LazyImage from 'ember-lazy-image/components/lazy-image'; 2 | export default LazyImage; 3 | -------------------------------------------------------------------------------- /app/templates/components/lazy-background-image.hbs: -------------------------------------------------------------------------------- 1 | {{#if hasBlock}} 2 | {{#if errorThrown}} 3 |
{{defaultErrorText}}
4 | {{else}} 5 |
{{yield}}
6 | {{/if}} 7 | {{else}} 8 | {{#if errorThrown}} 9 |
{{defaultErrorText}}
10 | {{else}} 11 |
12 | 14 | 17 | 19 | 26 | 27 | 28 |
29 | {{/if}} 30 | {{/if}} 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/templates/components/lazy-image.hbs: -------------------------------------------------------------------------------- 1 | {{#if hasBlock}} 2 | {{#if errorThrown}} 3 |
{{defaultErrorText}}
4 | {{else}} 5 |
{{yield}}
6 | {{/if}} 7 | {{else}} 8 | {{#if errorThrown}} 9 |
{{defaultErrorText}}
10 | {{else}} 11 |
12 | 14 | 17 | 19 | 26 | 27 | 28 |
29 | {{/if}} 30 | {{/if}} 31 | 32 | {{#if useDimensionsAttrs}} 33 | {{unbound alt}} 34 | {{else}} 35 | {{unbound alt}} 36 | {{/if}} 37 | 38 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-lazy-image", 3 | "dependencies": { 4 | "jquery": "^2.1.4", 5 | "ember": "1.13.10", 6 | "ember-resolver": "~0.1.21", 7 | "loader.js": "ember-cli/loader.js#3.2.1", 8 | "ember-cli-shims": "ember-cli/ember-cli-shims#0.0.5", 9 | "ember-cli-test-loader": "ember-cli/ember-cli-test-loader#0.1.3", 10 | "ember-load-initializers": "ember-cli/ember-load-initializers#0.1.5", 11 | "ember-qunit": "0.4.15", 12 | "ember-qunit-notifications": "0.1.0", 13 | "qunit": "~1.20.0" 14 | }, 15 | "resolutions": { 16 | "qunit-notifications": "~0.1.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /config/ember-try.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 3 | module.exports = { 4 | scenarios: [{ 5 | name: 'ember-release', 6 | dependencies: { 7 | 'ember': 'components/ember#release' 8 | }, 9 | resolutions: { 10 | 'ember': 'release' 11 | } 12 | }, { 13 | name: 'ember-beta', 14 | dependencies: { 15 | 'ember': 'components/ember#beta' 16 | }, 17 | resolutions: { 18 | 'ember': 'beta' 19 | } 20 | }, { 21 | name: 'ember-canary', 22 | dependencies: { 23 | 'ember': 'components/ember#canary' 24 | }, 25 | resolutions: { 26 | 'ember': 'canary' 27 | } 28 | }] 29 | }; 30 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(/* environment, appConfig */) { 4 | return { }; 5 | }; 6 | -------------------------------------------------------------------------------- /ember-cli-build.js: -------------------------------------------------------------------------------- 1 | /* global require, module */ 2 | var EmberApp = require('ember-cli/lib/broccoli/ember-addon'); 3 | 4 | module.exports = function(defaults) { 5 | var app = new EmberApp(defaults, { 6 | // Add options here 7 | }); 8 | 9 | /* 10 | This build file specifes the options for the dummy test app of this 11 | addon, located in `/tests/dummy` 12 | This build file does *not* influence how the addon or the app using it 13 | behave. You most likely want to be modifying `./index.js` or app's build file 14 | */ 15 | 16 | return app.toTree(); 17 | }; 18 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 'use strict'; 3 | 4 | module.exports = { 5 | name: 'ember-lazy-image', 6 | 7 | included: function emberLazyImageIncluded(app) { 8 | this._super.included(app); 9 | 10 | app.import('vendor/lazy-image/lazy-image.css'); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-lazy-image", 3 | "version": "0.0.14", 4 | "description": "Lazy image loading for Ember apps", 5 | "directories": { 6 | "doc": "doc", 7 | "test": "tests" 8 | }, 9 | "scripts": { 10 | "start": "ember server", 11 | "build": "ember build", 12 | "test": "ember try:testall" 13 | }, 14 | "repository": "https://github.com/twokul/ember-lazy-image/", 15 | "engines": { 16 | "node": ">= 0.10.0" 17 | }, 18 | "author": "Alex Navasardyan", 19 | "license": "MIT", 20 | "devDependencies": { 21 | "broccoli-asset-rev": "^2.3.0", 22 | "ember-cli": "1.13.8", 23 | "ember-cli-app-version": "1.0.0", 24 | "ember-cli-content-security-policy": "0.4.0", 25 | "ember-cli-dependency-checker": "^1.1.0", 26 | "ember-cli-htmlbars": "1.0.1", 27 | "ember-cli-htmlbars-inline-precompile": "^0.3.1", 28 | "ember-cli-ic-ajax": "0.2.1", 29 | "ember-cli-inject-live-reload": "^1.3.1", 30 | "ember-cli-qunit": "1.0.3", 31 | "ember-cli-uglify": "^1.2.0", 32 | "ember-disable-prototype-extensions": "^1.0.0", 33 | "ember-export-application-global": "^1.0.5", 34 | "ember-try": "0.0.8", 35 | "phantomjs": "^1.9.18" 36 | }, 37 | "dependencies": { 38 | "ember-cli-babel": "^5.0.0", 39 | "ember-in-viewport": "^2.0.4", 40 | "ember-local-storage": "0.0.5" 41 | }, 42 | "keywords": [ 43 | "ember-addon", 44 | "image", 45 | "lazy load" 46 | ], 47 | "ember-addon": { 48 | "configPath": "tests/dummy/config" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /testem.json: -------------------------------------------------------------------------------- 1 | { 2 | "framework": "qunit", 3 | "test_page": "tests/index.html?hidepassed", 4 | "disable_watching": true, 5 | "launch_in_ci": [ 6 | "PhantomJS" 7 | ], 8 | "launch_in_dev": [ 9 | "PhantomJS", 10 | "Chrome" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /tests/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "location", 6 | "setTimeout", 7 | "$", 8 | "-Promise", 9 | "define", 10 | "console", 11 | "visit", 12 | "exists", 13 | "fillIn", 14 | "click", 15 | "keyEvent", 16 | "triggerEvent", 17 | "find", 18 | "findWithAssert", 19 | "wait", 20 | "DS", 21 | "andThen", 22 | "currentURL", 23 | "currentPath", 24 | "currentRouteName" 25 | ], 26 | "node": false, 27 | "browser": false, 28 | "boss": true, 29 | "curly": false, 30 | "debug": false, 31 | "devel": false, 32 | "eqeqeq": true, 33 | "evil": true, 34 | "forin": false, 35 | "immed": false, 36 | "laxbreak": false, 37 | "newcap": true, 38 | "noarg": true, 39 | "noempty": false, 40 | "nonew": false, 41 | "nomen": false, 42 | "onevar": false, 43 | "plusplus": false, 44 | "regexp": false, 45 | "undef": true, 46 | "sub": true, 47 | "strict": false, 48 | "white": false, 49 | "eqnull": true, 50 | "esnext": true 51 | } 52 | -------------------------------------------------------------------------------- /tests/dummy/app/app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Resolver from 'ember/resolver'; 3 | import loadInitializers from 'ember/load-initializers'; 4 | import config from './config/environment'; 5 | 6 | var App; 7 | 8 | Ember.MODEL_FACTORY_INJECTIONS = true; 9 | 10 | App = Ember.Application.extend({ 11 | modulePrefix: config.modulePrefix, 12 | podModulePrefix: config.podModulePrefix, 13 | Resolver: Resolver 14 | }); 15 | 16 | loadInitializers(App, config.modulePrefix); 17 | 18 | export default App; 19 | -------------------------------------------------------------------------------- /tests/dummy/app/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twokul/ember-lazy-image/6611fa76d9c8342e94853060c4656d9cebaed3c8/tests/dummy/app/components/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twokul/ember-lazy-image/6611fa76d9c8342e94853060c4656d9cebaed3c8/tests/dummy/app/controllers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twokul/ember-lazy-image/6611fa76d9c8342e94853060c4656d9cebaed3c8/tests/dummy/app/helpers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy 7 | 8 | 9 | 10 | {{content-for 'head'}} 11 | 12 | 13 | 14 | 15 | {{content-for 'head-footer'}} 16 | 17 | 18 | {{content-for 'body'}} 19 | 20 | 21 | 22 | 23 | {{content-for 'body-footer'}} 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/dummy/app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twokul/ember-lazy-image/6611fa76d9c8342e94853060c4656d9cebaed3c8/tests/dummy/app/models/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/router.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import config from './config/environment'; 3 | 4 | var Router = Ember.Router.extend({ 5 | location: config.locationType 6 | }); 7 | 8 | export default Router.map(function() { 9 | }); 10 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twokul/ember-lazy-image/6611fa76d9c8342e94853060c4656d9cebaed3c8/tests/dummy/app/routes/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/routes/index.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | 3 | export default Ember.Route.extend({ 4 | model: function() { 5 | return Ember.A([{ 6 | text: 'Yehuda Katz', 7 | url: 'http://emberjs.com/images/team/ykatz.jpg' 8 | }, { 9 | text: 'Tom Dale', 10 | url: 'http://emberjs.com/images/team/tdale.jpg' 11 | }, { 12 | text: 'Peter Wagenet', 13 | url: 'http://emberjs.com/images/team/pwagenet.jpg' 14 | }, { 15 | text: 'Trek Glowacki', 16 | url: 'http://emberjs.com/images/team/tglowaki.jpg' 17 | }, { 18 | text: 'Erik Bryn', 19 | url: 'http://emberjs.com/images/team/ebryn.jpg' 20 | }, { 21 | text: 'Kris Selden', 22 | url: 'http://emberjs.com/images/team/kselden.jpg' 23 | }, { 24 | text: 'Stefan Penner', 25 | url: 'http://emberjs.com/images/team/spenner.jpg' 26 | }, { 27 | text: 'Leah Silber', 28 | url: 'http://emberjs.com/images/team/lsilber.jpg' 29 | }, { 30 | text: 'Alex Matchneer', 31 | url: 'http://emberjs.com/images/team/amatchneer.jpg' 32 | }, { 33 | text: 'Robert Jackson', 34 | url: 'http://emberjs.com/images/team/rjackson.jpg' 35 | }, { 36 | text: 'Igor Terzic', 37 | url: 'http://emberjs.com/images/team/iterzic.jpeg' 38 | }]); 39 | } 40 | }); 41 | -------------------------------------------------------------------------------- /tests/dummy/app/styles/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twokul/ember-lazy-image/6611fa76d9c8342e94853060c4656d9cebaed3c8/tests/dummy/app/styles/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/styles/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twokul/ember-lazy-image/6611fa76d9c8342e94853060c4656d9cebaed3c8/tests/dummy/app/styles/app.css -------------------------------------------------------------------------------- /tests/dummy/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 |

Welcome to Ember.js

2 | 3 | {{outlet}} 4 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twokul/ember-lazy-image/6611fa76d9c8342e94853060c4656d9cebaed3c8/tests/dummy/app/templates/components/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/templates/index.hbs: -------------------------------------------------------------------------------- 1 | {{#each model as |person|}} 2 | {{lazy-image url=person.url alt=person.text}} 3 | {{/each}} 4 | -------------------------------------------------------------------------------- /tests/dummy/app/views/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twokul/ember-lazy-image/6611fa76d9c8342e94853060c4656d9cebaed3c8/tests/dummy/app/views/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/config/environment.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 3 | module.exports = function(environment) { 4 | var ENV = { 5 | modulePrefix: 'dummy', 6 | environment: environment, 7 | baseURL: '/', 8 | locationType: 'auto', 9 | EmberENV: { 10 | FEATURES: { 11 | // Here you can enable experimental features on an ember canary build 12 | // e.g. 'with-controller': true 13 | } 14 | }, 15 | contentSecurityPolicy: { 16 | 'img-src': "'self' emberjs.com", 17 | 'style-src': "'self' 'unsafe-inline'" 18 | }, 19 | 20 | APP: { 21 | // Here you can pass flags/options to your application instance 22 | // when it is created 23 | }, 24 | 25 | viewportConfig: { 26 | viewportUseRAF: false 27 | } 28 | }; 29 | 30 | if (environment === 'development') { 31 | // ENV.APP.LOG_RESOLVER = true; 32 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 33 | // ENV.APP.LOG_TRANSITIONS = true; 34 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 35 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 36 | } 37 | 38 | if (environment === 'test') { 39 | // Testem prefers this... 40 | ENV.baseURL = '/'; 41 | ENV.locationType = 'none'; 42 | 43 | ENV.EmberENV.RAISE_ON_DEPRECATION = true; 44 | 45 | // keep test console output quieter 46 | ENV.APP.LOG_ACTIVE_GENERATION = false; 47 | ENV.APP.LOG_VIEW_LOOKUPS = false; 48 | 49 | ENV.APP.rootElement = '#ember-testing'; 50 | } 51 | 52 | if (environment === 'production') { 53 | 54 | } 55 | 56 | return ENV; 57 | }; 58 | -------------------------------------------------------------------------------- /tests/dummy/public/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /tests/dummy/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /tests/helpers/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember/resolver'; 2 | import config from '../../config/environment'; 3 | 4 | var resolver = Resolver.create(); 5 | 6 | resolver.namespace = { 7 | modulePrefix: config.modulePrefix, 8 | podModulePrefix: config.podModulePrefix 9 | }; 10 | 11 | export default resolver; 12 | -------------------------------------------------------------------------------- /tests/helpers/start-app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Application from '../../app'; 3 | import Router from '../../router'; 4 | import config from '../../config/environment'; 5 | 6 | export default function startApp(attrs) { 7 | var application; 8 | 9 | var attributes = Ember.merge({}, config.APP); 10 | attributes = Ember.merge(attributes, attrs); // use defaults, but you can override; 11 | 12 | Ember.run(function() { 13 | application = Application.create(attributes); 14 | application.setupForTesting(); 15 | application.injectTestHelpers(); 16 | }); 17 | 18 | return application; 19 | } 20 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy Tests 7 | 8 | 9 | 10 | {{content-for 'head'}} 11 | {{content-for 'test-head'}} 12 | 13 | 14 | 15 | 16 | 17 | {{content-for 'head-footer'}} 18 | {{content-for 'test-head-footer'}} 19 | 20 | 21 | 22 | {{content-for 'body'}} 23 | {{content-for 'test-body'}} 24 | 25 | 26 | 27 | 28 | 29 | 30 | {{content-for 'body-footer'}} 31 | {{content-for 'test-body-footer'}} 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import resolver from './helpers/resolver'; 2 | import { 3 | setResolver 4 | } from 'ember-qunit'; 5 | 6 | setResolver(resolver); 7 | -------------------------------------------------------------------------------- /tests/unit/components/lazy-image-test.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { 3 | moduleForComponent, 4 | test 5 | } from 'ember-qunit'; 6 | 7 | import Cache from 'ember-lazy-image/lib/cache'; 8 | import hbs from 'htmlbars-inline-precompile'; 9 | 10 | moduleForComponent('lazy-image', 'LazyImageComponent', { 11 | unit: true, 12 | 13 | beforeEach() { 14 | window.sessionStorage.clear(); 15 | } 16 | }); 17 | 18 | const { run } = Ember; 19 | const get = Ember.get; 20 | 21 | const imageSelector = '.lazy-image'; 22 | const placeholderSelector = '.lazy-image-placeholder'; 23 | const errorMessageSelector = '.lazy-image-error-message'; 24 | const imageContainerSelector = '.lazy-image-container'; 25 | 26 | test('it has correct defaults', function(assert) { 27 | assert.expect(5); 28 | 29 | const component = this.subject(); 30 | 31 | assert.equal(get(component, 'loaded'), false); 32 | assert.equal(get(component, 'errorThrown'), false); 33 | assert.equal(get(component, 'lazyUrl'), null); 34 | assert.equal(get(component, 'defaultErrorText'), 'Image failed to load'); 35 | assert.equal(get(component, 'class'), 'lazy-image'); 36 | }); 37 | 38 | test('it renders default placeholder', function(assert) { 39 | assert.expect(1); 40 | 41 | this.render(hbs`{{lazy-image}}`); 42 | 43 | assert.ok(this.$(placeholderSelector).length > 0, 'placeholder is correctly rendered'); 44 | }); 45 | 46 | test('it renders default error message if image fails to load', function(assert) { 47 | assert.expect(2); 48 | 49 | const component = this.subject({ 50 | errorThrown: true 51 | }); 52 | 53 | this.render(); 54 | 55 | assert.ok(component.$(errorMessageSelector).length > 0, 'error message is correctly rendered'); 56 | assert.ok(component.$(errorMessageSelector + ':contains("' + 'Image failed to load' + '")'), 'default error message is rendered correctly'); 57 | }); 58 | 59 | test('it leverages cache', function(assert) { 60 | run(() => { 61 | Cache.create(); 62 | }); 63 | 64 | assert.expect(1); 65 | 66 | const component = this.subject({ 67 | url: 'http://emberjs.com/images/team/tdale.jpg' 68 | }); 69 | 70 | this.render(); 71 | 72 | run(() => { 73 | component.set('viewportEntered', true); 74 | component.trigger('didEnterViewport'); 75 | }); 76 | 77 | let lazyImages = window.sessionStorage['ember-lazy-images']; 78 | let cache = lazyImages ? JSON.parse(lazyImages) : lazyImages; 79 | 80 | assert.deepEqual(cache, { 81 | emberjscomimagesteamtdalejpg: true 82 | }); 83 | }); 84 | 85 | test('`width` and `height` bindings work correctly', function(assert) { 86 | assert.expect(2); 87 | 88 | const component = this.subject({ 89 | width: 400, 90 | height: 400 91 | }); 92 | 93 | this.render(); 94 | 95 | assert.equal(component.$('img').attr('width'), 400, 'width is correct'); 96 | assert.equal(component.$('img').attr('height'), 400, 'height is correct'); 97 | }); 98 | 99 | test('`width` and `height` are not used if set to 0 or unset', function(assert) { 100 | assert.expect(2); 101 | 102 | const component = this.subject({ 103 | width: 400 104 | }); 105 | 106 | this.render(); 107 | 108 | assert.equal(component.$('img').attr('width'), undefined, 'width is not used'); 109 | assert.equal(component.$('img').attr('height'), undefined, 'height is not used'); 110 | }); 111 | 112 | test('`data-*` attribute bindings work correctly', function(assert) { 113 | assert.expect(1); 114 | 115 | const component = this.subject({ 116 | 'data-person-id': 1234 117 | }); 118 | 119 | this.render(); 120 | 121 | assert.equal(component.$('img').attr('data-person-id'), 1234, 'data attribute is correct'); 122 | }); 123 | 124 | test('passing class names for the element', function(assert) { 125 | assert.expect(1); 126 | 127 | const component = this.subject({ 128 | class: 'img-responsive image-thumbnail' 129 | }); 130 | 131 | this.render(); 132 | 133 | const expected = 'lazy-image img-responsive image-thumbnail'; 134 | assert.equal(component.$('img').attr('class'), expected); 135 | }); 136 | 137 | test('passing alt attribute for the element', function(assert) { 138 | assert.expect(1); 139 | 140 | const component = this.subject({ 141 | alt: 'alternate text' 142 | }); 143 | 144 | this.render(); 145 | 146 | const expected = 'alternate text'; 147 | assert.equal(component.$('img').attr('alt'), expected); 148 | }); 149 | -------------------------------------------------------------------------------- /vendor/lazy-image/lazy-image.css: -------------------------------------------------------------------------------- 1 | .lazy-image-container { 2 | position: relative; 3 | min-height: 200px; 4 | } 5 | 6 | .lazy-image-container img { 7 | z-index: 0; 8 | opacity: 0; 9 | } 10 | 11 | .lazy-image-container.loaded img { 12 | opacity: 1; 13 | } 14 | 15 | .lazy-image-error-message { 16 | position: absolute; 17 | top: 50%; 18 | right: 50%; 19 | } 20 | 21 | .lazy-image-container.loaded .lazy-image-error-message { 22 | display: none; 23 | } 24 | 25 | .lazy-image-container.loaded .lazy-image-placeholder { 26 | display: none; 27 | } 28 | 29 | .lazy-background-image.loaded .lazy-image-placeholder { 30 | display: none; 31 | } 32 | 33 | .lazy-image-placeholder { 34 | position: absolute; 35 | top: 50%; 36 | right: 50%; 37 | } 38 | 39 | .lazy-background-image { 40 | background-size: cover; 41 | background-repeat: no-repeat; 42 | min-height: 270px; 43 | } 44 | --------------------------------------------------------------------------------