├── .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 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
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 |
--------------------------------------------------------------------------------