# Recipes
# Changing a stencil
There are only two default stencil components now, RectangleStencil
(default) and CircleStencil
, but you can easily create your own stencil himself.
To specify stencil component you should pass it to stencilComponent
prop. For globally registered component just pass their name:
<cropper
stencil-component="circle-stencil"
/>
But if your component is not registered globally you should pass the component’s options object.
There is one of approaches to pass it:
import { CircleStencil, Cropper } from 'vue-advanced-cropper';
export default {
components: {
Cropper, CircleStencil
},
data() {
return {
img: 'https://images.unsplash.com/photo-1485178575877-1a13bf489dfe?ixlib=rb-1.2.1&auto=format&fit=crop&w=991&q=80',
};
},
};
<div id="app">
<cropper
src="https://images.pexels.com/photos/1254140/pexels-photo-1254140.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940"
:stencil-component="$options.components.CircleStencil"
/>
</div>
The cause of this specificity is using the dynamic components (opens new window) approach to allow use an arbitrary stencil component.
# Passing props to the stencil
To pass any props to the stencil pass them as object to stencilProps
prop.
For example, that's how you can set aspect ratio:
<cropper
:stencil-props="{
aspectRatio: 6/9,
movable: false,
resizable: false
}"
/>
The list of available props varies from one stencil component to another. The props of default stencils are available at this site (RectangleStencil, CircleStencil)
# Setting the aspect ratio
This library supports setting either aspect ratio value or aspect ratio range (i.e. minimum and maximums aspect ratio values).
Generally speaking, aspect ratio is the property of the stencil, not the cropper, so the possibility to set aspect ratio
entirely depends of used stencil. For example, CircleStencil
aspect ratio can't be customized, it's always circle, in the same time
RectangleStencil
has not any restrictions on aspect ratios.
The examples below are written for RectangleStencil
.
# Fixed aspect ratio
<cropper
:stencil-props="{
aspectRatio: 1/1,
}"
/>
# Aspect ratio range
<cropper
:stencil-props="{
minAspectRatio: 16/8,
maxAspectRatio: 4/8
}"
/>
# Getting the result
# First method
You can get the coordinates of stencil and canvas with cropped image by processing change
event.
TIP
Cropper will emit change
event on clear or load (include initial) image, on resize and move the stencil, on resize, move, rotate and flip the image.
import { Cropper } from 'vue-advanced-cropper';
export default {
components: {
Cropper,
},
data() {
return {
coordinates: {
width: 0,
height: 0,
left: 0,
top: 0,
},
image: null,
};
},
methods: {
onChange({ coordinates, canvas, }) {
this.coordinates = coordinates;
// You able to do different manipulations at a canvas
// but there we just get a cropped image, that can be used
// as src for <img/> to preview result
this.image = canvas.toDataURL();
},
},
};
<div id="app">
<cropper
src="https://images.pexels.com/photos/226746/pexels-photo-226746.jpeg"
@change="onChange"
/>
</div>
# Second method
Also there is alternative to get the cropper result. You can call the cropper method getResult
to get current stencil coordinates and canvas with cropped image.
Click at the button Crop Image below to see this method in action
import { Cropper } from 'vue-advanced-cropper';
export default {
components: {
Cropper,
},
data() {
return {
coordinates: {
width: 0,
height: 0,
left: 0,
top: 0,
},
image: null,
};
},
methods: {
crop() {
const { coordinates, canvas, } = this.$refs.cropper.getResult();
this.coordinates = coordinates;
// You able to do different manipulations at a canvas
// but there we just get a cropped image, that can be used
// as src for <img/> to preview result
this.image = canvas.toDataURL();
},
},
};
<div id="app">
<cropper
src="https://images.pexels.com/photos/580012/pexels-photo-580012.jpeg"
ref="cropper"
/>
<button @click="crop">
Crop
</button>
</div>
# Resize the result
If you use coordinates only, the result should be scaled on server-side, but if you use canvas you prefer to resize the result on client-side.
The most simplest way to do it is pass the restrictions for the result size in canvas
property.
<cropper
:src="image"
:canvas="{
minHeight: 0,
minWidth: 0,
maxHeight: 2048,
maxWidth: 2048,
}"
/>
If you need to set the specific height and width use height
and width
attributes.
But you should note that canvas will have the same aspect ratio as the stencil, so the result size may
be different than one that you have set.
<cropper
:src="image"
:canvas="{
height: 256,
width: 256
}"
/>
It uses default canvas image scaling procedure under the hood. If the result doesn't suit you, try to use the external libraries to resize image (pica (opens new window), downscale (opens new window) and etc.)
# Preview the result
To implement real-time preview of cropping result you can use Preview
component (it used internally to
display the cropped area in the stencil).
import { Cropper, Preview } from 'vue-advanced-cropper';
export default {
components: {
Cropper,
Preview
},
data() {
return {
img: 'https://images.unsplash.com/photo-1590291409749-452efbe0d76c?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=634&q=80',
result: {
coordinates: null,
image: null
}
};
},
methods: {
onChange({ coordinates, image }) {
this.result = {
coordinates,
image
};
},
},
};
Notice!
The debounce
is set to false
to make the preview realtime.
<div id="app">
<cropper
:src="img"
@change="onChange"
:debounce="false"
:stencil-props="{
aspectRatio: 1
}"
/>
<preview
:width="120"
:height="120"
:image="result.image"
:coordinates="result.coordinates"
/>
</div>
# Load image from a disc
The image loading doesn't depend at this library and can be completed by a numerous ways. There will be considered only one of them.
That's what you will get:
Notice!
Blob (opens new window) is used in the example below. Ensure that browsers that you support can fully handle it or use the corresponding polyfill.
import { Cropper } from 'vue-advanced-cropper'
// This function is used to detect the actual image type,
function getMimeType(file, fallback = null) {
const byteArray = (new Uint8Array(file)).subarray(0, 4);
let header = '';
for (let i = 0; i < byteArray.length; i++) {
header += byteArray[i].toString(16);
}
switch (header) {
case "89504e47":
return "image/png";
case "47494638":
return "image/gif";
case "ffd8ffe0":
case "ffd8ffe1":
case "ffd8ffe2":
case "ffd8ffe3":
case "ffd8ffe8":
return "image/jpeg";
default:
return fallback;
}
}
export default {
components: {
Cropper,
},
data() {
return {
image: {
src: null,
type: null
}
};
},
methods: {
crop() {
const { canvas } = this.$refs.cropper.getResult();
canvas.toBlob((blob) => {
// Do something with blob: upload to a server, download and etc.
}, this.image.type);
},
reset() {
this.image = {
src: null,
type: null
}
},
loadImage(event) {
// Reference to the DOM input element
const { files } = event.target;
// Ensure that you have a file before attempting to read it
if (files && files[0]) {
// 1. Revoke the object URL, to allow the garbage collector to destroy the uploaded before file
if (this.image.src) {
URL.revokeObjectURL(this.image.src)
}
// 2. Create the blob link to the file to optimize performance:
const blob = URL.createObjectURL(files[0]);
// 3. The steps below are designated to determine a file mime type to use it during the
// getting of a cropped image from the canvas. You can replace it them by the following string,
// but the type will be derived from the extension and it can lead to an incorrect result:
//
// this.image = {
// src: blob;
// type: files[0].type
// }
// Create a new FileReader to read this image binary data
const reader = new FileReader();
// Define a callback function to run, when FileReader finishes its job
reader.onload = (e) => {
// Note: arrow function used here, so that "this.image" refers to the image of Vue component
this.image = {
// Set the image source (it will look like blob:http://example.com/2c5270a5-18b5-406e-a4fb-07427f5e7b94)
src: blob,
// Determine the image type to preserve it during the extracting the image from canvas:
type: getMimeType(e.target.result, files[0].type),
};
};
// Start the reader job - read file as a data url (base64 format)
reader.readAsArrayBuffer(files[0]);
}
},
},
destroyed() {
// Revoke the object URL, to allow the garbage collector to destroy the uploaded before file
if (this.image.src) {
URL.revokeObjectURL(this.image.src)
}
}
};
<div id="app">
<div class="upload-example">
<cropper
ref="cropper"
class="upload-example-cropper"
:src="image.src"
/>
<div class="button-wrapper">
<button class="button" @click="$refs.file.click()">
<input type="file" ref="file" @change="loadImage($event)" accept="image/*">
Load image
</button>
</div>
</div>
</div>
# Upload image to a server
The preferred method to upload image to the server is using a blob (opens new window) (for IE-11 you should use polyfill). The detailed explanation why you shouldn't use the data-url you can read in the great answer (opens new window) on Stackoverflow.
There are different approaches to implement image uploading because it depends on your backend. The one of them is presented below.
Look the network section in your developer tools to examine the sent request.
import { Cropper } from 'vue-advanced-cropper';
export default {
components: {
Cropper,
},
data() {
return {
image: 'https://images.unsplash.com/photo-1591273531346-ba9262aa2da6?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=751&q=80',
};
},
methods: {
reset() {
this.image = null;
},
uploadImage() {
const { canvas } = this.$refs.cropper.getResult();
if (canvas) {
const form = new FormData();
canvas.toBlob(blob => {
form.append('file', blob);
// You can use axios, superagent and other libraries instead here
fetch('http://example.com/upload/', {
method: 'POST',
body: form,
});
// Perhaps you should add the setting appropriate file format here
}, 'image/jpeg');
}
},
},
};