-
-
Notifications
You must be signed in to change notification settings - Fork 5.7k
/
Copy pathCropper.ts
67 lines (60 loc) · 2.79 KB
/
Cropper.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import {createElementFromHTML, hideElem, showElem, type DOMEvent} from '../../utils/dom.ts';
import {debounce} from 'perfect-debounce';
import type {CropperCanvas, CropperSelection} from 'cropperjs';
type CropperOpts = {
container: HTMLElement,
wrapper: HTMLDivElement,
fileInput: HTMLInputElement,
}
async function initCompCropper({container, fileInput, wrapper}: CropperOpts) {
await import(/* webpackChunkName: "cropperjs" */'cropperjs');
fileInput.addEventListener('input', (e: DOMEvent<Event, HTMLInputElement>) => {
if (!e.target.files?.length) {
wrapper.replaceChildren();
hideElem(container);
return;
}
const [file] = e.target.files;
const objectUrl = URL.createObjectURL(file);
const cropperCanvas = createElementFromHTML<CropperCanvas>(`
<cropper-canvas theme-color="var(--color-primary)">
<cropper-image src="${objectUrl}" scalable skewable translatable></cropper-image>
<cropper-shade hidden></cropper-shade>
<cropper-handle action="select" plain></cropper-handle>
<cropper-selection aspect-ratio="1" movable resizable>
<cropper-handle action="move" theme-color="transparent"></cropper-handle>
<cropper-handle action="n-resize"></cropper-handle>
<cropper-handle action="e-resize"></cropper-handle>
<cropper-handle action="s-resize"></cropper-handle>
<cropper-handle action="w-resize"></cropper-handle>
<cropper-handle action="ne-resize"></cropper-handle>
<cropper-handle action="nw-resize"></cropper-handle>
<cropper-handle action="se-resize"></cropper-handle>
<cropper-handle action="sw-resize"></cropper-handle>
</cropper-selection>
</cropper-canvas>
`);
cropperCanvas.querySelector<CropperSelection>('cropper-selection').addEventListener('change', debounce(async (e) => {
const selection = e.target as CropperSelection;
if (!selection.width || !selection.height) return;
const canvas = await selection.$toCanvas();
canvas.toBlob((blob) => {
const dataTransfer = new DataTransfer();
dataTransfer.items.add(new File(
[blob],
file.name.replace(/\.[^.]{3,4}$/, '.png'),
{type: 'image/png', lastModified: file.lastModified},
));
fileInput.files = dataTransfer.files;
});
}, 200));
wrapper.replaceChildren(cropperCanvas);
showElem(container);
});
}
export async function initAvatarUploaderWithCropper(fileInput: HTMLInputElement) {
const panel = fileInput.nextElementSibling as HTMLElement;
if (!panel?.matches('.cropper-panel')) throw new Error('Missing cropper panel for avatar uploader');
const wrapper = panel.querySelector<HTMLImageElement>('.cropper-wrapper');
await initCompCropper({container: panel, fileInput, wrapper});
}