Image Crop and Upload with Laravel & Vue.js
September 10, 2019
Recently, I worked on a project based on Laravel and Vue.js where I had to set up a manual image cropping tool with server upload. I then decided to share it in this tutorial.
Getting started
Assuming you have Laravel already installed on your server or your local machine, otherwise, follow the installation guide from the Laravel official website.
For this tutorial, minimal required versions are Laravel 5.4 and Vue.js 2.
Creating the cropper component
Laravel provides a great support for Vue.js. So, we will build our Vue components directly inside the Laravel’s assets folder.
All components are located in ressource/js/components, open that folder and create a new component CropUploadComponent.vue
.
<template>
<div>
Image crop and upload
</div>
</template>
<script>
export default {
name: "CropUploadComponent",
}
</script>
Then, register that component in ressources/js/app.js before const app
by adding the following line
Vue.component(
"crop-upload",
require("./components/CropUploadComponent.vue").default
)
Run the command below from your project root directory to install the necessary node modules.
$ npm install
Next, start compiling assets using the following command.
$ npm run dev
You can also run the following command to compile the assets as you write new code or modify the existing code.
$ npm run watch
Open your laravel view file located on resources/views
,
in this case we are using default home view welcome.blade.php
,
Then import assets and the cropper component as follow.
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Image crop upload</title>
<link href="{{ mix('css/app.css') }}" type="text/css" rel="stylesheet" />
</head>
<body class="bg-light">
<div class="w-75 mx-auto my-5">
<h2 class="mb-3">Image crop upload</h2>
<div id="app">
<crop-upload></crop-upload>
</div>
</div>
<script src="{{ mix('js/app.js') }}" type="text/javascript"></script>
</body>
</html>
Let’s start building our component by creating the upload button, image preview and preview function
<template>
<div>
<input type="file" name="image" accept="image/*" @change="setImage" />
<img :src="imageSrc" style="width: 100px;" />
</div>
</template>
<script>
export default {
data: function () {
return {
imageSrc: "",
}
},
methods: {
setImage: function (e) {
const file = e.target.files[0]
if (!file.type.includes("image/")) {
alert("Please select an image file")
return
}
if (typeof FileReader === "function") {
const reader = new FileReader()
reader.onload = event => {
this.imageSrc = event.target.result
// Rebuild cropperjs with the updated source
this.$refs.cropper.replace(event.target.result)
}
reader.readAsDataURL(file)
} else {
alert("Sorry, FileReader API not supported")
}
},
},
}
</script>
The code above will create image previewer
Image cropper
There is plenty of cropping packages for Vue.js, I tried many of them and I found that awesome package vuecropperjs
Run the following command from your project root directory to install the vuecropperjs
$ npm install --save vue-cropperjs
Then import the cropper in CropUploadComponent.vue
import VueCropper from 'vue-cropperjs';
import 'cropperjs/dist/cropper.css';
export default {
components: {
VueCropper
},
...
}
Include the cropper component in the template part
<vue-cropper
class="mr-2 w-50"
ref="cropper"
:guides="true"
:src="imageSrc"
></vue-cropper>
If you want to add extra features, discover all cropperjs options via this link,
Full code CropUploadComponent.vue
<template>
<div class="p-3 bg-white shadow rounded-lg">
<input type="file" name="image" accept="image/*" @change="setImage" />
<!-- Image previewer -->
<img :src="imageSrc" width="100" />
<!-- Cropper container -->
<div
v-if="this.imageSrc"
class="my-3 d-flex align-items-center justify-content-center mx-auto"
>
<vue-cropper
class="mr-2 w-50"
ref="cropper"
:guides="true"
:src="imageSrc"
></vue-cropper>
<!-- Cropped image previewer -->
<img class="ml-2 w-50 bg-light" :src="croppedImageSrc" />
</div>
<button v-if="this.imageSrc" @click="cropImage">Crop</button>
<button v-if="this.croppedImageSrc" @click="uploadImage">Upload</button>
</div>
</template>
<script>
import VueCropper from "vue-cropperjs"
import "cropperjs/dist/cropper.css"
export default {
components: {
VueCropper,
},
data: function () {
return {
imageSrc: "",
croppedImageSrc: "",
}
},
methods: {
setImage: function (e) {
const file = e.target.files[0]
if (!file.type.includes("image/")) {
alert("Please select an image file")
return
}
if (typeof FileReader === "function") {
const reader = new FileReader()
reader.onload = event => {
this.imageSrc = event.target.result
// Rebuild cropperjs with the updated source
this.$refs.cropper.replace(event.target.result)
}
reader.readAsDataURL(file)
} else {
alert("Sorry, FileReader API not supported")
}
},
cropImage() {
// Get image data for post processing, e.g. upload or setting image src
this.croppedImageSrc = this.$refs.cropper.getCroppedCanvas().toDataURL()
},
uploadImage() {
this.$refs.cropper.getCroppedCanvas().toBlob(function (blob) {
let formData = new FormData()
// Add name for our image
formData.append("name", "image-name-" + new Date().getTime())
// Append image file
formData.append("file", blob)
axios
.post("/api/store", formData)
.then(response => {
console.log(response.data)
})
.catch(function (error) {
console.log(error)
})
})
},
},
}
</script>
Image upload
Let’s move to the backend and create our upload system
First, add the store action in routes/api.php
for our previous axios
call
Route::post('store', 'ImageController@store');
We now need to implement this action in the controller.
Go to app/http/controllers
and create a new controller
ImageController.php
.
<?php
namespace App\Http\Controllers;
class ImageController extends Controller
{
public function store()
{
return response()->json(['success' => 'It works!']);
}
}
Next, let’s install the Intervention image library in our Laravel project. Run the command below from your project root directory.
composer require intervention/image
After installing the library, open config/app.php file and add the following lines.
Add the service providers for this package in the providers
array.
Intervention\Image\ImageServiceProvider::class
Add the facade to the aliases
array.
'Image' => Intervention\Image\Facades\Image::class
Full file imageController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Image;
class ImageController extends Controller
{
public function store(Request $request)
{
if ($request->hasFile('file')) {
$image = $request->file;
$name = $request->name.'.jpg';
$path = 'public/images/' . $name;
$img = Image::make($image);
Storage::disk('local')->put($path, $img->encode());
$url = asset('storage/images/' . $name);
return response()->json(['url' => $url]);
}
return response()->json(['error' => 'No file']);
}
}
After processing the upload, you will find your image in the storage folder as shown in the following image
Conclusion
In this tutorial, we’ve created a cropping and uploading system using Laravel and Vue.js. This system is not totally complete and lacks some features either on the frontend as uploading progress or on the backend as image type validation, that we’ll talk about in future blog posts.
Finally if you have any questions, do not hesitate to contact me.