├── .babelrc
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── demo
├── Imagvue.gif
└── Imagvue.png
├── dist
└── imagvue.js
├── package-lock.json
├── package.json
├── rollup.config.js
└── src
└── imagvue.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", {
4 | "modules": false,
5 | "targets": {
6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
7 | }
8 | }],
9 | "stage-2"
10 | ],
11 | "plugins": ["transform-vue-jsx","external-helpers"]
12 | }
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | .cache
4 | imagvue.map
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.log
3 | .babelrc
4 | node_modules
5 | src
6 | .cache
7 | demo
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Willy Hong
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 | # Imagvue
2 |
3 | [](https://vuejs.org/) [](https://www.npmjs.com/package/imagvue) [](https://github.com/runkids/Imagvue)
4 |
5 | - `Imagvue` provides basic image processing props(size,blur,contrast,grayscale, etc.).
6 |
7 | - Support image lazy loading.
8 |
9 | - All Attributes can bind with data
10 |
11 |
12 |
13 | ## Demo
14 |
15 | [](https://codesandbox.io/embed/n7ykx4rxyp?module=%2Fsrc%2FApp.vue&view=preview)
16 |
17 |
18 | ## Installation
19 | Get from npm / yarn:
20 | ```js
21 | npm i imagvue
22 | ```
23 | ```js
24 | yarn add imagvue
25 | ```
26 | Directly include [imagvue.min.js](https://cdn.jsdelivr.net/npm/imagvue@0.0.5/dist/imagvue.min.js) to your view like
27 |
28 | ```html
29 |
30 | ```
31 |
32 | ## Usage
33 |
34 | ##### html:
35 | ```html
36 |
37 | ```
38 |
39 | ##### vue file:
40 | ``` js
41 | import imagvue from 'imagvue'
42 |
43 | export default {
44 | name: 'app',
45 | components: {
46 | imagvue,
47 | },
48 | data(){
49 | return {
50 | url: 'https://source.unsplash.com/random',
51 | localURL: require('./imagvue.png'),
52 | }
53 | }
54 | }
55 | ```
56 |
57 | ## Lazy loading Image
58 |
59 | [DEMO](https://runkids.github.io/f2e/week2/)
60 |
61 | ##### how to use ?
62 | Use `transition-group` and set attribute `src` with your loading image inner `imagvue`.
63 | Also you can set attribute`lazy` for delay time.
64 |
65 | ##### 1. src
66 | Type: `String`
67 | Required: `ture`
68 |
69 | Your loading image.
70 |
71 | ##### 2. lazy
72 | Type: `Number`
73 | Default: `0`
74 |
75 | Show image delay time.
76 |
77 | ##### 3. rootMargin
78 |
79 | Type: `String`
80 | Default: `0px`
81 |
82 | Syntax similar to that of CSS Margin
83 |
84 | [rootMargin](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/rootMargin)
85 |
86 | ##### 4. threshold
87 | Type: `Number`
88 | Default: `0`
89 |
90 | Ratio of element convergence
91 |
92 | [threshold](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/thresholds)
93 |
94 | ```html
95 |
101 | your loading image
103 | :lazy="500" --> lazy time , default is 0 ( ms )
104 | rootMargin="0px 0px"
105 | :threshold="0.1"
106 | >
107 |
108 |
109 | ```
110 |
111 |
112 |
113 |
114 | ##### Browser Support
115 |
116 | Available in [latest browsers](http://caniuse.com/#feat=intersectionobserver). If browser support is not available, use this [polyfill](https://www.npmjs.com/package/intersection-observer).
117 |
118 |
119 | ## Props
120 |
121 | ##### 1. value
122 | Type: `String`
123 | Required: `ture`
124 |
125 | The image URL. This is mandatory for the ` `
126 | ```html
127 |
128 | ```
129 |
130 | ##### 2. width
131 | Type: `String` , `Number`
132 | Required: `false`
133 | Default: `auto`
134 |
135 | The intrinsic width of the image in pixels.
136 |
137 | ##### 3. height
138 | Type: `String` , `Number`
139 | Required: `false`
140 | Default: `auto`
141 |
142 | The intrinsic height of the image in pixels.
143 |
144 | ##### 4. onerror
145 | Type: `Function`
146 | Required: `false`
147 |
148 | If an error occurs while trying to load or render an image ,
149 | call a function
150 |
151 | ```html
152 |
156 |
157 | ```
158 |
159 |
160 |
161 | ##### 5. blur
162 | Type: `String` , `Number`
163 | Required: `false`
164 | Default: 0
165 |
166 | Applies a Gaussian blur to the input image.
167 | Range: 0 ~ larger value ( px )
168 |
169 | ```html
170 |
171 | ```
172 |
173 |
174 |
175 | ##### 6. contrast
176 | Type: `String` , `Number`
177 | Required: `false`
178 | Default: 100
179 |
180 | Adjusts the contrast of the input.
181 | Range: 0 ~ over 100 ( % )
182 |
183 | ```html
184 |
185 | ```
186 |
187 |
188 |
189 | ##### 7. brightness
190 | Type: `String` , `Number`
191 | Required: `false`
192 | Default: 100
193 |
194 | Applies a linear multiplier to input image
195 | Range: 0 ~ over 100 ( % )
196 |
197 | ```html
198 |
199 | ```
200 |
201 |
202 |
203 | ##### 8. grayscale
204 | Type: `String` , `Number`
205 | Required: `false`
206 | Default: 0
207 |
208 | Converts the input image to grayscale.
209 | Range: 0 ~ 100 ( % )
210 |
211 | ```html
212 |
213 | ```
214 |
215 |
216 |
217 | ##### 9. hueRotate
218 | Type: `String` , `Number`
219 | Required: `false`
220 | Default: 0
221 |
222 | Applies a hue rotation on the input image.
223 | Range: 0 ~ 360 ( deg )
224 |
225 | ```html
226 |
227 | ```
228 |
229 |
230 |
231 | ##### 10. invert
232 | Type: `String` , `Number`
233 | Required: `false`
234 | Default: 0
235 |
236 | Inverts the samples in the input image.
237 | Range: 0 ~ 100 ( % )
238 |
239 | ```html
240 |
241 | ```
242 |
243 |
244 |
245 | ##### 11. opacity
246 | Type: `String` , `Number`
247 | Required: `false`
248 | Default: 0
249 |
250 | Applies transparency to the samples in the input image.
251 | Range: 0 ~ 100 ( % )
252 |
253 | ```html
254 |
255 | ```
256 |
257 |
258 |
259 | ##### 12. saturate
260 | Type: `String` , `Number`
261 | Required: `false`
262 | Default: 0
263 |
264 | Saturates the input image.
265 | Range: 0 ~ 100 ( % )
266 |
267 | ```html
268 |
269 | ```
270 |
271 |
272 |
273 | ##### 13. sepia
274 | Type: `String` , `Number`
275 | Required: `false`
276 | Default: 0
277 |
278 | Converts the input image to sepia.
279 | Range: 0 ~ 100 ( % )
280 |
281 | ```html
282 |
283 | ```
284 |
285 |
286 |
287 | ##### 14. dropShadow
288 | Type: `Object`
289 | Required: `false`
290 | Default: null
291 |
292 | Applies a drop shadow effect to the input image.
293 |
294 | - `offset`: This value to set the shadow offset.
295 | - `blurRadius`: The larger this value, the bigger the blur, so the shadow becomes bigger and lighter.
296 | - `spreadRadius`: Positive values will cause the shadow to expand and grow bigger, and negative values will cause the shadow to shrink.
297 | - `color`: The color of the shadow.
298 |
299 | ```js
300 | export default {
301 | name: 'app',
302 | components: {
303 | imagvue,
304 | },
305 | data(){
306 | return {
307 | dropShadow:{
308 | offset: 16, --> required
309 | blurRadius: 0, --> optional default 0 px
310 | spreadRadius: 0, --> optional default 0 px
311 | color: 'black' --> optional default black
312 | }
313 | }
314 | }
315 | }
316 | ```
317 |
318 | ```html
319 |
320 | ```
321 |
322 |
323 |
324 | ##### 15. filters
325 | Type: `Boolean`
326 | Required: `false`
327 | Default: true
328 |
329 | if you won't to use filters ( blur,contrast,grayscale, etc.).
330 | just set props `filters` to false
331 |
332 | ```html
333 |
334 | ```
335 |
336 | ##### 16. customData
337 | Type: `Object`
338 | Required: `false`
339 | Default: null
340 |
341 | This is used to pass additional information to ``
342 |
343 | - on: events to be subscribe of ``
344 | - props: props to be passed to ``
345 |
346 | ```html
347 |
348 | ```
349 |
350 | ```js
351 | methods:{
352 | onLoadEvent(){
353 | //todo
354 | },
355 | customData(){
356 | return {
357 | on: {
358 | load: this.onLoadEvent,
359 | }
360 | }
361 | }
362 | }
363 | ```
364 |
365 |
366 | ## Code Example
367 |
368 | ```html
369 |
370 |
371 |
389 |
390 |
391 |
392 |
393 | ```
394 |
395 | ```js
396 |
444 | ```
445 | ```css
446 |
461 | ```
462 | ## License
463 | Imagvue is licensed under MIT License.
464 |
--------------------------------------------------------------------------------
/demo/Imagvue.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/runkids/Imagvue/47a3ad9875ae460efe2267f4487cbbcd7417d613/demo/Imagvue.gif
--------------------------------------------------------------------------------
/demo/Imagvue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/runkids/Imagvue/47a3ad9875ae460efe2267f4487cbbcd7417d613/demo/Imagvue.png
--------------------------------------------------------------------------------
/dist/imagvue.js:
--------------------------------------------------------------------------------
1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.imagvue=e()}(this,function(){"use strict";var t,S=Object.assign||function(t){for(var e=1;e"
25 | },
26 | "license": "MIT",
27 | "scripts": {
28 | "build": "rollup -c"
29 | },
30 | "devDependencies": {
31 | "babel-core": "^6.26.3",
32 | "babel-plugin-external-helpers": "^6.22.0",
33 | "babel-preset-es2015": "^6.24.1",
34 | "babelrc-rollup": "^3.0.0",
35 | "rollup": "^0.66.4",
36 | "rollup-plugin-babel": "^3.0.7",
37 | "rollup-plugin-commonjs": "^9.1.8",
38 | "rollup-plugin-node-resolve": "^3.4.0",
39 | "rollup-plugin-uglify": "^6.0.0"
40 | },
41 | "dependencies": {
42 | "babel-preset-env": "^1.7.0",
43 | "vue": "^2.5.17"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from 'rollup-plugin-babel'
2 | import resolve from 'rollup-plugin-node-resolve'
3 | import commonjs from 'rollup-plugin-commonjs'
4 | import {uglify} from 'rollup-plugin-uglify';
5 |
6 | export default {
7 | entry: 'src/imagvue.js',
8 | dest: 'dist/imagvue.js',
9 | moduleName: 'imagvue',
10 | format: 'umd',
11 | plugins: [
12 | resolve({
13 | jsnext: true,
14 | main: true,
15 | browser: true,
16 | }),
17 | commonjs(),
18 | babel({
19 | exclude: 'node_modules/**',
20 | }),
21 | uglify()
22 | ],
23 | }
--------------------------------------------------------------------------------
/src/imagvue.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | /*
4 | * build attribute like { src: '../logo.png' }
5 | */
6 | function buildAttribute(object, propName, value){
7 | if(value == undefined) return object;
8 | object = !object ? {} : object;
9 | object[propName] = value;
10 | return object;
11 | }
12 |
13 | function dropShadowOptions(dropShadow){
14 | if( dropShadow!==null ){
15 | let { offset, blurRadius=0, spreadRadius=0, color='black' } = dropShadow
16 | return `drop-shadow(${offset}px ${blurRadius}px ${spreadRadius}px ${color})`;
17 | }
18 | return '';
19 | }
20 |
21 | function toInt(value){
22 | return parseInt(value, 10);
23 | }
24 |
25 | function validatorRange(value){
26 | let int = toInt(value);
27 | return int >= 0 && int <= 100;
28 | }
29 |
30 | function buildImagvue() {
31 |
32 | const props = {
33 | value:{
34 | type: String,
35 | required: true,
36 | },
37 |
38 | onerror:{
39 | type: Function,
40 | default: ()=>{},
41 | },
42 |
43 | height:{
44 | type: [String, Number],
45 | default: 'auto',
46 | },
47 |
48 | width:{
49 | type: [String, Number],
50 | default: 'auto',
51 | },
52 |
53 | blur:{ //模糊
54 | type: [String, Number],
55 | default: '0',
56 | validator: (value)=> toInt(value) >= 0
57 | },
58 |
59 | contrast:{ //對比 0% ~ over 100%
60 | type: [String, Number],
61 | default: '100',
62 | validator: (value)=> toInt(value) >= 0
63 | },
64 |
65 | brightness:{ //亮度 0% ~ over 100%
66 | type: [String, Number],
67 | default: '100',
68 | validator: (value)=> toInt(value) >= 0
69 | },
70 |
71 | grayscale:{ //灰階 0~100
72 | type: [String, Number],
73 | default: '0',
74 | validator: value => validatorRange(value)
75 | },
76 |
77 | hueRotate:{ //色相旋轉 0~360 deg
78 | type: [String, Number],
79 | default: '0',
80 | validator: (value)=> {
81 | let int = toInt(value);
82 | return int >= 0 && int <= 360
83 | }
84 | },
85 |
86 | invert:{ //負片效果 0~100%
87 | type: [String, Number],
88 | default: '0',
89 | validator: value => validatorRange(value)
90 | },
91 |
92 | opacity:{ //透明度 0~100%
93 | type: [String, Number],
94 | default: '100',
95 | validator: value => validatorRange(value)
96 | },
97 |
98 | saturate:{ //飽和度 0 ~ over 100%
99 | type: [String, Number],
100 | default: '100',
101 | validator: (value)=> toInt(value) >= 0
102 | },
103 |
104 | sepia:{ //懷舊 0 ~ 100%
105 | type: [String, Number],
106 | default: '0',
107 | validator: value => validatorRange(value)
108 | },
109 |
110 | /*
111 | * 陰影
112 | * dropShadow:{
113 | * offset: 16, --> required
114 | * blurRadius: 0, --> optional default 0 px
115 | * spreadRadius: 0, --> optional default 0 px
116 | * color: 'black' --> optional default black
117 | * }
118 | */
119 | dropShadow:{
120 | type: Object,
121 | default: null
122 | },
123 |
124 | filters:{
125 | type: Boolean,
126 | default: true
127 | },
128 |
129 | customData:{
130 | type: Object,
131 | default: null,
132 | }
133 | }
134 |
135 | const imagvueComponent = {
136 | name: 'imagvue',
137 |
138 | props,
139 |
140 | data(){
141 | return{
142 | lazyLoadMode: false,
143 | lazyTime: 0,
144 | lazyLoadImage: '',
145 | io: null ,
146 | options:{
147 | rootMargin: null,
148 | threshold: null,
149 | },
150 | }
151 | },
152 |
153 | render(h) {
154 | const slots = this.$slots.default ;
155 |
156 | if(slots && slots.length ===1){
157 | const child = slots[0];
158 | if( child.componentOptions && child.componentOptions.tag === "transition-group"){
159 | let { src, lazy, rootMargin='0px', threshold=0 } = child.data.attrs;
160 | this.lazyLoadMode = true;
161 | this.lazyLoadImage = src;
162 | this.lazyTime = lazy || 500;
163 | this.options.rootMargin = rootMargin;
164 | this.options.threshold = toInt(threshold);
165 | }
166 | }
167 |
168 | let children = slots;
169 |
170 | let attributes = null;
171 | const update = (attributeName, attributeValue) => {
172 | attributes = buildAttribute(attributes, attributeName, attributeValue)
173 | };
174 |
175 | const initAttribute = {
176 | ...this.$attrs,
177 | src: this.lazyLoadMode ? this.lazyLoadImage :this.value,
178 | width: this.width,
179 | height: this.height,
180 | ...( this.lazyLoadMode ? { 'data-src':this.value } : {} )
181 | };
182 |
183 | const initStyles = {
184 | filter: `
185 | blur(${ this.blur }px)
186 | contrast(${ this.contrast }%)
187 | brightness(${ this.brightness }%)
188 | grayscale(${ this.grayscale }%)
189 | hue-rotate(${ this.hueRotate }deg)
190 | opacity(${ this.opacity }%)
191 | invert(${ this.invert }%)
192 | saturate(${ this.saturate }%)
193 | sepia(${ this.sepia }%)
194 | ${ dropShadowOptions( this.dropShadow ) }
195 | `
196 | }
197 |
198 | const initEvent = {
199 | error: this.onerror,
200 | }
201 |
202 | if(this.filters) update('style', initStyles);
203 | update('attrs', initAttribute);
204 | update('on', initEvent);
205 |
206 | // if have custom data update to imagvue
207 | if( this.customData ){
208 | const { on, props } = this.customData;
209 | update('on', {...on, ...initEvent}); //nature event Note. can't use for ex. v-on
210 | update('props', props); // component props
211 | }
212 |
213 | return h('img', attributes, children);
214 | },
215 |
216 | methods:{
217 |
218 | listenScrollEvent(entries){
219 | // 當圖片完全出現時
220 | entries.forEach(entry => {
221 | if(entry.intersectionRatio > 0){
222 | setTimeout(()=>{
223 | this.unObserve();
224 | let container = entry.target;
225 | container.src = container.getAttribute('data-src');
226 | container.removeAttribute('data-src');
227 | },this.lazyTime);
228 | }
229 | });
230 | },
231 |
232 | unObserve(){
233 | if (this.io) {
234 | this.io.unobserve(this.$el);
235 | }
236 | },
237 |
238 | },
239 |
240 | mounted() {
241 | if ( this.lazyLoadMode && ("IntersectionObserver" in window) ) {
242 | const { rootMargin, threshold } = this.options ;
243 | this.io = new IntersectionObserver(this.listenScrollEvent,{
244 | rootMargin,
245 | threshold
246 | });
247 | this.io.observe(this.$el);
248 | }
249 | },
250 |
251 | beforeDestroy () {
252 | this.unObserve();
253 | },
254 |
255 | };
256 |
257 | return imagvueComponent;
258 | }
259 |
260 | if (typeof exports === "object") {
261 | module.exports = buildImagvue();
262 | } else if (typeof define === "function" && define.amd) {
263 | define([], buildImagvue());
264 | } else if (window && window.Vue) {
265 | let imagvue = buildImagvue();
266 | Vue.component("imagvue", imagvue);
267 | }
268 | })();
--------------------------------------------------------------------------------