基于cropper.js封裝vue實(shí)現(xiàn)在線圖片裁剪組件功能
效果圖如下所示,

cropper.js
cropper.js 安裝
- npm或bower安裝
npm install cropper # or bower install cropper
clone下載:下載地址
git clone https://github.com/fengyuanchen/cropper.git
引用cropper.js
主要引用cropper.js跟cropper.css兩個(gè)文件
<script src="/path/to/jquery.js"></script><!-- jQuery is required --> <link href="/path/to/cropper.css" rel="external nofollow" rel="stylesheet"> <script src="/path/to/cropper.js"></script>
注意:必須先引入jquery文件,才能使用cropper.js插件
簡(jiǎn)單使用
構(gòu)建截圖所要用到的div容器
<!-- Wrap the image or canvas element with a block element (container) --> <div>  </div>
添加容器的樣式,讓img填充滿(mǎn)整個(gè)容器(很重要)
/* Limit image width to avoid overflow the container */
img {
max-width: 100%; /* This rule is very important, please do not ignore this! */
}
調(diào)用cropper.js方法,初始化截圖控件
$('#image').cropper({
aspectRatio: 16 / 9,
crop: function(e) {
// Output the result data for cropping image.
console.log(e.x);
console.log(e.y);
console.log(e.width);
console.log(e.height);
console.log(e.rotate);
console.log(e.scaleX);
console.log(e.scaleY);
}
});
其他詳細(xì)api請(qǐng)參考:github:cropper.js
封裝成vue組件
封裝成vue組件中需解決的問(wèn)題
- cropper.js相關(guān)
模擬input框點(diǎn)擊選擇圖片并對(duì)選擇的圖片進(jìn)行格式、大小限制
重新選擇圖片裁剪
確認(rèn)裁剪并獲取base64格式的圖片信息
- vue相關(guān)
非父子組件之間的通信問(wèn)題
模擬input框點(diǎn)擊選擇圖片并對(duì)選擇的圖片進(jìn)行格式、大小限制
構(gòu)建一個(gè)隱藏的input標(biāo)簽,然后模擬點(diǎn)擊此input,從而達(dá)到能選擇圖片的功能
<!-- input框 -->
<input id="myCropper-input" type="file" :accept="imgCropperData.accept" ref="inputer" @change="handleFile">
//模擬點(diǎn)擊
document.getElementById('myCropper-input').click();
給input綁定一個(gè)監(jiān)聽(tīng)內(nèi)容變化的方法,拿到上傳的文件,并進(jìn)行格式、大小校驗(yàn)
// imgCropperData: {
// accept: 'image/gif, image/jpeg, image/png, image/bmp',
// }
handleFile (e) {
let _this = this;
let inputDOM = this.$refs.inputer;
// 通過(guò)DOM取文件數(shù)據(jù)
_this.file = inputDOM.files[0];
// 判斷文件格式
if (_this.imgCropperData.accept.indexOf(_this.file.type) == -1) {
_this.$Modal.error({
title: '格式錯(cuò)誤',
content: '您選擇的圖片格式不正確!'
});
return;
}
// 判斷文件大小限制
if (_this.file.size > 5242880) {
_this.$Modal.error({
title: '超出限制',
content: '您選擇的圖片過(guò)大,請(qǐng)選擇5MB以?xún)?nèi)的圖片!'
});
return;
}
var reader = new FileReader();
// 將圖片將轉(zhuǎn)成 base64 格式
reader.readAsDataURL(_this.file);
reader.onload = function () {
_this.imgCropperData.imgSrc = this.result;
_this.initCropper();
}
}
重新選擇圖片裁剪
當(dāng)?shù)谝淮芜x擇圖片之后,肯定會(huì)面臨需要重選圖片的問(wèn)題,那么就會(huì)面臨如何替換掉裁剪框中的圖片,上面的步驟選擇了圖片后通過(guò)FileRender()方法拿到了圖片的主要信息,現(xiàn)在就需要重新構(gòu)建裁剪框就可以解決問(wèn)題了,查看cropper.js給出的官方demo,發(fā)現(xiàn)官方是使用動(dòng)態(tài)添加裁剪容器的方法,進(jìn)行操作的,這里我們仿照官方進(jìn)行實(shí)現(xiàn)。
// 初始化剪切
initCropper () {
let _this = this;
// 初始化裁剪區(qū)域
_this.imgObj = $('');
let $avatarPreview = $('.avatar-preview');
$('#myCropper-workspace').empty().html(_this.imgObj);
_this.imgObj.cropper({
aspectRatio: _this.proportionX / _this.proportionY,
preview: $avatarPreview,
crop: function(e) {
}
});
}
確認(rèn)裁剪并獲取base64格式的圖片信息
let $imgData = _this.imgObj.cropper('getCroppedCanvas')
imgBase64Data = $imgData.toDataURL('image/png');
構(gòu)造用于上傳的數(shù)據(jù)
// 構(gòu)造上傳圖片的數(shù)據(jù)
let formData = new FormData();
// 截取字符串
let photoType = imgBase64Data.substring(imgBase64Data.indexOf(",") + 1);
//進(jìn)制轉(zhuǎn)換
const b64toBlob = (b64Data, contentType = '', sliceSize = 512) => {
const byteCharacters = atob(b64Data);
const byteArrays = [];
for(let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for(let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
const blob = new Blob(byteArrays, {
type: contentType
});
return blob;
}
const contentType = 'image/jepg';
const b64Data2 = photoType;
const blob = b64toBlob(b64Data2, contentType);
formData.append("file", blob, "client-camera-photo.png")
formData.append("type", _this.imgType)
非父子組件之間的通信問(wèn)題
在之前的項(xiàng)目中,常用到父子組件之間的通信傳參,一般用兩種方法
在router里面放置參數(shù),然后通過(guò)調(diào)用route.params.xxx或者route.query.xxx進(jìn)行獲取
通過(guò)props進(jìn)行通信
這里我們使用eventBus進(jìn)行組件之間的通信
步驟
1.聲明一個(gè)bus組件用于B組件把參數(shù)傳遞給A組件
//bus.js import Vue from 'vue'; export default new Vue();
2.在A組件中引用bus組件,并實(shí)時(shí)監(jiān)聽(tīng)其參數(shù)變化
// A.vue
import Bus from '../../components/bus/bus.js'
export default {
components: { Bus },
data () {},
created: function () {
Bus.$on('getTarget', imgToken => {
var _this = this;
console.log(imgToken);
...
});
}
}
3.B組件中同樣引用bus組件,來(lái)把參數(shù)傳給A組件
// B.vue
// 傳參
Bus.$emit('getTarget', imgToken);
參考:
vue-$on
vue-$emit
vue.js之路(4)——vue2.0s中eventBus實(shí)現(xiàn)兄弟組件通信
vue選圖截圖插件完整代碼
<template>
<div class="myCropper-container">
<div id="myCropper-workspace">
<div class="myCropper-words" v-show="!imgCropperData.imgSrc">請(qǐng)點(diǎn)擊按鈕選擇圖片進(jìn)行裁剪</div>
</div>
<div class="myCropper-preview" :class="isShort ? 'myCropper-preview-short' : 'myCropper-preview-long'">
<div class="myCropper-preview-1 avatar-preview">

</div>
<div class="myCropper-preview-2 avatar-preview">

</div>
<div class="myCropper-preview-3 avatar-preview">

</div>
<input id="myCropper-input" type="file" :accept="imgCropperData.accept" ref="inputer" @change="handleFile">
<Button type="ghost" class="myCropper-btn" @click="btnClick">選擇圖片</Button>
<Button type="primary" class="myCropper-btn" :loading="cropperLoading" @click="crop_ok">確認(rèn)</Button>
</div>
</div>
</template>
<script>
var ezjsUtil = Vue.ezjsUtil;
import Bus from './bus/bus.js'
export default {
components: { Bus },
props: {
imgType: {
type: String
},
proportionX: {
type: Number
},
proportionY: {
type: Number
}
},
data () {
return {
imgCropperData: {
accept: 'image/gif, image/jpeg, image/png, image/bmp',
maxSize: 5242880,
file: null, //上傳的文件
imgSrc: '', //讀取的img文件base64數(shù)據(jù)流
imgUploadSrc: '', //裁剪之后的img文件base64數(shù)據(jù)流
},
imgObj: null,
hasSelectImg: false,
cropperLoading: false,
isShort: false,
}
},
created: function () {
let _this = this;
},
mounted: function () {
let _this = this;
// 初始化預(yù)覽區(qū)域
let maxWidthNum = Math.floor(300 / _this.proportionX);
let previewWidth = maxWidthNum * _this.proportionX;
let previewHeight = maxWidthNum * _this.proportionY;
if (previewWidth / previewHeight <= 1.7) {
previewWidth = previewWidth / 2;
previewHeight = previewHeight / 2;
_this.isShort = true;
}
// 設(shè)置最大預(yù)覽容器的寬高
$('.myCropper-preview-1').css('width', previewWidth + 'px');
$('.myCropper-preview-1').css('height', previewHeight + 'px');
// 設(shè)置中等預(yù)覽容器的寬高
$('.myCropper-container .myCropper-preview .myCropper-preview-2').css('width',( previewWidth / 2) + 'px');
$('.myCropper-container .myCropper-preview .myCropper-preview-2').css('height', (previewHeight / 2) + 'px');
// 設(shè)置最小預(yù)覽容器的寬高
$('.myCropper-container .myCropper-preview .myCropper-preview-3').css('width',( previewWidth / 4) + 'px');
$('.myCropper-container .myCropper-preview .myCropper-preview-3').css('height', (previewHeight / 4) + 'px');
},
methods: {
// 點(diǎn)擊選擇圖片
btnClick () {
let _this = this;
// 模擬input點(diǎn)擊選擇文件
document.getElementById('myCropper-input').click();
},
// 選擇之后的回調(diào)
handleFile (e) {
let _this = this;
let inputDOM = this.$refs.inputer;
// 通過(guò)DOM取文件數(shù)據(jù)
_this.file = inputDOM.files[0];
// 判斷文件格式
if (_this.imgCropperData.accept.indexOf(_this.file.type) == -1) {
_this.$Modal.error({
title: '格式錯(cuò)誤',
content: '您選擇的圖片格式不正確!'
});
return;
}
// 判斷文件大小限制
if (_this.file.size > 5242880) {
_this.$Modal.error({
title: '超出限制',
content: '您選擇的圖片過(guò)大,請(qǐng)選擇5MB以?xún)?nèi)的圖片!'
});
return;
}
var reader = new FileReader();
// 將圖片將轉(zhuǎn)成 base64 格式
reader.readAsDataURL(_this.file);
reader.onload = function () {
_this.imgCropperData.imgSrc = this.result;
_this.initCropper();
}
},
// 初始化剪切
initCropper () {
let _this = this;
// 初始化裁剪區(qū)域
_this.imgObj = $('');
let $avatarPreview = $('.avatar-preview');
$('#myCropper-workspace').empty().html(_this.imgObj);
_this.imgObj.cropper({
aspectRatio: _this.proportionX / _this.proportionY,
preview: $avatarPreview,
crop: function(e) {
}
});
_this.hasSelectImg = true;
},
// 確認(rèn)
crop_ok () {
let _this = this, imgToken = null, imgBase64Data = null;
// 判斷是否選擇圖片
if (_this.hasSelectImg == false) {
_this.$Modal.error({
title: '裁剪失敗',
content: '請(qǐng)選擇圖片,然后進(jìn)行裁剪操作!'
});
return false;
}
// 確認(rèn)按鈕不可用
_this.cropperLoading = true;
let $imgData = _this.imgObj.cropper('getCroppedCanvas')
imgBase64Data = $imgData.toDataURL('image/png');
// 構(gòu)造上傳圖片的數(shù)據(jù)
let formData = new FormData();
// 截取字符串
let photoType = imgBase64Data.substring(imgBase64Data.indexOf(",") + 1);
//進(jìn)制轉(zhuǎn)換
const b64toBlob = (b64Data, contentType = '', sliceSize = 512) => {
const byteCharacters = atob(b64Data);
const byteArrays = [];
for(let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for(let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
const blob = new Blob(byteArrays, {
type: contentType
});
return blob;
}
const contentType = 'image/jepg';
const b64Data2 = photoType;
const blob = b64toBlob(b64Data2, contentType);
formData.append("file", blob, "client-camera-photo.png")
formData.append("type", _this.imgType)
// ajax上傳
$.ajax({
url: _this.$nfs.uploadUrl,
method: 'POST',
data: formData,
// 默認(rèn)為true,設(shè)為false后直到ajax請(qǐng)求結(jié)束(調(diào)完回掉函數(shù))后才會(huì)執(zhí)行$.ajax(...)后面的代碼
async: false,
// 下面三個(gè),因?yàn)橹苯邮褂肍ormData作為數(shù)據(jù),contentType會(huì)自動(dòng)設(shè)置,也不需要jquery做進(jìn)一步的數(shù)據(jù)處理(序列化)。
cache: false,
contentType: false,
processData: false,
type: _this.imgType,
success: function(res) {
let imgToken = res.data.token;
_this.cropperLoading = false;
// 傳參
Bus.$emit('getTarget', imgToken);
},
error: function(error) {
_this.cropperLoading = false;
_this.$Modal.error({
title: '系統(tǒng)錯(cuò)誤',
content: '請(qǐng)重新裁剪圖片進(jìn)行上傳!'
});
}
});
},
}
}
</script>
<style lang="less" scoped>
.myCropper-container {
height: 400px;
}
.myCropper-container #myCropper-input {
width: 0px;
height: 0px;
}
.myCropper-container #myCropper-workspace {
width: 500px;
height: 400px;
border: 1px solid #dddee1;
float: left;
}
// 裁剪圖片未選擇圖片的提示文字
.myCropper-container #myCropper-workspace .myCropper-words{
text-align: center;
font-size: 18px;
padding-top: 180px;
}
// 裁剪圖片的預(yù)覽區(qū)域
.myCropper-container .myCropper-preview-long {
width: 300px;
}
.myCropper-container .myCropper-preview-short {
width: 200px;
}
.myCropper-container .myCropper-preview {
float: left;
height: 400px;
margin-left: 10px;
}
.myCropper-container .myCropper-preview .myCropper-preview-1 {
border-radius: 5px;
overflow: hidden;
border: 1px solid #dddee1;
box-shadow: 3px 3px 3px #dddee1;
img {
width: 100%;
height: 100%;
}
}
.myCropper-container .myCropper-preview .myCropper-preview-2 {
margin-top: 20px;
border-radius: 5px;
overflow: hidden;
border: 1px solid #dddee1;
box-shadow: 3px 3px 3px #dddee1;
img {
width: 100%;
height: 100%;
}
}
.myCropper-container .myCropper-preview .myCropper-preview-3 {
margin-top: 20px;
border-radius: 5px;
overflow: hidden;
border: 1px solid #dddee1;
box-shadow: 3px 3px 3px #dddee1;
img {
width: 100%;
height: 100%;
}
}
// 按鈕
.myCropper-btn {
float: left;
margin-top: 20px;
margin-right: 10px;
}
</style>
BY-LucaLJX
總結(jié)
以上所述是小編給大家介紹的基于cropper.js封裝vue實(shí)現(xiàn)在線圖片裁剪組件功能,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
- 前端vue-cropperjs實(shí)現(xiàn)圖片裁剪方案
- Vue使用Vue-cropper實(shí)現(xiàn)圖片裁剪
- vue圖片裁剪插件vue-cropper使用方法詳解
- Vue-cropper 圖片裁剪的基本原理及思路講解
- vue-cli結(jié)合Element-ui基于cropper.js封裝vue實(shí)現(xiàn)圖片裁剪組件功能
- cropper js基于vue的圖片裁剪上傳功能的實(shí)現(xiàn)代碼
- vue移動(dòng)端裁剪圖片結(jié)合插件Cropper的使用實(shí)例代碼
- 在Vue3項(xiàng)目中使用VueCropper裁剪組件實(shí)現(xiàn)裁剪及預(yù)覽效果
相關(guān)文章
element-plus/element-ui走馬燈配置圖片及圖片自適應(yīng)的最簡(jiǎn)便方法
走馬燈功能在展示圖片時(shí)經(jīng)常用到,下面這篇文章主要給大家介紹了關(guān)于element-plus/element-ui走馬燈配置圖片及圖片自適應(yīng)的最簡(jiǎn)便方法,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-03-03
vue使用lottie-web實(shí)現(xiàn)web動(dòng)畫(huà)效果
在web端,lottie-web庫(kù)可以解析導(dǎo)出的動(dòng)畫(huà)json文件,并將其以svg或者canvas的方式將動(dòng)畫(huà)繪制在我們的頁(yè)面上,這篇文章主要介紹了vue使用lottie-web實(shí)現(xiàn)web動(dòng)畫(huà),需要的朋友可以參考下2024-06-06
vue配置electron使用electron-builder進(jìn)行打包的操作方法
這篇文章主要介紹了vue配置electron使用electron-builder進(jìn)行打包的操作方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-08-08
解決VUE自定義拖拽指令時(shí) onmouseup 與 click事件沖突問(wèn)題
這篇文章主要介紹了解決VUE自定義拖拽指令時(shí) onmouseup 與 click事件沖突問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-07-07
vue-echarts如何實(shí)現(xiàn)圖表組件封裝詳解
由于在項(xiàng)目中需要對(duì)數(shù)據(jù)進(jìn)行可視化處理,也就是用圖表展示,所以下面這篇文章主要給大家介紹了關(guān)于vue-echarts如何實(shí)現(xiàn)圖表組件封裝的相關(guān)資料,需要的朋友可以參考下2022-05-05
Vue2.0父組件與子組件之間的事件發(fā)射與接收實(shí)例代碼
這篇文章主要介紹了Vue2.0父組件與子組件之間的事件發(fā)射與接收實(shí)例代碼,需要的朋友可以參考下2017-09-09
詳解從新建vue項(xiàng)目到引入組件Element的方法
本篇文章主要介紹了詳解從新建vue項(xiàng)目到引入組件Element的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-08-08

