├── .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 | [![vue2](https://img.shields.io/badge/vue-2.x-brightgreen.svg)](https://vuejs.org/) [![npm](https://img.shields.io/npm/v/imagvue.svg)](https://www.npmjs.com/package/imagvue) [![npm bundle size (minified)](https://img.shields.io/bundlephobia/min/react.svg)](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 | [![Edit imagvue demo](https://codesandbox.io/static/img/play-codesandbox.svg)](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 | 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 | })(); --------------------------------------------------------------------------------