起由
最近在工作中遇到了在 VUE 下需要簡單裁剪圖片的需求,同時還要對圖片進行相關大小和格式的限制
這個時候就想起了在 jq 時代大名鼎鼎的 cropper.js 了
簡單的界面設計如下圖:

分析
從圖中可以看出,我們需要的功能主要有如下幾方面的內(nèi)容:
- 獲取到圖片資源
- 一個帶模板的彈出層
- 截圖組件
- 交互區(qū)域(這里指圖中的確定取消按鈕)
- 足夠的可擴展性
實現(xiàn)思路
首先需要完成的當然是獲取到用戶上傳的文件,第一個想法是使用原生的方案,也即使用原生的 input 框進行相關事件的獲取
<input type="file" />
這樣的方式擴展是最靈活的,但是同時也是最繁瑣的,事無巨細都需要一一考慮,這不符合我們敏捷開發(fā)的原則
考慮到項目中已引入 elementui 的環(huán)境,故考慮使用 中的
組件來進行獲取文件的操作
項目需求決定只能操作單張圖片,進行相關配置后效果圖如下:

獲取到了文件之后首先需要對于 文件類型 和 大小 進行校驗,確定其為我們真實希望獲得的類型和滿足我們對于圖片的大小限制
接著我們需要將獲取到的圖片傳遞給截圖組件,且在用戶按下確認或取消時能 立即響應, 完成獲取截圖的文件和截圖等過程,最后將新截的圖片加進預覽并保存新的圖片狀態(tài)
以上便是我們在這個組件中整個想要完成的功能流程
Show me the code
其實在同樣的思路下,會有千萬種實現(xiàn)方法,以下僅是我個人對于這一想法的一些實踐,歡迎討論指正
首先是有 標簽(此處隱去引入庫及掛載過程)
<el-upload
:before-upload="beforeUpload" // 上傳前的事件,在這里做文件類型和大小的檢查
:file-list="showImgUrl" // 圖片預覽列表
:http-request="picUpload" // 上傳事件,覆蓋默認上傳事件,應在此處將圖片傳入截圖組件
:limit="1" // 文件數(shù)量限制
:multiple="false" // 選擇文件時不允許選擇多文件
:on-exceed="overFile" // 文件數(shù)量超出限制時的鉤子函數(shù),應在此處做覆蓋以及提示
:on-remove="removeFile" // 圖片刪除時的鉤子函數(shù),應在此時清空圖片的緩存相關操作
action // 默認跳轉(zhuǎn)地址,這里設置為空
list-type="picture-card" // 文件選擇框的樣式以及插入動畫
ref="upload" // DOM 鉤子
>
<i class="el-icon-plus"></i> // 樣式需要的圖標
</el-upload>
我們整個流程都可以從 標簽的生命周期處體現(xiàn)
接著我們來一個一個看看在這些鉤子函數(shù)里我們都做了什么吧:
// 圖片上傳前校驗
// 這里對圖片類型和大小做出限制
beforeUpload(file) {
const isLt5M = file.size < 5 * 1024 * 1024
const fileType = file.type.split('/')[0]
if (!isLt5M) {
this.$message.error('圖片大小不能超過5M')
return false
}
if (fileType !== 'image') {
this.$message.error('只能上傳圖片格式')
return false
}
},
圖片校驗通過后即進入裁剪流程,我們是通過覆蓋默認上傳事件實現(xiàn)的
在這里由于從本地讀出時沒有相關的 url 信息,所以我們在這里使用 fileReader API 把獲取到的圖片文件轉(zhuǎn)化成 base64 的格式,然后再進行相關的操作
// 在生命周期掛載完成的時候判斷瀏覽器中是否為現(xiàn)代瀏覽器
// 若不支持 FileReader API 則提示并退出編輯操作
mounted() {
if (!window.FileReader) {
this.$alert('您的瀏覽器版本過舊,請更換現(xiàn)代瀏覽器進行操作', '提示').then(
() => {
this.__cancel()
}
)
}
},
// 圖片上傳前觸發(fā)裁剪組件
// 將圖片讀出并在完成時觸發(fā)裁剪
picUpload(option) {
let file = option.file
if (file) {
this.fileReader.readAsDataURL(file)
}
this.fileReader.onload = () => {
let src = this.fileReader.result
this.cropperShow = true
this.cropperImg = src
}
},
這里應該調(diào)起裁剪插件,猛地發(fā)現(xiàn),插件我都還沒新增鴨
可怕!!
于是就回去翻了翻 cropperjs 的文檔,emmmm, 真的沒有意外,還是 jq 時代的樣子,沒有辦法直接拿下來就用,那么這個輪子究竟有沒有人已經(jīng)造過了呢,目光下移, vue-cropper 進入了我的視線
(這里忽略下載引入的過程,直接從使用開始)
// 圖片裁剪引用
// el-dialog 為 element 中彈框組件
// cropperShow: 是否顯示 load: 用戶是否處于交互狀態(tài) cropperImg: 圖片源
// 更多配置項請參考 vue-cropper 相關文檔
<el-dialog :visible.sync="cropperShow" width="640px" :close-on-click-modal="false" :show-close="false">
<span slot="title">操作圖片</span> // 標題文字
<vueCropper
ref="cropper" // Dom鉤子,用于獲取綁定其上的相關屬性及函數(shù)
class="cropper"
:img="cropperImg" // 裁剪圖片源
:can-scale="!load" // 是否允許滾輪縮放
:auto-crop="true" // 是否默認生成截圖框
:fixed-box="true" // 是否固定截圖框大小
enlarge="2" // 輸出比例倍數(shù)
:output-size="option.size" // 生成圖片質(zhì)量
:output-type="option.outputType" // 生成圖片格式
:center-box="option.centerBox" // 截圖框是否限制在圖片內(nèi)
:fixed-number="[1,1]" // 截圖框的寬高比例
></vueCropper>
<div class="cropper-btn">
<el-button type="primary" @click="confirm" :loading="load">確定</el-button>
<el-button @click="cancel" :disabled="load">取消</el-button>
</div>
<el-dialog // 用戶交互中彈出的提示彈框
width="30%"
:visible.sync="load"
append-to-body
>
<div slot="title">飛速處理圖片中...</div>
</el-dialog>
</el-dialog>
// 配置參數(shù)
option: {
size: 0,
outputType: '',
centerBox: false
},
此時我們就能看到文章開頭所呈現(xiàn)的相關效果了,并且我們也能通過插件提供的鉤子函數(shù)獲取到相應的圖片數(shù)據(jù)
但是我們想要走完整個流程,還必須添加交互響應事件以及進行相關的上傳操作
// 確定選擇
// dom.getCropData() 獲取當前截圖框圖像的 base64 格式的數(shù)據(jù)
// 獲取成功數(shù)據(jù)后調(diào)起上傳操作
confirm() {
this.$refs.cropper.getCropData(data => {
this.httpRequest(data)
this.load = true
})
},
// 取消
cancel() {
console.log('cancel edit picture')
// 關閉裁剪組件相關操作
}
// 圖片裁剪后上傳
// 這里可進行進一步優(yōu)化將 config 請求配置寫入 http 請求庫中
httpRequest(src) {
let config = {
url: '***', // 這里填后端的圖片上傳鏈接
method: 'post',
data: {
// 截取 base64 文件部分
image: src.split(',')[1]
}
}
this.$ehttp
.postUpload(config)
.then(res => {
console.log(res, '*********************************************')
if (res.data.code === 0) {
this.params.properties.imageHash = res.data.data
this.$message.success('圖片編輯成功')
this.$refs.upload.clearFiles() // 清空預覽列表
this.showImgUrl = [{ name: 'pic', url: src }] // 將新上傳圖片插入上傳列表
this.cropperShow = false
} else {
// this.$message.error('res.dat')
this.$refs.upload.clearFiles()
}
})
.catch(err => {
this.$message.error(err)
})
},
結(jié)語
至此我們整個裁剪插件就已經(jīng)全部完成了,通過上傳回參可以任意處理裁剪后的圖像了
整個流程下來主要的感覺是對于圖片格式的轉(zhuǎn)換會相對的比較繁瑣
vue-cropper 插件本身只完成裁剪的功能,方便我們在此基礎上開發(fā)個性化的裁剪插件
同時由于其基于 cropperjs 開發(fā),所以在截圖框的樣式方面由插件寫死了,若是我們對于截圖框的樣式也有個性化需求則可 down 下源碼進行自定義修改,這已經(jīng)超出了本文的范圍故會在后面的文章中進行相關的研究(坑先挖好,填不填就看心情了hhh)

參考資料
ElementUI: https://element.eleme.cn/#/zh-CN/component/upload
CropperJS: https://github.com/fengyuanchen/cropper
vue-cropper: https://github.com/xyxiao001/vue-cropper
PS
最后當然是日常求贊環(huán)節(jié)啦~
Clancy
2019.5.30