Guide
React, Vue, Svelte and a plain page
Every wrapper is a thin layer over the same DOM core, so all of them behave identically.
React
import { useRef } from 'react';
import { ImageCropper } from '@devix-labs/image-cropper/react';
import '@devix-labs/image-cropper/styles.css';
function AvatarField() {
const cropper = useRef(null);
const save = async () => {
const body = new FormData();
body.append('avatar', await cropper.current.toFile());
await fetch('/profile/avatar', { method: 'POST', body });
};
return (
<>
<ImageCropper ref={cropper} name="avatar" aspect={1} mode="fixed" round maxWidth={512} />
<button onClick={save}>Save</button>
</>
);
}
The ref gives you toBlob(), toFile(), reset(), clear() and instance. Anything that is not
an option — name, className, required, aria-* — goes to the input. Changing aspect applies
it; the rest are read at mount, so pass a key to rebuild.
To follow the crop as it moves, use onChange; to get a picture on every change, set live and use
onCrop.
Vue
<script setup>
import { onMounted, onBeforeUnmount, ref } from 'vue';
import { imageCropper } from '@devix-labs/image-cropper/vue';
import '@devix-labs/image-cropper/styles.css';
const host = ref(null);
let picker;
onMounted(() => {
picker = imageCropper(host.value, { aspect: 1, mode: 'fixed', round: true, maxWidth: 512 });
});
onBeforeUnmount(() => picker?.destroy());
defineExpose({ toFile: () => picker.toFile() });
</script>
<template>
<input ref="host" type="file" name="avatar">
</template>
Svelte
<script>
import { cropper } from '@devix-labs/image-cropper/svelte';
import '@devix-labs/image-cropper/styles.css';
let picker;
</script>
<input type="file" name="avatar"
use:cropper={{ aspect: 1, round: true, onPick: () => {} }}
bind:this={picker}>
A web component
<script type="module" src="https://devix.pk/cdn/oss/image-cropper@1.0.0/element.js"></script>
<dx-image id="avatar" name="avatar" aspect="1" mode="fixed" round max-width="512"></dx-image>
<script type="module">
const blob = await document.querySelector('#avatar').toBlob();
</script>
Attributes: name, src, aspect, mode, round, max-width, max-height, quality. It
exposes .picker and .toBlob(), and fires crop.
A plain page, from the CDN
<link rel="stylesheet" href="https://devix.pk/cdn/oss/image-cropper@1.0.0/image-cropper.min.css">
<script src="https://devix.pk/cdn/oss/image-cropper@1.0.0/image-cropper.min.js"></script>
<script>
const picker = DevixImageCropper.createImageCropper(document.querySelector('#avatar'), { aspect: 1 });
</script>
Laravel
<form id="profile" method="post" action="{{ route('avatar.store') }}">
@csrf
<label for="avatar">Profile picture</label>
<input id="avatar" name="avatar" type="file">
<button>Save</button>
</form>
<script type="module">
import { createImageCropper } from '/cdn/oss/image-cropper@1.0.0/index.js';
const picker = createImageCropper(document.querySelector('#avatar'), {
aspect: 1, mode: 'fixed', round: true, maxWidth: 512, type: 'image/webp',
});
document.querySelector('#profile').addEventListener('submit', async (event) => {
event.preventDefault();
const body = new FormData(event.target);
body.set('avatar', await picker.toFile('avatar'));
await fetch(event.target.action, { method: 'POST', body });
});
</script>
$request->validate(['avatar' => ['required', 'image', 'dimensions:min_width=64', 'max:2048']]);
$path = $request->file('avatar')->store('avatars', 'public');
A 512-pixel WebP from a 12-megapixel phone photograph is a few tens of kilobytes, so the upload is quick and the server has nothing left to resize.
Server rendering
Nothing touches window at import time, so importing the package on a server is safe in Next.js,
Nuxt, SvelteKit and Astro. The widget is built when the component mounts.