創(chuàng)建公用文件 cropper.vue 摘自github vue-cropper組件,節(jié)省了安裝的步驟
<template>
<div class="vue-cropper" ref="cropper">
<div class="cropper-box">
<div class="cropper-box-canvas"
v-show="!loading"
:style="{
'width': trueWidth + 'px',
'height': trueHeight + 'px',
'transform': 'scale(' + scale + ',' + scale + ') ' + 'translate3d('+ x / scale + 'px,' + y / scale + 'px,' + '0)'
+ 'rotateZ('+ rotate * 90 +'deg)'
}"
>
<img
:src="imgs"
alt="cropper-img"
ref="cropperImg"
/>
</div>
</div>
<div
class="cropper-drag-box"
:class="{'cropper-move': move && !crop, 'cropper-crop': crop, 'cropper-modal': cropping}"
@mousedown="startMove"
>
</div>
<div
v-show="cropping"
class="cropper-crop-box"
:style="{
'width': cropW + 'px',
'height': cropH + 'px',
'transform': 'translate3d('+ cropOffsertX + 'px,' + cropOffsertY + 'px,' + '0)'
}">
<span class="cropper-view-box">
<img
:style="{
'width': trueWidth + 'px',
'height': trueHeight + 'px',
'transform': 'scale(' + scale + ',' + scale + ') ' + 'translate3d('+ (x - cropOffsertX) / scale + 'px,' + (y - cropOffsertY) / scale + 'px,' + '0)'
+ 'rotateZ('+ rotate * 90 +'deg)'
}"
:src="imgs"
alt="cropper-img"
/>
</span>
<span
class="cropper-face cropper-move"
@mousedown="cropMove"
></span>
<span class="crop-info" v-if="info" :style="{'top': cropInfo}">{{ this.cropW > 0 ? this.cropW : 0 }} × {{ this.cropH > 0 ? this.cropH : 0 }}</span>
<span v-if="!fixedBox">
<span class="crop-line line-w" @mousedown="changeCropSize($event, false, true, 0, 1)"></span>
<span class="crop-line line-a" @mousedown="changeCropSize($event, true, false, 1, 0)"></span>
<span class="crop-line line-s" @mousedown="changeCropSize($event, false, true, 0, 2)"></span>
<span class="crop-line line-d" @mousedown="changeCropSize($event, true, false, 2, 0)"></span>
<span class="crop-point point1" @mousedown="changeCropSize($event, true, true, 1, 1)"></span>
<span class="crop-point point2" @mousedown="changeCropSize($event, false, true, 0, 1)"></span>
<span class="crop-point point3" @mousedown="changeCropSize($event, true, true, 2, 1)"></span>
<span class="crop-point point4" @mousedown="changeCropSize($event, true, false, 1, 0)"></span>
<span class="crop-point point5" @mousedown="changeCropSize($event, true, false, 2, 0)"></span>
<span class="crop-point point6" @mousedown="changeCropSize($event, true, true, 1, 2)"></span>
<span class="crop-point point7" @mousedown="changeCropSize($event, false, true, 0, 2)"></span>
<span class="crop-point point8" @mousedown="changeCropSize($event, true, true, 2, 2)"></span>
</span>
</div>
</div>
</template>
<script>
export default {
data: function () {
return {
// 容器高寬
w: 0,
h: 0,
// 圖片縮放比例
scale: 1,
// 圖片偏移x軸
x: 0,
// 圖片偏移y軸
y: 0,
// 圖片加載
loading: true,
// 圖片真實(shí)寬度
trueWidth: 0,
// 圖片真實(shí)高度
trueHeight: 0,
move: true,
// 移動(dòng)的x
moveX: 0,
// 移動(dòng)的y
moveY: 0,
// 開(kāi)啟截圖
crop: false,
// 正在截圖
cropping: false,
// 裁剪框大小
cropW: 0,
cropH: 0,
cropOldW: 0,
cropOldH: 0,
// 判斷是否能夠改變
canChangeX: false,
canChangeY: false,
// 改變的基準(zhǔn)點(diǎn)
changeCropTypeX: 1,
changeCropTypeY: 1,
// 裁剪框的坐標(biāo)軸
cropX: 0,
cropY: 0,
cropChangeX: 0,
cropChangeY: 0,
cropOffsertX: 0,
cropOffsertY: 0,
// 支持的滾動(dòng)事件
support: '',
// 圖片旋轉(zhuǎn)
rotate: 0,
orientation: 0,
imgs: '',
// 圖片縮放系數(shù)
coe: 0.2,
// 是否正在多次縮放
scaling: false,
scalingSet: '',
coeStatus: ''
}
},
props: {
img: {
type: String,
default: ''
},
// 輸出圖片壓縮比
outputSize: {
type: Number,
default: 1
},
outputType: {
type: String,
default: 'jpeg'
},
info: {
type: Boolean,
default: true
},
// 是否自成截圖框
autoCrop: {
type: Boolean,
default: false
},
autoCropWidth: {
type: Number,
default: 0
},
autoCropHeight: {
type: Number,
default: 0
},
// 是否開(kāi)啟固定寬高比
fixed: {
type: Boolean,
default: false
},
// 寬高比 w/h
fixedNumber: {
type: Array,
default: () => {
return [1, 1]
}
},
// 固定大小 禁止改變截圖框大小
fixedBox: {
type: Boolean,
default: false
},
// 輸出截圖是否縮放
full: {
type: Boolean,
default: false
},
// 是否可以拖動(dòng)圖片
canMove: {
type: Boolean,
default: true
},
// 是否可以拖動(dòng)截圖框
canMoveBox: {
type: Boolean,
default: true
},
// 上傳圖片按照原始比例顯示
original: {
type: Boolean,
default: false
}
},
computed: {
cropInfo () {
return this.cropOffsertY > 20 ? '-20px' : '0px'
}
},
watch: {
// 如果圖片改變, 重新布局
img () {
// 當(dāng)傳入圖片時(shí), 讀取圖片信息同時(shí)展示
this.checkedImg()
},
imgs (val) {
if (val === '') {
return
}
this.reload()
},
cropW () {
this.cropW = ~~(this.cropW)
this.showPreview()
},
cropH () {
this.cropH = ~~(this.cropH)
this.showPreview()
},
cropOffsertX () {
this.showPreview()
},
cropOffsertY () {
this.showPreview()
},
scale () {
this.showPreview()
},
x () {
this.showPreview()
},
y () {
this.showPreview()
},
rotate () {
this.showPreview()
}
},
methods: {
// 校驗(yàn)圖片
checkedImg () {
if (this.img === '') return
this.loading = true
this.scale = 1
this.clearCrop()
let canvas = document.createElement('canvas')
let img = new Image
let rotate = 0
img.onload = () => {
let width = img.width
let height = img.height
let ctx = canvas.getContext('2d')
ctx.save()
switch (this.orientation) {
case 6:
rotate = 1
break
case 8:
rotate = -1
break
case 3:
rotate = 3
break
default:
rotate = 0
}
if (rotate === 0) {
this.imgs = this.img
return
}
switch (rotate) {
case 0:
canvas.width = width
canvas.height = height
ctx.drawImage(img, 0, 0, width, height)
break
case 1:
case -3:
// 旋轉(zhuǎn)90度 或者-270度 寬度和高度對(duì)調(diào)
canvas.width = height
canvas.height = width
ctx.rotate(rotate * 90 * Math.PI / 180)
ctx.drawImage(img, 0, -height, width, height)
break
case 2:
case -2:
canvas.width = width
canvas.height = height
ctx.rotate(rotate * 90 * Math.PI / 180)
ctx.drawImage(img, -width, -height, width, height)
break
case 3:
case -1:
canvas.width = height
canvas.height = width
ctx.rotate(rotate * 90 * Math.PI / 180)
ctx.drawImage(img, -width, 0, width, height)
break
default:
canvas.width = width
canvas.height = height
ctx.drawImage(img, 0, 0, width, height)
}
ctx.restore()
canvas.toBlob((blob) => {
let data = URL.createObjectURL(blob)
this.imgs = data
}, 'image/' + this.outputType, 1)
}
img.onerror = () => {
this.$emit('imgLoad', 'error')
}
img.crossOrigin = '*'
img.src = this.img
},
// 當(dāng)按下鼠標(biāo)鍵
startMove (e) {
e.preventDefault()
// 如果move 為true 表示當(dāng)前可以拖動(dòng)
if (this.move && !this.crop) {
if (!this.canMove) {
return false
}
// 開(kāi)始移動(dòng)
this.moveX = e.clientX - this.x
this.moveY = e.clientY - this.y
window.addEventListener('mousemove', this.moveImg)
window.addEventListener('mouseup', this.leaveImg)
} else {
// 截圖ing
this.cropping = true
// 綁定截圖事件
window.addEventListener('mousemove', this.createCrop)
window.addEventListener('mouseup', this.endCrop)
this.cropOffsertX = e.offsetX;
this.cropOffsertY = e.offsetY;
this.cropX = e.clientX;
this.cropY = e.clientY;
this.cropChangeX = this.cropOffsertX;
this.cropChangeY = this.cropOffsertY;
this.cropW = 0
this.cropH = 0
}
},
// 移動(dòng)圖片
moveImg (e) {
e.preventDefault()
var nowX = e.clientX;
var nowY = e.clientY;
this.$nextTick(() => {
this.x = ~~(nowX - this.moveX)
this.y = ~~(nowY - this.moveY)
})
},
// 移動(dòng)圖片結(jié)束
leaveImg (e) {
window.removeEventListener('mousemove', this.moveImg);
window.removeEventListener('mouseup', this.leaveImg);
},
// 修改圖片大小函數(shù)
changeScale (num) {
num = num || 1
var coe = 20
coe = coe / this.trueWidth > coe / this.trueHeight ? coe / this.trueHeight : coe / this.trueWidth
num = num * coe
num > 0 ? this.scale += Math.abs(num) : this.scale > Math.abs(num) ? this.scale -= Math.abs(num) : this.scale
},
// 創(chuàng)建截圖框
createCrop (e) {
e.preventDefault()
// 移動(dòng)生成大小
var nowX = e.clientX ? e.clientX : 0
var nowY = e.clientY ? e.clientY : 0
this.$nextTick(() => {
var fw = ~~(nowX - this.cropX)
var fh = ~~(nowY - this.cropY)
if (fw > 0) {
this.cropW = fw + this.cropChangeX > this.w ? this.w - this.cropChangeX : fw
this.cropOffsertX = this.cropChangeX
} else {
this.cropW = (this.w - this.cropChangeX + Math.abs(fw)) > this.w ? this.cropChangeX : Math.abs(fw)
this.cropOffsertX = this.cropChangeX + fw > 0 ? this.cropChangeX + fw : 0
}
if (!this.fixed) {
if (fh > 0) {
this.cropH = fh + this.cropChangeY > this.h ? this.h - this.cropChangeY : fh
this.cropOffsertY = this.cropChangeY
} else {
this.cropH = (this.h - this.cropChangeY + Math.abs(fh)) > this.h ? this.cropChangeY : Math.abs(fh)
this.cropOffsertY = this.cropChangeY + fh > 0 ? this.cropChangeY + fh : 0
}
} else {
var fixedHeight = ~~(this.cropW / this.fixedNumber[0] * this.fixedNumber[1])
if (fixedHeight + this.cropOffsertY > this.h) {
this.cropH = this.h - this.cropOffsertY
this.cropW = ~~(this.cropH / this.fixedNumber[1] * this.fixedNumber[0])
if (fw > 0) {
this.cropOffsertX = this.cropChangeX
} else {
this.cropOffsertX = this.cropChangeX - this.cropW
}
} else {
this.cropH = fixedHeight
}
this.cropOffsertY = this.cropOffsertY
}
})
},
// 改變截圖框大小
changeCropSize (e, w, h, typeW, typeH) {
e.preventDefault()
window.addEventListener('mousemove', this.changeCropNow)
window.addEventListener('mouseup', this.changeCropEnd)
this.canChangeX = w;
this.canChangeY = h;
this.changeCropTypeX = typeW;
this.changeCropTypeY = typeH;
this.cropX = e.clientX;
this.cropY = e.clientY;
this.cropOldW = this.cropW;
this.cropOldH = this.cropH;
this.cropChangeX = this.cropOffsertX
this.cropChangeY = this.cropOffsertY
if (this.fixed) {
if (this.canChangeX && this.canChangeY) {
this.canChangeY = 0
}
}
},
// 正在改變
changeCropNow (e) {
e.preventDefault()
var nowX = e.clientX;
var nowY = e.clientY;
this.$nextTick(() => {
var fw = ~~(nowX - this.cropX)
var fh = ~~(nowY - this.cropY)
if (this.canChangeX) {
if (this.changeCropTypeX === 1) {
if (this.cropOldW - fw > 0) {
this.cropW = this.w - this.cropChangeX - fw <= this.w ? this.cropOldW - fw : this.cropOldW + this.cropChangeX
this.cropOffsertX = this.w - this.cropChangeX - fw <= this.w ? this.cropChangeX + fw : 0
} else {
console.log(fw);
this.cropW = Math.abs(fw) + this.cropChangeX <= this.w ? Math.abs(fw) - this.cropOldW : this.w - this.cropOldW - this.cropChangeX
this.cropOffsertX = this.cropChangeX + this.cropOldW
}
} else if (this.changeCropTypeX === 2) {
if (this.cropOldW + fw > 0) {
this.cropW = this.cropOldW + fw + this.cropOffsertX <= this.w ? this.cropOldW + fw : this.w - this.cropOffsertX
this.cropOffsertX = this.cropChangeX
} else {
this.cropW = (this.w - this.cropChangeX + Math.abs(fw + this.cropOldW)) <= this.w ? Math.abs(fw + this.cropOldW) : this.cropChangeX
this.cropOffsertX = (this.w - this.cropChangeX + Math.abs(fw + this.cropOldW)) <= this.w ? this.cropChangeX - Math.abs(fw + this.cropOldW) : 0
}
}
}
if (this.canChangeY) {
if (this.changeCropTypeY === 1) {
if (this.cropOldH - fh > 0) {
this.cropH = this.h - this.cropChangeY - fh <= this.h ? this.cropOldH - fh : this.cropOldH + this.cropChangeY
this.cropOffsertY = this.h - this.cropChangeY - fh <= this.h ? this.cropChangeY + fh : 0
} else {
this.cropH = Math.abs(fh) + this.cropChangeY <= this.h ? Math.abs(fh) - this.cropOldH : this.h - this.cropOldH - this.cropChangeY
this.cropOffsertY = this.cropChangeY + this.cropOldH
}
} else if (this.changeCropTypeY === 2) {
if (this.cropOldH + fh > 0) {
this.cropH = this.cropOldH + fh + this.cropOffsertY <= this.h ? this.cropOldH + fh : this.h - this.cropOffsertY
this.cropOffsertY = this.cropChangeY
} else {
this.cropH = (this.h - this.cropChangeY + Math.abs(fh + this.cropOldH)) <= this.h ? Math.abs(fh + this.cropOldH) : this.cropChangeY
this.cropOffsertY = (this.h - this.cropChangeY + Math.abs(fh + this.cropOldH)) <= this.h ? this.cropChangeY - Math.abs(fh + this.cropOldH) : 0
}
}
}
if (this.canChangeX && this.fixed) {
var fixedHeight = ~~(this.cropW / this.fixedNumber[0] * this.fixedNumber[1])
if (fixedHeight + this.cropOffsertY > this.h) {
this.cropH = this.h - this.cropOffsertY
this.cropW = ~~(this.cropH / this.fixedNumber[1] * this.fixedNumber[0])
} else {
this.cropH = fixedHeight
}
}
if (this.canChangeY && this.fixed) {
var fixedWidth = ~~(this.cropH / this.fixedNumber[1] * this.fixedNumber[0])
if (fixedWidth + this.cropOffsertX > this.w) {
this.cropW = this.w - this.cropOffsertX
this.cropH = ~~(this.cropW / this.fixedNumber[0] * this.fixedNumber[1])
} else {
this.cropW = fixedWidth
}
}
})
},
// 結(jié)束改變
changeCropEnd (e) {
window.removeEventListener('mousemove', this.changeCropNow)
window.removeEventListener('mouseup', this.changeCropEnd)
},
// 創(chuàng)建完成
endCrop () {
if (this.cropW === 0 && this.cropH === 0) {
this.cropping = false
}
window.removeEventListener('mousemove', this.createCrop)
window.removeEventListener('mouseup', this.endCrop)
},
// 開(kāi)始截圖
startCrop () {
this.crop = true
// console.log('開(kāi)始截圖')
},
// 停止截圖
stopCrop () {
this.crop = false
// console.log('停止截圖')
},
// 清除截圖
clearCrop () {
this.cropping = false
this.cropW = 0
this.cropH = 0
// console.log('清除截圖')
},
// 截圖移動(dòng)
cropMove (e) {
e.preventDefault()
if (!this.canMoveBox) {
this.crop = false
this.startMove(e)
return false
}
window.addEventListener('mousemove', this.moveCrop)
window.addEventListener('mouseup', this.leaveCrop)
this.cropX = e.clientX - this.cropOffsertX
this.cropY = e.clientY - this.cropOffsertY
},
moveCrop (e) {
e.preventDefault()
var nowX = e.clientX;
var nowY = e.clientY;
this.$nextTick(() => {
var fw = ~~(nowX - this.cropX);
var fh = ~~(nowY - this.cropY)
if (fw <= 1) {
this.cropOffsertX = 1
} else if (~~(fw + this.cropW) > this.w) {
this.cropOffsertX = this.w - this.cropW - 1
} else {
this.cropOffsertX = fw
}
if (fh <= 1) {
this.cropOffsertY = 1
} else if (~~(fh + this.cropH) > this.h) {
this.cropOffsertY = this.h - this.cropH - 1
} else {
this.cropOffsertY = fh
}
})
},
leaveCrop (e) {
window.removeEventListener('mousemove', this.moveCrop)
window.removeEventListener('mouseup', this.leaveCrop)
},
// 獲取轉(zhuǎn)換成base64 的圖片信息
getCropData (cb) {
let canvas = document.createElement('canvas')
let img = new Image
let rotate = this.rotate
let trueWidth = this.trueWidth
let trueHeight = this.trueHeight
let cropOffsertX = this.cropOffsertX
let cropOffsertY = this.cropOffsertY
img.onload = () => {
if (~~(this.cropW) !== 0) {
let ctx = canvas.getContext('2d')
let width = this.cropW
let height = this.cropH
let imgW = trueWidth * this.scale
let imgH = trueHeight * this.scale
// 圖片x軸偏移
let dx = (this.x - cropOffsertX) + this.trueWidth * (1 - this.scale) / 2
// 圖片y軸偏移
let dy = (this.y - cropOffsertY) + this.trueHeight * (1 - this.scale) / 2
// console.log(dx, dy)
//保存狀態(tài)
canvas.width = width
canvas.height = height
ctx.save()
switch (rotate) {
case 0:
if (!this.full) {
ctx.drawImage(img, dx, dy, imgW, imgH)
} else {
// 輸出原圖比例截圖
canvas.width = width / this.scale
canvas.height = height / this.scale
ctx.drawImage(img, dx / this.scale, dy / this.scale, imgW / this.scale, imgH / this.scale)
}
break
case 1:
case -3:
if (!this.full) {
// 換算圖片旋轉(zhuǎn)后的坐標(biāo)彌補(bǔ)
dx = dx + (imgW - imgH) / 2
dy = dy + (imgH - imgW) / 2
ctx.rotate(rotate * 90 * Math.PI / 180)
ctx.drawImage(img, dy, -dx - imgH, imgW, imgH)
} else {
canvas.width = width / this.scale
canvas.height = height / this.scale
// 換算圖片旋轉(zhuǎn)后的坐標(biāo)彌補(bǔ)
dx = dx / this.scale + (imgW / this.scale - imgH / this.scale) / 2
dy = dy / this.scale + (imgH / this.scale - imgW / this.scale) / 2
ctx.rotate(rotate * 90 * Math.PI / 180)
ctx.drawImage(img, dy, (-dx - imgH / this.scale), imgW / this.scale, imgH / this.scale)
}
break
case 2:
case -2:
if (!this.full) {
ctx.rotate(rotate * 90 * Math.PI / 180)
ctx.drawImage(img, -dx - imgW, -dy - imgH, imgW, imgH)
} else {
canvas.width = width / this.scale
canvas.height = height / this.scale
ctx.rotate(rotate * 90 * Math.PI / 180)
dx = dx / this.scale
dy = dy / this.scale
ctx.drawImage(img, -dx - imgW / this.scale, -dy - imgH / this.scale, imgW / this.scale, imgH / this.scale)
}
break
case 3:
case -1:
if (!this.full) {
// 換算圖片旋轉(zhuǎn)后的坐標(biāo)彌補(bǔ)
dx = dx + (imgW - imgH) / 2
dy = dy + (imgH - imgW) / 2
ctx.rotate(rotate * 90 * Math.PI / 180)
ctx.drawImage(img, -dy - imgW, dx, imgW, imgH)
} else {
canvas.width = width / this.scale
canvas.height = height / this.scale
// 換算圖片旋轉(zhuǎn)后的坐標(biāo)彌補(bǔ)
dx = dx / this.scale + (imgW / this.scale - imgH / this.scale) / 2
dy = dy / this.scale + (imgH / this.scale - imgW / this.scale) / 2
ctx.rotate(rotate * 90 * Math.PI / 180)
ctx.drawImage(img, -dy - imgW / this.scale, dx, imgW / this.scale, imgH / this.scale)
}
break
default:
if (!this.full) {
ctx.drawImage(img, dx, dy, imgW, imgH)
} else {
// 輸出原圖比例截圖
canvas.width = width / this.scale
canvas.height = height / this.scale
ctx.drawImage(img, dx / this.scale, dy / this.scale, imgW / this.scale, imgH / this.scale)
}
}
ctx.restore()
} else {
let width = trueWidth * this.scale
let height = trueHeight * this.scale
let ctx = canvas.getContext('2d')
ctx.save()
switch (rotate) {
case 0:
canvas.width = width
canvas.height = height
ctx.drawImage(img, 0, 0, width, height)
break
case 1:
case -3:
// 旋轉(zhuǎn)90度 或者-270度 寬度和高度對(duì)調(diào)
canvas.width = height
canvas.height = width
ctx.rotate(rotate * 90 * Math.PI / 180)
ctx.drawImage(img, 0, -height, width, height)
break
case 2:
case -2:
canvas.width = width
canvas.height = height
ctx.rotate(rotate * 90 * Math.PI / 180)
ctx.drawImage(img, -width, -height, width, height)
break
case 3:
case -1:
canvas.width = height
canvas.height = width
ctx.rotate(rotate * 90 * Math.PI / 180)
ctx.drawImage(img, -width, 0, width, height)
break
default:
canvas.width = width
canvas.height = height
ctx.drawImage(img, 0, 0, width, height)
}
ctx.restore()
}
let data = canvas.toDataURL('image/' + this.outputType, this.outputSize)
cb(data)
}
// 判斷圖片是否是base64
var s = this.img.substr(0, 4)
if (s !== 'data') {
img.crossOrigin = 'anonymous'
}
img.src = this.imgs
},
//轉(zhuǎn)化base64 為blob對(duì)象
getCropBlob(cb) {
this.getCropData((data) => {
var arr = data.split(',')
var mime = arr[0].match(/:(.*?);/)[1]
var bstr = atob(arr[1])
var n = bstr.length
var u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
cb(
new Blob([u8arr], {
type: mime
}
))
})
},
// 自動(dòng)預(yù)覽函數(shù)
showPreview() {
var obj = {}
obj.div = {
'width': this.cropW + 'px',
'height': this.cropH + 'px'
}
obj.img = {
'width': this.trueWidth + 'px',
'height': this.trueHeight + 'px',
'transform': 'scale(' + this.scale + ',' + this.scale + ') ' + 'translate3d('+ (this.x - this.cropOffsertX) / this.scale + 'px,' + (this.y - this.cropOffsertY) / this.scale + 'px,' + '0)'
+ 'rotateZ('+ this.rotate * 90 + 'deg)'
}
obj.w = this.cropW
obj.h = this.cropH
obj.url = this.imgs
this.$emit('realTime', obj)
},
// reload 圖片布局函數(shù)
reload () {
let img = new Image
img.onload = () => {
// 讀取圖片的信息原始信息, 解析是否需要旋轉(zhuǎn)
// 讀取圖片的旋轉(zhuǎn)信息
// 得到外層容器的寬度高度
this.w = ~~(window.getComputedStyle(this.$refs.cropper).width.replace('px', ''))
this.h = ~~(window.getComputedStyle(this.$refs.cropper).height.replace('px', ''))
// 存入圖片真實(shí)高度
this.trueWidth = img.width
this.trueHeight = img.height
// 判斷是否需要壓縮大圖
if (!this.original) {
if (this.trueWidth > this.w) {
// 如果圖片寬度大于容器寬度
this.scale = this.w / this.trueWidth
}
if (this.trueHeight * this.scale > this.h) {
this.scale = this.h / this.trueHeight
}
} else {
this.scale = 1
}
this.$nextTick(() => {
this.x = -(this.trueWidth - this.trueWidth * this.scale) / 2 + (this.w - this.trueWidth * this.scale) / 2
this.y = -(this.trueHeight - this.trueHeight * this.scale) / 2 + (this.h - this.trueHeight * this.scale) / 2
this.loading = false
// 獲取是否開(kāi)啟了自動(dòng)截圖
if (this.autoCrop) {
this.goAutoCrop()
}
// 圖片加載成功的回調(diào)
this.$emit('imgLoad', 'success')
})
}
img.onerror = () => {
this.$emit('imgLoad', 'error')
}
img.src = this.imgs
},
// 自動(dòng)截圖函數(shù)
goAutoCrop () {
this.cropping = true
// 截圖框默認(rèn)大小
// 如果為0 那么計(jì)算容器大小 默認(rèn)為80%
var w = this.autoCropWidth
var h = this.autoCropHeight
if (w === 0 || h === 0) {
w = this.w * 0.8
h = this.h * 0.8
}
w = w > this.w ? this.w : w
h = h > this.h ? this.h : h
if (this.fixed) {
h = w / this.fixedNumber[0] * this.fixedNumber[1]
}
// 如果比例之后 高度大于h
if (h > this.h) {
h = this.h
w = h / this.fixedNumber[1] * this.fixedNumber[0]
}
this.changeCrop(w, h)
},
// 手動(dòng)改變截圖框大小函數(shù)
changeCrop (w, h) {
// 判斷是否大于容器
this.cropW = w
this.cropH = h
// 居中走一走
this.cropOffsertX = (this.w - w) / 2
this.cropOffsertY = (this.h - h) / 2
},
// 重置函數(shù), 恢復(fù)組件置初始狀態(tài)
refresh () {
// console.log('refresh')
this.imgs = ''
this.scale = 1
this.crop = false
this.rotate = 0
this.w = 0
this.h = 0
this.trueWidth = 0
this.trueHeight = 0
this.clearCrop()
},
// 向左邊旋轉(zhuǎn)
rotateLeft () {
this.rotate = this.rotate <= -3 ? 0 : this.rotate - 1
},
// 向右邊旋轉(zhuǎn)
rotateRight () {
this.rotate = this.rotate >= 3 ? 0 : this.rotate + 1
},
// 清除旋轉(zhuǎn)
rotateClear () {
this.rotate = 0
}
},
mounted () {
let that = this
this.showPreview();
this.checkedImg();
// 兼容blob
if (!HTMLCanvasElement.prototype.toBlob) {
Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
value: function (callback, type, quality) {
var binStr = atob( this.toDataURL(type, quality).split(',')[1] ),
len = binStr.length,
arr = new Uint8Array(len)
for (var i=0; i<len; i++ ) {
arr[i] = binStr.charCodeAt(i)
}
callback( new Blob( [arr], {type: that.type || 'image/png'} ) )
}
})
}
}
}
</script>
<style scoped>
.vue-cropper {
position: relative;
width: 100%;
height: 100%;
box-sizing: border-box;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
direction: ltr;
}
.cropper-box,
.cropper-box-canvas,
.cropper-drag-box,
.cropper-crop-box,
.cropper-face {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
user-select: none;
}
.cropper-box-canvas img {
position: relative;
max-width: none;
max-height: none;
transform: none;
user-select: none;
}
.cropper-box {
overflow: hidden;
}
.cropper-move {
cursor: move;
}
.cropper-crop {
cursor: crosshair;
}
.cropper-modal {
background: rgba(0, 0, 0, 0.5);
}
.cropper-view-box {
display: block;
width: 100%;
height: 100%;
overflow: hidden;
outline: 1px solid #ccc;
outline-color: rgba(51, 153, 255, 0.75);
user-select: none;
}
.cropper-view-box img {
max-width: none;
max-height: none;
user-select: none;
}
.cropper-face {
top: 0;
left: 0;
background-color: #fff;
opacity: 0.1;
}
.crop-info {
position: absolute;
left: 0;
min-width: 65px;
font-size: 12px;
line-height: 20px;
color: white;
text-align: center;
background-color: rgba(0, 0, 0, 0.8);
}
.crop-line {
position: absolute;
display: block;
width: 100%;
height: 100%;
opacity: 0.1;
}
.line-w {
top: -3px;
left: 0;
height: 5px;
cursor: n-resize;
}
.line-a {
top: 0;
left: -3px;
width: 5px;
cursor: w-resize;
}
.line-s {
bottom: -3px;
left: 0;
height: 5px;
cursor: s-resize;
}
.line-d {
top: 0;
right: -3px;
width: 5px;
cursor: e-resize;
}
.crop-point {
position: absolute;
width: 8px;
height: 8px;
background-color: #39f;
border-radius: 100%;
opacity: 0.75;
}
.point1 {
top: -4px;
left: -4px;
cursor: nw-resize;
}
.point2 {
top: -5px;
left: 50%;
margin-left: -3px;
cursor: n-resize;
}
.point3 {
top: -4px;
right: -4px;
cursor: ne-resize;
}
.point4 {
top: 50%;
left: -4px;
margin-top: -3px;
cursor: w-resize;
}
.point5 {
top: 50%;
right: -4px;
margin-top: -3px;
cursor: w-resize;
}
.point6 {
bottom: -5px;
left: -4px;
cursor: sw-resize;
}
.point7 {
bottom: -5px;
left: 50%;
margin-left: -3px;
cursor: s-resize;
}
.point8 {
right: -4px;
bottom: -5px;
cursor: nw-resize;
}
@media screen and (max-width: 500px) {
.crop-point {
position: absolute;
width: 20px;
height: 20px;
background-color: #39f;
border-radius: 100%;
opacity: 0.45;
}
.point1 {
top: -10px;
left: -10px;
}
.point2,
.point4,
.point5,
.point7 {
display: none;
}
.point3 {
top: -10px;
right: -10px;
}
.point4 {
top: 0;
left: 0;
}
.point6 {
bottom: -10px;
left: -10px;
}
.point8 {
right: -10px;
bottom: -10px;
}
}
</style>
在需要的文件中使用 template
<div class="header-box">
<img :src="firePic" alt="" class="header-img" v-show="firePic">
<input
accept="image/jpeg, image/png, image/jpg"
id="firePic"
style="display:none"
type="file"
@change="onFileChange('firePic',$event)"
/>
<span @click="imgClick('firePic')" class="button-header">
替換頭像
</span>
</div>
// 用 dialog 包含 組件
<el-dialog :visible.sync="cropperModel" width="330px" :close-on-click-modal="false" :show-close="false">
<cc-cur
:img="option.img"
:outputSize="option.size"
:outputType="option.outputType"
:info="true"
:full="option.full"
:canMove="option.canMove"
:canMoveBox="option.canMoveBox"
:original="option.original"
:autoCrop="option.autoCrop"
:autoCropWidth="option.autoCropWidth"
:autoCropHeight="option.autoCropHeight"
:fixedBox="option.fixedBox"
:fixed="option.fixed"
:fixedNumber="option.fixedNumber"
style="width: 300px;height: 300px;"
ref="cropper"
/>
<div class="footer-btn">
<el-button @click="changeImg('add')">
<i class="el-icon-plus fnt"></i>
</el-button>
<el-button @click="changeImg('dele')">
<i class="el-icon-minus fnt"></i>
</el-button>
<el-button @click="cropperModel = false">
取 消
</el-button>
<el-button type="primary" @click="getImg">
確 定
</el-button>
</div>
</el-dialog>
script
import Curppor from "@/views/common/components/Cropper.vue";
components:{
"cc-cur": Curppor,
}
// ts 寫法 可用js替換
private option: any = {
img: "", // 裁剪圖片的地址 (默認(rèn):空)
outputSize: 1, // 裁剪生成圖片的質(zhì)量 (默認(rèn):1)
full: false, // 是否輸出原圖比例的截圖 選true生成的圖片會(huì)非常大 (默認(rèn):false)
outputType: "png", // 裁剪生成圖片的格式 (默認(rèn):jpg)
canMove: false, // 上傳圖片是否可以移動(dòng) (默認(rèn):true)
original: false, // 上傳圖片按照原始比例渲染 (默認(rèn):false)
canMoveBox: true, // 截圖框能否拖動(dòng) (默認(rèn):true)
autoCrop: true, // 是否默認(rèn)生成截圖框 (默認(rèn):false)
autoCropWidth: 120, // 默認(rèn)生成截圖框?qū)挾? (默認(rèn):80%)
autoCropHeight: 120, // 默認(rèn)生成截圖框高度 (默認(rèn):80%)
fixedBox: true, // 固定截圖框大小 不允許改變 (默認(rèn):false)
fixed: true, // 是否開(kāi)啟截圖框?qū)捀吖潭ū壤? (默認(rèn):true)
fixedNumber: [1, 1], // 截圖框比例 (默認(rèn):[1:1])
enlarge: 1,
};
private clickNum: number = 9; // 可點(diǎn)擊縮放次數(shù)
private btrLen: number = 0; // 字節(jié)長(zhǎng)度
// 對(duì)應(yīng)方法
private imgClick(val: string) { // 點(diǎn)擊替換頭像按鈕
const input: any = document.getElementById(val);
input.click();
}
private onFileChange(type: string, e: any) {
const fileInput = e.target;
const file = fileInput.files[0];
if (fileInput.files.length === 0) {
return;
}
this.cropperModel = true;
// 將文件流文件 轉(zhuǎn)成base64 放入裁剪框中
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
this.option.img = reader.result;
};
}
private changeImg(type: string) {
const cropper: any = this.$refs.cropper;
if (type === "add") {
if (this.clickNum < 19) {
cropper.changeScale(1);
this.clickNum++;
} else {
this.$message.success("已放大到最大倍數(shù)");
}
} else {
if (this.clickNum > 0) {
cropper.changeScale(-1);
this.clickNum--;
} else {
this.$message.success("已縮小到最小倍數(shù)");
}
}
}
private dataURLtoFile(dataurl: string) {// base64轉(zhuǎn)換為文件
const arr: any = dataurl.split(",");
const mime = arr[0].match(/:(.*?);/)[1];
const bstr: string = atob(arr[1]);
this.btrLen = bstr.length;
const u8arr = new Uint8Array(this.btrLen);
while (this.btrLen --) {
u8arr[this.btrLen ] = bstr.charCodeAt(this.btrLen);
}
return new File([u8arr], "shoplog.png", {type: mime});
}
private getImg() {
const cropper: any = this.$refs.cropper;
cropper.getCropData((data: any) => { // 確定后將base64文件轉(zhuǎn)為文件流 傳給后端拿到圖片地址
this.oss.uploadPic(this.dataURLtoFile(data)).then((url: string) => { //結(jié)合自身修改
this.firePic = url;
this.cropperModel = false;
this.$message.success("修改店鋪頭像成功");
});
});
}
效果預(yù)覽

1

點(diǎn)擊替換頭像

修改后上傳