├── .babelrc ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── assets ├── 1.jpg ├── 2.jpg ├── 3.jpg ├── 4-l.jpg ├── 4-m.jpg ├── 4.jpg ├── 5.jpg ├── 6.jpg ├── 7.jpg ├── r1.jpg ├── r2.jpg ├── r3.jpg ├── r4.jpg ├── r5.jpg ├── r6.jpg └── r7.jpg ├── clip ├── clip.js ├── coding.jpg └── r.jpg ├── dist ├── index.css ├── index.js └── vue.js ├── example ├── demo-vue.html ├── demo-vue.js ├── demo.dist.js └── index.html ├── index.html ├── index.js ├── package.json ├── src ├── index-vue.js ├── index.js └── index.styl ├── webpack.demo.conf.js └── webpack.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["latest", { 4 | "es2015": { "modules": false } 5 | }], 6 | "stage-2" 7 | ], 8 | "plugins": ["transform-runtime"], 9 | "comments": false, 10 | "env": { 11 | "test": { 12 | "presets": ["latest", "stage-2"], 13 | "plugins": [ "istanbul" ] 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | npm-debug.log 4 | yarn-error.log -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | ./example 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 cc 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 | # progressive-image 2 | 3 | A dead simple progressive-image module for Vanilla JavaScript and Vue.js 1.0+ & 2.0+ 4 | 5 | ![gif](https://github.com/ccforward/cc/raw/master/Blog/pic/progressive-0.gif) 6 | 7 | ## GitHub 8 | 9 | [GitHub - progressive-image](https://github.com/ccforward/progressive-image) 10 | 11 | ## NPM 12 | 13 | [![NPM version][npm-image]][npm-url] 14 | 15 | [NPM - progressive-image](https://www.npmjs.com/package/progressive-image) 16 | 17 | ## update 18 | 19 | #### @v1.1.0 20 | add the `scale` option for origin image animation 21 | 22 | ## Install 23 | 24 | ```shell 25 | $ npm install progressive-image --save 26 | 27 | $ yarn add progressive-image 28 | ``` 29 | 30 | ## For Vanilla JS 31 | 32 | ### demo 33 | 34 | [progressive-image-demo](https://ccforward.github.io/progressive-image/index.html) 35 | 36 | [progressive-image-vue-demo](https://ccforward.github.io/progressive-image/example/demo-vue.html) 37 | 38 | ### Usage 39 | 40 | #### import css 41 | ```html 42 | 43 | ``` 44 | 45 | or 46 | 47 | ```html 48 | 49 | ``` 50 | 51 | #### HTML 52 | 53 | ```html 54 |
55 |
56 | 57 |
58 |
59 | 60 |
61 |
62 | 63 | ``` 64 | 65 | #### JS 66 | 67 | ```js 68 | (function(){ 69 | new Progressive({ 70 | el: '#app', 71 | lazyClass: 'lazy', 72 | removePreview: true 73 | scale: true 74 | }).fire() 75 | })() 76 | 77 | ``` 78 | 79 | ## For Vue.js 80 | 81 | #### import css 82 | ```html 83 | 84 | ``` 85 | 86 | or 87 | 88 | ```html 89 | 90 | ``` 91 | 92 | #### HTML 93 | 94 | ```html 95 |
96 | 102 |
103 | ``` 104 | 105 | #### JS 106 | 107 | ```js 108 | import Vue from 'vue' 109 | import progressive from 'progressive-image/dist/vue' 110 | 111 | Vue.use(progressive, { 112 | removePreview: true, 113 | scale: true 114 | }) 115 | 116 | new Vue({ 117 | name: 'demo', 118 | el: '#app', 119 | data: { 120 | imgs: [ 121 | { 122 | src: 'http://7xiblh.com1.z0.glb.clouddn.com/progressive/2.jpg', 123 | preview: 'http://7xiblh.com1.z0.glb.clouddn.com/progressive/r2.jpg' 124 | }, 125 | { 126 | src: 'http://7xiblh.com1.z0.glb.clouddn.com/progressive/3.jpg', 127 | preview: 'http://7xiblh.com1.z0.glb.clouddn.com/progressive/r3.jpg' 128 | } 129 | ] 130 | } 131 | }) 132 | ``` 133 | 134 | 135 | ## build 136 | 137 | #### build dist 138 | 139 | ```shell 140 | npm run build 141 | ``` 142 | 143 | #### build demo 144 | 145 | ```shell 146 | npm run demo 147 | ``` 148 | 149 | [npm-url]: https://www.npmjs.com/package/progressive-image 150 | [npm-image]: https://img.shields.io/npm/v/progressive-image.svg -------------------------------------------------------------------------------- /assets/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccforward/progressive-image/18bd29522b8ffb4cf6efac42755023e907909bcd/assets/1.jpg -------------------------------------------------------------------------------- /assets/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccforward/progressive-image/18bd29522b8ffb4cf6efac42755023e907909bcd/assets/2.jpg -------------------------------------------------------------------------------- /assets/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccforward/progressive-image/18bd29522b8ffb4cf6efac42755023e907909bcd/assets/3.jpg -------------------------------------------------------------------------------- /assets/4-l.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccforward/progressive-image/18bd29522b8ffb4cf6efac42755023e907909bcd/assets/4-l.jpg -------------------------------------------------------------------------------- /assets/4-m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccforward/progressive-image/18bd29522b8ffb4cf6efac42755023e907909bcd/assets/4-m.jpg -------------------------------------------------------------------------------- /assets/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccforward/progressive-image/18bd29522b8ffb4cf6efac42755023e907909bcd/assets/4.jpg -------------------------------------------------------------------------------- /assets/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccforward/progressive-image/18bd29522b8ffb4cf6efac42755023e907909bcd/assets/5.jpg -------------------------------------------------------------------------------- /assets/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccforward/progressive-image/18bd29522b8ffb4cf6efac42755023e907909bcd/assets/6.jpg -------------------------------------------------------------------------------- /assets/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccforward/progressive-image/18bd29522b8ffb4cf6efac42755023e907909bcd/assets/7.jpg -------------------------------------------------------------------------------- /assets/r1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccforward/progressive-image/18bd29522b8ffb4cf6efac42755023e907909bcd/assets/r1.jpg -------------------------------------------------------------------------------- /assets/r2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccforward/progressive-image/18bd29522b8ffb4cf6efac42755023e907909bcd/assets/r2.jpg -------------------------------------------------------------------------------- /assets/r3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccforward/progressive-image/18bd29522b8ffb4cf6efac42755023e907909bcd/assets/r3.jpg -------------------------------------------------------------------------------- /assets/r4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccforward/progressive-image/18bd29522b8ffb4cf6efac42755023e907909bcd/assets/r4.jpg -------------------------------------------------------------------------------- /assets/r5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccforward/progressive-image/18bd29522b8ffb4cf6efac42755023e907909bcd/assets/r5.jpg -------------------------------------------------------------------------------- /assets/r6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccforward/progressive-image/18bd29522b8ffb4cf6efac42755023e907909bcd/assets/r6.jpg -------------------------------------------------------------------------------- /assets/r7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccforward/progressive-image/18bd29522b8ffb4cf6efac42755023e907909bcd/assets/r7.jpg -------------------------------------------------------------------------------- /clip/clip.js: -------------------------------------------------------------------------------- 1 | const gm = require('gm') 2 | 3 | gm('./coding.jpg') 4 | .resize(20) 5 | .write('r.jpg', function (err) { 6 | err && console.log(err) 7 | }); -------------------------------------------------------------------------------- /clip/coding.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccforward/progressive-image/18bd29522b8ffb4cf6efac42755023e907909bcd/clip/coding.jpg -------------------------------------------------------------------------------- /clip/r.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccforward/progressive-image/18bd29522b8ffb4cf6efac42755023e907909bcd/clip/r.jpg -------------------------------------------------------------------------------- /dist/index.css: -------------------------------------------------------------------------------- 1 | .progressive { 2 | position: relative; 3 | display: block; 4 | overflow: hidden; 5 | } 6 | .progressive img { 7 | display: block; 8 | width: 100%; 9 | max-width: 100%; 10 | height: auto; 11 | border: 0 none; 12 | } 13 | .progressive img.preview { 14 | filter: blur(2vw); 15 | transform: scale(1.05); 16 | } 17 | .progressive img.hide { 18 | opacity: 0; 19 | } 20 | .progressive img.origin { 21 | position: absolute; 22 | left: 0; 23 | top: 0; 24 | animation: origin 1.5s ease-out; 25 | } 26 | .progressive img.origin-scale { 27 | position: absolute; 28 | left: 0; 29 | top: 0; 30 | animation: origin-scale 1s ease-out; 31 | } 32 | @-moz-keyframes origin { 33 | 0% { 34 | opacity: 0; 35 | } 36 | 100% { 37 | opacity: 1; 38 | } 39 | } 40 | @-webkit-keyframes origin { 41 | 0% { 42 | opacity: 0; 43 | } 44 | 100% { 45 | opacity: 1; 46 | } 47 | } 48 | @-o-keyframes origin { 49 | 0% { 50 | opacity: 0; 51 | } 52 | 100% { 53 | opacity: 1; 54 | } 55 | } 56 | @keyframes origin { 57 | 0% { 58 | opacity: 0; 59 | } 60 | 100% { 61 | opacity: 1; 62 | } 63 | } 64 | @-moz-keyframes origin-scale { 65 | 0% { 66 | opacity: 0; 67 | transform: scale(1.1); 68 | } 69 | 100% { 70 | opacity: 1; 71 | transform: scale(1); 72 | } 73 | } 74 | @-webkit-keyframes origin-scale { 75 | 0% { 76 | opacity: 0; 77 | transform: scale(1.1); 78 | } 79 | 100% { 80 | opacity: 1; 81 | transform: scale(1); 82 | } 83 | } 84 | @-o-keyframes origin-scale { 85 | 0% { 86 | opacity: 0; 87 | transform: scale(1.1); 88 | } 89 | 100% { 90 | opacity: 1; 91 | transform: scale(1); 92 | } 93 | } 94 | @keyframes origin-scale { 95 | 0% { 96 | opacity: 0; 97 | transform: scale(1.1); 98 | } 99 | 100% { 100 | opacity: 1; 101 | transform: scale(1); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | !function(e,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var n=t();for(var r in n)("object"==typeof exports?exports:e)[r]=n[r]}}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var i=n[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,t),i.l=!0,i.exports}var n={};return t.m=e,t.c=n,t.i=function(e){return e},t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=24)}([function(e,t,n){e.exports=!n(3)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(e,t){e.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},function(e,t){var n=e.exports={version:"2.4.0"};"number"==typeof __e&&(__e=n)},function(e,t){e.exports=function(e){try{return!!e()}catch(e){return!0}}},function(e,t){var n=e.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=n)},function(e,t,n){var r=n(14),i=n(19),o=n(21),u=Object.defineProperty;t.f=n(0)?Object.defineProperty:function(e,t,n){if(r(e),t=o(t,!0),r(n),i)try{return u(e,t,n)}catch(e){}if("get"in n||"set"in n)throw TypeError("Accessors not supported!");return"value"in n&&(e[t]=n.value),e}},function(e,t,n){"use strict";t.__esModule=!0,t.default=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var i=n(11),o=r(i);t.default=function(){function e(e,t){for(var n=0;n=t?a():n=setTimeout(a,t)}}},on:function(e,t,n){e.addEventListener(t,n)},off:function(e,t,n){e.removeEventListener(t,n)}},this.windowHasBind=!1,this.lazy=this.Util.throttle(function(e){n.fire()},300),this.animationEvent=this.getAnimationEvent()}return u()(e,[{key:"fire",value:function(){this.windowHasBind||(this.windowHasBind=!0,this.events(window,!0));var e=document.querySelectorAll(this.el+" img."+this.lazyClass),t=e.length;if(t>0)for(var n=0;n0&&r.left0&&this.loadImage(e[n])}else this.windowHasBind=!1,this.events(window,!1)}},{key:"events",value:function(e,t){var n=this;t?this.EVENTS.forEach(function(t){n.Util.on(e,t,n.lazy)}):this.EVENTS.forEach(function(t){n.Util.off(e,t,n.lazy)})}},{key:"loadImage",value:function(e){var t=this,n=new Image;e.dataset&&(e.dataset.srcset&&(n.srcset=e.dataset.srcset),e.dataset.sizes&&(n.sizes=e.dataset.sizes)),n.src=e.dataset.src,n.className="origin",this.scale&&(n.className="origin-scale"),e.classList.remove("lazy"),n.onload=function(r){t.mountImage(e,n)},n.onerror=function(t){e.classList.add("lazy")}}},{key:"getAnimationEvent",value:function(){var e=document.createElement("fake"),t={animation:"animationend",OAnimation:"oAnimationEnd",MozAnimation:"animationend",WebkitAnimation:"webkitAnimationEnd"};for(var n in t)if(void 0!==e.style[n])return t[n]}},{key:"mountImage",value:function(e,t){var n=this,r=e.parentNode;r.appendChild(t).addEventListener(this.animationEvent,function(t){t.target.alt=e.alt||"",e.classList.add("hide"),n.removePreview&&(r.removeChild(e),t.target.classList.remove("origin"),t.target.classList.remove("origin-scale"))})}}]),e}();"undefined"!=typeof exports?(void 0!==e&&e.exports&&(exports=e.exports=a),exports.Progressive=a):"function"==typeof define&&n(9)?define("Progressive",[],function(){return a}):this.Progressive=a}).call(t,n(10)(e))}])}); -------------------------------------------------------------------------------- /dist/vue.js: -------------------------------------------------------------------------------- 1 | !function(e,n){if("object"==typeof exports&&"object"==typeof module)module.exports=n();else if("function"==typeof define&&define.amd)define([],n);else{var t=n();for(var i in t)("object"==typeof exports?exports:e)[i]=t[i]}}(this,function(){return function(e){function n(i){if(t[i])return t[i].exports;var o=t[i]={i:i,l:!1,exports:{}};return e[i].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var t={};return n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,t,i){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:i})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n(n.s=23)}({23:function(e,n,t){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.default=function(e){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};Array.prototype.remove=function(e){if(this.length){var n=this.indexOf(e);return n>-1?this.splice(n,1):void 0}};var t="2"==e.version.split(".")[0],i=["scroll","wheel","mousewheel","resize","touchmove"],o={getAnimationEvent:function(){var e=document.createElement("fake"),n={animation:"animationend",OAnimation:"oAnimationEnd",MozAnimation:"animationend",WebkitAnimation:"webkitAnimationEnd"};for(var t in n)if(void 0!==e.style[t])return n[t]},throttle:function(e,n){var t=null,i=0;return function(){if(!t){var o=Date.now()-i,r=this,a=arguments,s=function(){i=Date.now(),t=!1,e.apply(r,a)};o>=n?s():t=setTimeout(s,n)}}},on:function(e,n,t){e.addEventListener(n,t)},off:function(e,n,t){e.removeEventListener(n,t)}},r=function(e,n){n?i.forEach(function(n){o.on(e,n,f)}):i.forEach(function(n){o.off(e,n,f)})},a=o.getAnimationEvent(),s=[],c=[],u={removePreview:n.removePreview||!1,scale:n.scale||!1,hasBind:!1},f=o.throttle(function(e){for(var n=0,t=s.length;n-1)return p(e.el,e.src,"loaded");var n=e.el.getBoundingClientRect();n.top0&&n.left0&&h(e)},d=function(n){var t=!1;return s.forEach(function(e){e.el==n&&(t=!0)}),t&&e.nextTick(function(){f()}),t},v=function(n,t,i){if(!d(n)){var o=t.value;if(c.indexOf(o)>-1)return p(n,o,"loaded");e.nextTick(function(e){d(n)||s.push({el:n,src:o}),f(),s.length>0&&!u.hasBind&&(u.hasBind=!0,r(window,!0))})}},p=function(e,n,t){e.setAttribute("lazy",t)},h=function(e){var n=new Image;e.el.dataset&&(e.el.dataset.srcset&&(n.srcset=e.el.dataset.srcset),e.el.dataset.sizes&&(n.sizes=e.el.dataset.sizes)),n.src=e.src,n.className="origin",u.scale&&(n.className="origin-scale"),n.onload=function(t){s.remove(e),c.push(e.src),m(e,n)},n.onerror=function(e){}},m=function(e,n){var t=e.el,i=t.parentNode;i.appendChild(n).addEventListener(a,function(e){t.alt&&(e.target.alt=t.alt),t.classList.add("hide"),u.removePreview&&(i.removeChild(t),e.target.classList.remove("origin"),e.target.classList.remove("origin-scale"))})},g=function(e,n,t,i){e&&u.hasBind&&(u.hasBind=!1,r(window,!1),s.length=c.length=0)};t?e.directive("progressive",{bind:v,update:v,inserted:v,comppnentUpdated:f,unbind:g}):e.directive("progressive",{bind:f,update:function(e,n){v(this.el,{modifiers:this.modifiers,arg:this.arg,value:e,oldValue:n})},unbind:function(){g(this.el)}})}}})}); -------------------------------------------------------------------------------- /example/demo-vue.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | progressive image - vue 7 | 40 | 41 | 42 | 43 | 44 |

progressive image demo

45 |

For Vue.js

46 |
47 |
48 | 49 |
50 | 51 | 57 | 58 |
59 |
60 | 61 |
62 |
63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /example/demo-vue.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import progressive from'../src/index-vue' 3 | 4 | Vue.use(progressive, { 5 | removePreview: true, 6 | scale: false 7 | }) 8 | 9 | new Vue({ 10 | name: 'demo', 11 | el: '#app', 12 | data: { 13 | big1: '../assets/1.jpg', 14 | big2: './assets/4.jpg', 15 | imgs: [ 16 | { 17 | src: '../assets/2.jpg', 18 | srcset: '../assets/2.jpg', 19 | preview: '../assets/r2.jpg' 20 | }, 21 | { 22 | src: '../assets/3.jpg', 23 | preview: '../assets/r3.jpg' 24 | }, 25 | { 26 | src: '../assets/7.jpg', 27 | preview: '../assets/r7.jpg' 28 | }, 29 | { 30 | src: '../assets/6.jpg', 31 | preview: '../assets/r6.jpg' 32 | }, 33 | { 34 | src: '../assets/5.jpg', 35 | preview: '../assets/r5.jpg' 36 | } 37 | 38 | ] 39 | } 40 | }) -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | progressive image 7 | 80 | 81 | 82 | 83 |

progressive image simple example

84 |
85 |
86 | 87 |
88 | 89 |
90 |
91 | 92 |
93 | 94 |
95 |
96 | 97 |
98 | 99 |
100 |
101 | 102 |
103 | 104 |
105 |
106 | 107 |
108 | 109 |
110 |
111 | 112 |
113 | 114 |
115 |
116 | 117 |
118 | 119 | 120 | 121 |
122 |
123 | 124 |
125 |
126 | 230 | 231 | 232 | 233 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | progressive image - villain 7 | 40 | 41 | 42 | 43 | 44 |

progressive image demo

45 |

For Vanilla JS

46 |
47 |
48 | 49 |
50 | 51 |
52 |
53 | 54 |
55 | 56 |
57 |
58 | 59 |
60 | 61 |
62 |
63 | 64 |
65 | 66 |
67 |
68 | 69 |
70 | 71 |
72 |
73 | 74 |
75 | 76 | 77 | 78 |
79 |
80 | 81 |
82 |
83 | 84 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('./dist/index.js') -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "progressive-image", 3 | "version": "1.2.2", 4 | "description": "A dead simple progressive-image module for Vanilla JavaScript and Vue.js 1.0+ & 2.0+", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "webpack --config webpack.js", 8 | "demo": "webpack --config webpack.demo.conf.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/ccforward/progressive-image" 13 | }, 14 | "keywords": [ 15 | "progressive", 16 | "progressive-image", 17 | "vue-progressive-image", 18 | "vue-progressive", 19 | "lazy-image", 20 | "lazy", 21 | "vue lazy" 22 | ], 23 | "devDependencies": { 24 | "babel-core": "^6.22.1", 25 | "babel-loader": "^6.2.10", 26 | "babel-plugin-transform-runtime": "^6.22.0", 27 | "babel-preset-latest": "^6.22.0", 28 | "babel-preset-stage-2": "^6.22.0", 29 | "babel-register": "^6.22.0", 30 | "css-loader": "^0.27.3", 31 | "extract-text-webpack-plugin": "^2.1.0", 32 | "gm": "^1.23.0", 33 | "style-loader": "^0.15.0", 34 | "stylus": "^0.54.5", 35 | "stylus-loader": "^3.0.1", 36 | "vue": "^2.2.4", 37 | "webpack": "^2.2.1" 38 | }, 39 | "author": "ccforward ", 40 | "license": "MIT" 41 | } 42 | -------------------------------------------------------------------------------- /src/index-vue.js: -------------------------------------------------------------------------------- 1 | export default (Vue, Opt = {}) => { 2 | Array.prototype.remove = function(item) { 3 | if(!this.length) return 4 | const idx = this.indexOf(item); 5 | if(idx > -1) return this.splice(idx,1) 6 | } 7 | const isVue2 = Vue.version.split('.')[0] == '2' 8 | const EVENTS = ['scroll', 'wheel', 'mousewheel', 'resize', 'touchmove'] 9 | const Util = { 10 | getAnimationEvent() { 11 | const el = document.createElement('fake') 12 | const animations = { 13 | "animation" : "animationend", 14 | "OAnimation" : "oAnimationEnd", 15 | "MozAnimation" : "animationend", 16 | "WebkitAnimation": "webkitAnimationEnd" 17 | } 18 | for (let a in animations){ 19 | if (el.style[a] !== undefined){ 20 | return animations[a] 21 | } 22 | } 23 | }, 24 | throttle(action, delay) { 25 | let timeout = null 26 | let lastRun = 0 27 | return function() { 28 | if (timeout) { 29 | return 30 | } 31 | const elapsed = Date.now() - lastRun 32 | const context = this 33 | const args = arguments 34 | const runCallback = function() { 35 | lastRun = Date.now() 36 | timeout = false 37 | action.apply(context, args) 38 | } 39 | if (elapsed >= delay) { 40 | runCallback() 41 | } else { 42 | timeout = setTimeout(runCallback, delay) 43 | } 44 | } 45 | }, 46 | on(el, ev, fn) { 47 | el.addEventListener(ev, fn) 48 | }, 49 | off(el, ev, fn) { 50 | el.removeEventListener(ev, fn) 51 | } 52 | } 53 | const events = (el, bind) => { 54 | if(bind){ 55 | EVENTS.forEach(evt => { 56 | Util.on(el, evt, lazy) 57 | }) 58 | }else { 59 | EVENTS.forEach(evt => { 60 | Util.off(el, evt, lazy) 61 | }) 62 | } 63 | } 64 | 65 | const animationEvent = Util.getAnimationEvent() 66 | 67 | const Listeners = [] 68 | const imgCache = [] 69 | 70 | const Options = { 71 | removePreview: Opt.removePreview || false, 72 | scale: Opt.scale || false, 73 | hasBind: false 74 | } 75 | 76 | const lazy = Util.throttle( _ => { 77 | for(let i=0,l=Listeners.length;i { 83 | if(imgCache.indexOf(listener.src) > -1){ 84 | return render(listener.el, listener.src, 'loaded') 85 | }else { 86 | const rect = listener.el.getBoundingClientRect() 87 | if (rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0) { 88 | loadImage(listener) 89 | } 90 | } 91 | } 92 | 93 | const isExist = el => { 94 | let exist = false 95 | Listeners.forEach(item => { 96 | if(item.el == el) exist = true 97 | }) 98 | if(exist){ 99 | Vue.nextTick( () => { 100 | lazy() 101 | }) 102 | } 103 | return exist 104 | } 105 | const init = (el, binding, vnode) => { 106 | if(isExist(el)) return; 107 | 108 | const src = binding.value 109 | if(imgCache.indexOf(src) > -1) { 110 | return render(el, src, 'loaded') 111 | } 112 | Vue.nextTick( _ => { 113 | if(!isExist(el)){ 114 | Listeners.push({ 115 | el: el, 116 | src: src 117 | }) 118 | } 119 | lazy() 120 | if(Listeners.length > 0 && !Options.hasBind) { 121 | Options.hasBind = true 122 | events(window, true) 123 | } 124 | }) 125 | 126 | } 127 | 128 | 129 | const render = (el, src, status) => { 130 | el.setAttribute('lazy', status); 131 | } 132 | const loadImage = item => { 133 | const img = new Image() 134 | if (item.el.dataset) { 135 | item.el.dataset.srcset && (img.srcset = item.el.dataset.srcset) 136 | item.el.dataset.sizes && (img.sizes = item.el.dataset.sizes) 137 | } 138 | 139 | img.src = item.src 140 | img.className = 'origin' 141 | if(Options.scale) { 142 | img.className = 'origin-scale' 143 | } 144 | img.onload = _ => { 145 | Listeners.remove(item) 146 | imgCache.push(item.src) 147 | mountImage(item, img) 148 | } 149 | img.onerror = _ => { 150 | 151 | } 152 | } 153 | 154 | const mountImage = (item, img) => { 155 | const preview = item.el 156 | const parent = preview.parentNode 157 | parent.appendChild(img).addEventListener(animationEvent, e => { 158 | preview.alt && (e.target.alt = preview.alt) 159 | preview.classList.add('hide') 160 | if(Options.removePreview){ 161 | parent.removeChild(preview) 162 | e.target.classList.remove('origin') 163 | e.target.classList.remove('origin-scale') 164 | } 165 | }) 166 | } 167 | 168 | const unbind = (el, binding, vnode, oldValue) => { 169 | if (!el) return 170 | if (Options.hasBind) { 171 | Options.hasBind = false 172 | events(window, false) 173 | Listeners.length = imgCache.length = 0 174 | } 175 | } 176 | 177 | if (isVue2) { 178 | Vue.directive('progressive', { 179 | bind: init, 180 | update: init, 181 | inserted: init, 182 | comppnentUpdated: lazy, 183 | unbind: unbind 184 | }) 185 | } else { 186 | Vue.directive('progressive', { 187 | bind: lazy, 188 | update(newValue, oldValue) { 189 | init(this.el, { 190 | modifiers: this.modifiers, 191 | arg: this.arg, 192 | value: newValue, 193 | oldValue: oldValue 194 | }) 195 | }, 196 | unbind() { 197 | unbind(this.el) 198 | } 199 | }) 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | require('./index.styl') 2 | 3 | class Progressive { 4 | constructor(option) { 5 | this.el = option.el 6 | this.lazyClass = option.lazyClass || 'lazy' 7 | this.removePreview = option.removePreview || false 8 | this.scale = option.scale || false 9 | 10 | this.EVENTS = ['scroll', 'wheel', 'mousewheel', 'resize', 'touchmove'] 11 | this.Util = { 12 | throttle(action, delay) { 13 | let timeout = null 14 | let lastRun = 0 15 | return function() { 16 | if (timeout) { 17 | return 18 | } 19 | const elapsed = Date.now() - lastRun 20 | const context = this 21 | const args = arguments 22 | const runCallback = function() { 23 | lastRun = Date.now() 24 | timeout = false 25 | action.apply(context, args) 26 | } 27 | if (elapsed >= delay) { 28 | runCallback() 29 | } else { 30 | timeout = setTimeout(runCallback, delay) 31 | } 32 | } 33 | }, 34 | on(el, ev, fn) { 35 | el.addEventListener(ev, fn) 36 | }, 37 | off(el, ev, fn) { 38 | el.removeEventListener(ev, fn) 39 | } 40 | } 41 | 42 | this.windowHasBind = false 43 | 44 | this.lazy = this.Util.throttle( _ => { 45 | this.fire() 46 | }, 300) 47 | 48 | this.animationEvent = this.getAnimationEvent() 49 | } 50 | 51 | fire() { 52 | if(!this.windowHasBind){ 53 | this.windowHasBind = true 54 | this.events(window, true) 55 | } 56 | 57 | const lazys = document.querySelectorAll(`${this.el} img.${this.lazyClass}`) 58 | const l = lazys.length 59 | if(l>0){ 60 | for (let i = 0; i < l; i++) { 61 | const rect = lazys[i].getBoundingClientRect() 62 | if (rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0) { 63 | this.loadImage(lazys[i]) 64 | } 65 | } 66 | }else { 67 | this.windowHasBind = false 68 | this.events(window, false) 69 | } 70 | } 71 | 72 | events(el, bind) { 73 | if(bind){ 74 | this.EVENTS.forEach(evt => { 75 | this.Util.on(el, evt, this.lazy) 76 | }) 77 | }else { 78 | this.EVENTS.forEach(evt => { 79 | this.Util.off(el, evt, this.lazy) 80 | }) 81 | } 82 | } 83 | 84 | loadImage(item) { 85 | const img = new Image() 86 | if (item.dataset) { 87 | item.dataset.srcset && (img.srcset = item.dataset.srcset) 88 | item.dataset.sizes && (img.sizes = item.dataset.sizes) 89 | } 90 | img.src = item.dataset.src 91 | img.className = 'origin' 92 | if(this.scale) { 93 | img.className = 'origin-scale' 94 | } 95 | item.classList.remove('lazy') 96 | img.onload = _ => { 97 | this.mountImage(item, img) 98 | } 99 | img.onerror = _ => { 100 | item.classList.add('lazy') 101 | } 102 | } 103 | 104 | getAnimationEvent(){ 105 | const el = document.createElement('fake') 106 | const animations = { 107 | "animation" : "animationend", 108 | "OAnimation" : "oAnimationEnd", 109 | "MozAnimation" : "animationend", 110 | "WebkitAnimation": "webkitAnimationEnd" 111 | } 112 | for (let a in animations){ 113 | if (el.style[a] !== undefined){ 114 | return animations[a] 115 | } 116 | } 117 | } 118 | 119 | mountImage(preview, img) { 120 | const parent = preview.parentNode 121 | parent.appendChild(img).addEventListener(this.animationEvent, e => { 122 | e.target.alt = preview.alt || '' 123 | preview.classList.add('hide') 124 | if(this.removePreview){ 125 | parent.removeChild(preview) 126 | e.target.classList.remove('origin') 127 | e.target.classList.remove('origin-scale') 128 | } 129 | }) 130 | } 131 | } 132 | 133 | if (typeof exports !== 'undefined') { 134 | if (typeof module !== 'undefined' && module.exports) 135 | exports = module.exports = Progressive 136 | exports.Progressive = Progressive 137 | } else if (typeof define === 'function' && define.amd) 138 | define('Progressive', [], function() { 139 | return Progressive 140 | }) 141 | else 142 | this.Progressive = Progressive 143 | -------------------------------------------------------------------------------- /src/index.styl: -------------------------------------------------------------------------------- 1 | .progressive { 2 | position relative 3 | display block 4 | overflow hidden 5 | img { 6 | display block 7 | width 100% 8 | max-width 100% 9 | height auto 10 | border 0 none 11 | &.preview { 12 | filter blur(2vw) 13 | transform scale(1.05) 14 | } 15 | &.hide { 16 | opacity 0 17 | } 18 | &.origin { 19 | position absolute 20 | left 0 21 | top 0 22 | animation origin 1.5s ease-out 23 | } 24 | &.origin-scale { 25 | position absolute 26 | left 0 27 | top 0 28 | animation origin-scale 1s ease-out 29 | } 30 | } 31 | } 32 | 33 | @keyframes origin { 34 | 0% { 35 | opacity 0 36 | } 37 | 100% { 38 | opacity 1 39 | } 40 | } 41 | @keyframes origin-scale { 42 | 0% { 43 | opacity 0 44 | transform scale(1.1) 45 | } 46 | 100% { 47 | opacity 1 48 | transform scale(1) 49 | } 50 | } -------------------------------------------------------------------------------- /webpack.demo.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | function resolve (dir) { 4 | return path.join(__dirname, '..', dir) 5 | } 6 | 7 | module.exports = { 8 | entry: { 9 | demo: './example/demo-vue.js' 10 | }, 11 | output: { 12 | path: path.resolve(__dirname, 'example'), 13 | filename: '[name].dist.js' 14 | }, 15 | resolve: { 16 | extensions: ['.js', '.vue', '.json'], 17 | alias: { 18 | 'vue$': 'vue/dist/vue.js', 19 | '@': resolve('src'), 20 | } 21 | }, 22 | module: { 23 | noParse: /es6-promise\.js$/, 24 | rules: [ 25 | { 26 | test: /\.js$/, 27 | loader: "babel-loader", 28 | exclude: /node_modules/ 29 | } 30 | ] 31 | } 32 | } -------------------------------------------------------------------------------- /webpack.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 3 | var webpack = require('webpack') 4 | 5 | module.exports = { 6 | entry: { 7 | index: './src/index.js', 8 | vue: './src/index-vue.js' 9 | }, 10 | output: { 11 | path: path.resolve(__dirname, 'dist'), 12 | filename: '[name].js', 13 | libraryTarget: 'umd' 14 | }, 15 | module: { 16 | noParse: /es6-promise\.js$/, 17 | rules: [ 18 | { 19 | test: /\.js$/, 20 | loader: "babel-loader", 21 | exclude: /node_modules/ 22 | }, 23 | { 24 | test: /\.styl$/, 25 | use: ExtractTextPlugin.extract({ 26 | fallback: "style-loader", 27 | use: "css-loader!stylus-loader" 28 | }) 29 | }, 30 | { 31 | test: /\.css$/, 32 | loader: "style-loader!css-loader" 33 | } 34 | ] 35 | }, 36 | plugins: [ 37 | new ExtractTextPlugin({ 38 | filename: 'index.css' 39 | }), 40 | new webpack.optimize.UglifyJsPlugin({ 41 | compress: { 42 | warnings: false 43 | } 44 | }) 45 | ] 46 | } --------------------------------------------------------------------------------