├── .gitignore ├── skaler.png ├── .editorconfig ├── demo ├── index.html └── src │ └── index.js ├── package.json ├── src └── index.js ├── license └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist -------------------------------------------------------------------------------- /skaler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terkelg/skaler/HEAD/skaler.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_size = 2 6 | indent_style = space 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.{json,yml,md}] 13 | indent_style = space -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Demo 5 | 6 | 7 | 8 | 9 | 10 | Select image to scale: 11 | 12 |
13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "skaler", 3 | "version": "1.0.7", 4 | "repository": "terkelg/skaler", 5 | "description": "A 329B client-side image resizer.", 6 | "main": "dist/skaler.mjs", 7 | "module": "dist/skaler.mjs", 8 | "unpkg": "dist/skaler.umd.js", 9 | "files": [ 10 | "dist", 11 | "src" 12 | ], 13 | "scripts": { 14 | "start": "http-server demo --silent -o & npm run watch", 15 | "watch": "microbundle watch --format umd --entry demo/src/index.js --output demo/dist/bundle.js", 16 | "build": "microbundle --name skaler --format es,umd --sourcemap false", 17 | "prepare": "npm run build" 18 | }, 19 | "keywords": [ 20 | "dom", 21 | "image", 22 | "photo", 23 | "picture", 24 | "img", 25 | "scaling", 26 | "scale", 27 | "files", 28 | "file", 29 | "upload", 30 | "form", 31 | "input" 32 | ], 33 | "license": "MIT", 34 | "devDependencies": { 35 | "http-server": "^0.12.1", 36 | "microbundle": "^0.11.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export default function skaler(file, { scale, width, height, name = file.name, type = file.type } = {}) { 2 | return new Promise((res, rej) => { 3 | const reader = new FileReader(); 4 | reader.readAsDataURL(file); 5 | reader.onload = e => { 6 | const img = new Image(); 7 | img.onload = () => { 8 | const el = document.createElement('canvas'); 9 | const dir = (width < img.width || height < img.height) ? 'min' : 'max'; 10 | const stretch = width && height; 11 | const ratio = scale ? scale : Math[dir]( 12 | (width / img.width) || 1, 13 | (height / img.height) || 1 14 | ); 15 | let w = el.width = stretch ? width : img.width * ratio; 16 | let h = el.height = stretch ? height : img.height * ratio; 17 | const ctx = el.getContext('2d'); 18 | ctx.drawImage(img, 0, 0, w, h); 19 | el.toBlob(blob => res(new File([blob], name, { type, lastModified: Date.now() }))); 20 | reader.onerror = rej; 21 | } 22 | img.src = e.target.result; 23 | } 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /demo/src/index.js: -------------------------------------------------------------------------------- 1 | import skaler from './../../src/index.js'; 2 | 3 | const picker = document.querySelector('.file'); 4 | const result = document.querySelector('.result'); 5 | 6 | picker.addEventListener('change', async () => { 7 | let file = picker.files[0]; 8 | 9 | const img = (label, file, opts) => { 10 | return new Promise(async res => { 11 | const img = await skaler(file, opts); 12 | const image = new Image(); 13 | image.onload = () => res({image, label}); 14 | image.src = URL.createObjectURL(img); 15 | }); 16 | } 17 | 18 | const images = await Promise.all([ 19 | img('Scale down to 200px width', file, {width: 200}), 20 | img('Scale up to 900px width', file, {width: 900}), 21 | img('Scale to 0.5 percent', file, {scale: 0.5}), 22 | img('Stretch to 150x150px', file, {width: 150, height: 150}) 23 | ]); 24 | 25 | images.forEach(({image, label}) => { 26 | const labelEl = document.createElement('h3'); 27 | labelEl.textContent = label; 28 | result.appendChild(labelEl); 29 | result.appendChild(image); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Terkel Gjervig (terkel.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |

2 | skaler 3 |

4 | 5 |

6 | 7 | version 8 | 9 | 10 | gzip size 11 | 12 | 13 | license 14 | 15 | 16 | dependencies 17 | 18 |

19 | 20 |

A 329B client-side image resizer

21 | 22 | Skaler is a simple and small tool to scale images client-side. 23 | It's ideal when all you want to do is to scale user submitted images before uploading to your server. 24 | 25 | Save storage space, bandwidth and reduce server load by scaling images client side before uploading. 26 | 27 | **~~lack of~~ Features** 28 | - Tiny 29 | - Vanilla JS 30 | - Zero Dependencies 31 | 32 | 33 | ## Install 34 | 35 | ``` 36 | $ npm install skaler 37 | ``` 38 | 39 | This module exposes two module definitions: 40 | 41 | * **ES Module**: `dist/skaler.mjs` 42 | * **UMD**: `dist/skaler.umd.js` 43 | 44 | Include skaler: 45 | ```js 46 | // ES6 47 | import skaler from 'skaler' 48 | 49 | // CJS 50 | const skaler = require('skaler'); 51 | ``` 52 | 53 | The script can also be directly included from [unpkg.com](https://unpkg.com): 54 | ```html 55 | 56 | ``` 57 | 58 | 59 | ## Usage 60 | 61 | ```js 62 | import skaler from 'skaler'; 63 | 64 | /** 65 | * Assume 'input' is the value coming from an input field: 66 | * 67 | */ 68 | 69 | const input = document.getElementById('#input').files[0]; 70 | 71 | const file = await skaler(input, { scale: 0.5 }); 72 | // ~> resized image as a File object - half the size 73 | 74 | const file = await skaler(input, { width: 300 }); 75 | // ~> resized image as a File object - 300px width 76 | 77 | const file = await skaler(input, { width: 300, height: 500 }); 78 | // ~> resized image as a File object - stretched to 300x500px 79 | 80 | ``` 81 | 82 | 83 | ## API 84 | 85 | ### skaler(file, options={}) 86 | Returns: `File` <_Promise_> 87 | 88 | Reutnrs promise that resolves to the resized [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File) object. 89 | 90 | > **Note**:The new files has an updated ` last modified time` property. 91 | 92 | #### file 93 | Type: `File` 94 | 95 | [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File) object to be resized. 96 | This is what input elements of type `file` returns. 97 | 98 | > **Note**: The file is expected to be of type image. 99 | 100 | #### options.scale 101 | Type: `number`
102 | 103 | Scale based on relative percentage. Example: 104 | ```js 105 | let file = await skaler(input, { scale: 0.5 }); 106 | // ~> output is half the size of the orignal 107 | ``` 108 | > **Note**: The `width` and `height` options are ignored if `scale` is provided. 109 | 110 | #### options.width 111 | Type: `number`
112 | 113 | Scale to a specific width. The file keeps it aspect ratio. 114 | ```js 115 | let file = await skaler(input, { width: 200 }); 116 | // ~> output is 200px width 117 | ``` 118 | 119 | > **Note**: The image can become stretched if both `width` and `height` are provided at the same time. 120 | 121 | #### options.height 122 | Type: `number`
123 | 124 | Scale to a specific height. The file keeps it aspect ratio. 125 | ```js 126 | let file = await skaler(input, { width: 200 }); 127 | // ~> output is 200px width 128 | ``` 129 | 130 | > **Note**: The image can become stretched if both `width` and `height` are provided at the same time. 131 | 132 | ### options.name 133 | Type: `string`
134 | 135 | Rename file during resizing. Defaults to the name of the input [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File). 136 | 137 | ### options.type 138 | Type: `String`
139 | 140 | A `string` representing the `MIME` type of the content that will be put into the file. Defaults to a value of the input [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File). 141 | 142 | 143 | ## Future 144 | 145 | I'd plan to optimize for even better performacne and smaller code using `offscreenCanvas` and `workers` in the future as browser support gets better. I also considered `createImageBitmap()` but it's currently not supported in Safari. 146 | 147 | 148 | ## License 149 | 150 | MIT © [Terkel Gjervig](https://terkel.com) 151 | --------------------------------------------------------------------------------