├── .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 | 
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 |
97 |
98 |
99 |
![]()
100 |
101 |
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 |
52 |
53 |
54 |
![]()
55 |
56 |
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 | }
--------------------------------------------------------------------------------