公司有需求要做一个类似于QQ的头像裁剪。经过网上搜索发现没有资源,只好自己手动实现了一个。直接上代码
<template>
<div>
<div class="clip-page">
<div class="pic_container" style="width: 160px; height: 160px; position: relative; overflow: hidden">
<div style="position: absolute; top: 0; left: 0; width: 160px; height: 160px">
<div class="dragBox" style="position: absolute; top: 0; left: 0; z-index: 999; cursor: pointer">
<!-- 底图 -->
<img class="pic-warp" :src="img" alt="" :style="{ 'transform':`scale(${scale})`}" draggable="false" />
</div>
</div>
<!-- 遮罩 -->
<div class="mask">
<!-- 圆形镂空 -->
<div class="circle_div">
</div> -->
<div class="lightBox" style="width: 160px; height: 160px; position: absolute; left: 0; top: -35px">
<img class="pic" :src="img" :style="{ 'transform':`scale(${scale})`}" draggable="false"
style="position: absolute; left: 0; top: 0; bottom: 0; margin: auto" />
</div>
</div>
</div>
</div>
<!-- 用来裁剪的图 -->
<img class="clipImg" :src="img" alt="" :style="{ 'transform':`scale(${scale})`}" style="display: none" />
<a-space style="margin-top: 5px">
<icon-minus @click="minus" />
<a-slider v-model="slider" @change="fixSize" :style="{ width: '120px' }" :disabled="noScale" />
<icon-plus @click="plus" />
</a-space>
<canvas id="canvas" style="display: none"></canvas>
</div>
<!-- 用来获取宽高的图 -->
<img :src="img" alt="" class="imgDom" style="display: none" />
</div>
</template>
<script lang="ts">
import { clearImageEdgeBlank } from '@/utils/clearImgBorder'
export default {
props: ['img'],
data() {
return {
noScale: false, //不能放大
slider: 0,
scale: 1,
dragX: 0,
dragY: 0,
minScale: 1,
WTH: 1,
step: 0.05,
W:0,
H:0
}
},
methods: {
fixSize(value: any) {
if(value === 100){
this.scale = 2
return
}
let ratio = (value / 100)
let diff = 2 - this.minScale
let temp = (ratio*diff)+this.minScale
if(temp<this.scale){
this.imgOnCenter()
}
this.scale = temp
// console.log(this.scale)
},
//缩小
minus() {
if (this.noScale) return
this.imgOnCenter()
if (this.slider > 5) {
this.slider -= 5
// let oper = this.slider / 100 + 1
// if (oper < this.minScale || oper == this.minScale) {
// return
// }
// this.scale = oper
let slide = this.scale
slide -= this.step
if (slide < 1) {
return
}
this.scale -= this.step
} else {
this.slider = 0
this.scale = this.minScale
}
},
//放大
plus() {
if (this.noScale) return
if (this.slider < 95) {
this.slider += 5
// this.scale = oper
let slide = this.scale
slide += this.step
if (slide > 2) {
return
}
this.scale += this.step
} else {
this.slider = 100
this.scale = this.minScale > 2 ? this.minScale : 2
}
},
async clip() {
const canvas: any = document.querySelector('#canvas')
const ctx = canvas.getContext('2d')
const img = document.querySelector('.clipImg')
if(this.WTH>=1){
canvas.width = 160 * this.WTH * (this.scale / this.minScale) * 10
canvas.height = 160 * (this.scale / this.minScale) * 10
ctx.rect(((this.scale / this.minScale) * 160 * this.WTH / 2 - 80 - this.dragX) * 10, ((this.scale / this.minScale)*160/2 -45 - this.dragY) * 10 , 1600, 900);
ctx.closePath()
ctx.clip();
ctx.drawImage(img, 0, 0, 160 * this.WTH * (this.scale / this.minScale) * 10, 160 * (this.scale / this.minScale) * 10);
}else if(this.WTH<1){
canvas.width = 160 * (this.scale / this.minScale) * 10
canvas.height = 160 / this.WTH * (this.scale / this.minScale) * 10
ctx.rect(((this.scale / this.minScale)*160/2 - 80 - this.dragX)*10, (((this.scale / this.minScale)*(160 /this.WTH )/2) -45 - this.dragY)*10 , 1600, 900);
ctx.closePath()
ctx.clip();
ctx.drawImage(img, 0, 0, 160 * (this.scale / this.minScale) * 10, 160 / this.WTH * (this.scale / this.minScale) * 10);
}
const base64 = await clearImageEdgeBlank(canvas.toDataURL('image/png'))
console.log(base64)
return base64
},
onDrag(e: any) {
let box: any = document.querySelector('.dragBox')
let lightBox: any = document.querySelector('.lightBox')
let startX = e.clientX
let startY = e.clientY
let innerX = parseInt(box.style.left)
let innerY = parseInt(box.style.top)
console.log(this.WTH)
document.onmousemove = (event) => {
let x = innerX + event.clientX - startX
let y = innerY + event.clientY - startY
box.style.left = x + 'px'
box.style.top = y + 'px'
lightBox.style.left = x + 'px'
lightBox.style.top = y - 35 + 'px'
this.dragX = x
this.dragY = y
//lightBox.style.backgroundPosition = x + 'px ' + y + 'px'
// lightBox.style.backgroundPosition.y = y + 'px'
let leftBorder = (160 - this.scale * 160) / 2
let rightBorder = (this.scale * 160 - 160) / 2
let topBorder = (160 - this.scale * 160) / 2
let bottomBorder = (this.scale * 160 - 160) / 2
if (this.WTH > 1) {
topBorder = 0
bottomBorder = 0
if (this.scale > this.minScale) {
let plus = this.scale / this.minScale
topBorder = (160 - plus * 160) / 2
bottomBorder = (plus * 160 - 160) / 2
}
}
if (this.WTH < 1) {
leftBorder = 0
rightBorder = 0
if (this.scale > this.minScale) {
let plus = this.scale / this.minScale
leftBorder = (160 - plus * 160) / 2
rightBorder = (plus * 160 - 160) / 2
}
// box.style.top = 160/this.WTH - 160 + 35 + 'px'
// lightBox.style.top = 160/this.WTH - 160 + 'px'
// this.dragY = topBorder - 35
// return
}
if (x < leftBorder) {
box.style.left = leftBorder + 'px'
lightBox.style.left = leftBorder + 'px'
this.dragX = leftBorder
}
if (x > rightBorder) {
box.style.left = rightBorder + 'px'
lightBox.style.left = rightBorder + 'px'
this.dragX = rightBorder
}
if (this.scale >= this.minScale) {
if (y < topBorder - 35) {
box.style.top = topBorder - 35 + 'px'
// lightBox.style.backgroundPosition = x + 'px ' + topBorder + 'px'
lightBox.style.top = topBorder - 35 - 35 + 'px'
this.dragY = topBorder - 35
}
if (y > bottomBorder + 35) {
box.style.top = bottomBorder + 35 + 'px'
//lightBox.style.backgroundPosition = x + 'px ' + bottomBorder + 'px'
lightBox.style.top = bottomBorder + 35 - 35 + 'px'
this.dragY = bottomBorder + 35
}
} else {
if (y < -((160/this.WTH)/2 - 45)) {
// box.style.top = -35 + 'px'
// lightBox.style.top = -35 - 35 + 'px'
// this.dragY = -35
box.style.top = -((160/this.WTH)/2 - 45) + 'px'
lightBox.style.top = -((160/this.WTH)/2 - 45) -35 + 'px'
this.dragY = -((160/this.WTH)/2 - 45)
console.log(y)
}
if (y > ((160/this.WTH)/2 - 45)) {
box.style.top = ((160/this.WTH)/2 - 45)+ 'px'
//lightBox.style.backgroundPosition = x + 'px ' + bottomBorder + 'px'
lightBox.style.top = ((160/this.WTH)/2 - 45) - 35 + 'px'
this.dragY = ((160/this.WTH)/2 - 45)
}
}
}
document.onmouseup = (e) => {
// console.log(e.offsetX)
document.onmousemove = null
document.onmouseup = null
}
},
imgOnCenter() {
let box: any = document.querySelector('.dragBox')
let lightBox: any = document.querySelector('.lightBox')
box.style.left = 0
box.style.top = 0
lightBox.style.left = 0
lightBox.style.top = -35 + 'px'
},
},
mounted() {
let container: any = document.querySelector('.pic_container')
container.addEventListener('wheel', (e: any) => {
//console.log(e.deltaY)
if (this.noScale) return
if (e.deltaY > 0) {
if (this.slider < 95) {
this.slider += 5
let slide = this.scale
slide += this.step
if (slide > 2) {
return
}
this.scale += this.step
} else {
this.slider = 100
this.scale = this.minScale > 2 ? this.minScale : 2
}
} else {
this.imgOnCenter()
if (this.slider > 5) {
this.slider -= 5
let slide = this.scale
slide -= this.step
if (slide < 1) {
return
}
this.scale -= this.step
} else {
this.slider = 0
this.scale = this.minScale
}
}
})
//拖拽截图框
let dragBox = document.querySelector('.circle_div')
dragBox?.addEventListener('mousedown', this.onDrag, false)
const imgDom: any = document.querySelector('.imgDom')
this.$nextTick(() => { })
setTimeout(() => {
const W = this.W = imgDom.width
const H = this.H = imgDom.height
this.WTH = (W / H).toFixed(2) as any
console.log(imgDom.width,'___W')
console.log(imgDom.height,'___h')
if(this.WTH>1){
if(W<160){
let s = (160/H).toFixed(2) as any
this.scale = s
this.minScale = s
}else{
this.scale = W/H
this.minScale = W/H
}
}else if(this.WTH<1){
if(H<160){
let s = (160/W).toFixed(2) as any
this.scale = s
this.minScale = s
}else{
this.scale = H/W
this.minScale = H/W
}
}
this.step = (2 - this.minScale) / 20
// console.log(this.minScale)
if (this.minScale > 2||this.minScale == 2) {
this.noScale = true
}
}, 100)
},
beforeDestroy() {
let container: any = document.querySelector('.pic_container')
container.removeEventListener('wheel', false)
},
}
</script>
<style scoped>
body {
-webkit-tap-highlight-color: transparent;
-moz-user-select: none;
/*火狐*/
-webkit-user-select: none;
/*webkit浏览器*/
-ms-user-select: none;
/*IE10*/
-khtml-user-select: none;
/*早期浏览器*/
user-select: none;
}
.clip-page {
margin: 0 auto;
width: 160px;
}
.mask {
width: 160px;
height: 160px;
z-index: 999;
background-color: #333;
opacity: 0.5;
position: absolute;
top: 0;
left: 0;
}
.dragBox {
width: 160px;
height: 160px;
display: flex;
align-items: center;
justify-content: center;
}
.pic {
position: absolute;
/* width: 160px;
height: 160px; */
max-width: 160px;
max-height: 160px;
/* min-height: 160px; */
/* background-repeat: no-repeat;
background-size: contain; */
/* top: -35px; */
z-index: 55;
/* background-attachment:fixed; */
left:0;
top:0;
right: 0;
bottom: 0;
margin: auto;
}
.pic-warp {
position: absolute;
/* width: 160px;
height: 160px; */
/* background-repeat: no-repeat;
background-size: contain; */
max-width: 160px;
max-height: 160px;
/* min-height: 160px; */
/* width: inherit; */
}
.circle_div {
width: 160px;
height: 90px;
overflow: hidden;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
z-index: 9999;
border-top: 1px solid white;
border-bottom: 1px solid white;
/* background-position: 0 60px; */
/* border: 1px solid white;
border-radius: 50%; */
cursor: pointer;
}
</style>
//clearImgBorder.js
//bitmap 包含关于img的各种参数信息
export function clearImageEdgeBlank(url:any, padding = 0) {
return new Promise((resolve, reject) => {
// create canvas
const canvas = document.createElement("canvas");
const ctx:any = canvas.getContext("2d");
// create image
const image = new Image();
image.onload = draw;
image.src = url;
image.crossOrigin = "Anonymous";
function draw() {
canvas.width = image.width;
canvas.height = image.height;
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const { data, width, height } = imageData;
// 裁剪需要的起点和终点,初始值为画布左上和右下点互换设置成极限值。
let startX = width,
startY = height,
endX = 0,
endY = 0;
/*
col为列,row为行,两层循环构造每一个网格,
便利所有网格的像素,如果有色彩则设置裁剪的起点和终点
*/
for (let col = 0; col < width; col++) {
for (let row = 0; row < height; row++) {
// 网格索引
const pxStartIndex = (row * width + col) * 4;
// 网格的实际像素RGBA
const pxData = {
r: data[pxStartIndex],
g: data[pxStartIndex + 1],
b: data[pxStartIndex + 2],
a: data[pxStartIndex + 3]
};
// 存在色彩:不透明
const colorExist = pxData.a !== 0;
/*
如果当前像素点有色彩
startX坐标取当前col和startX的最小值
endX坐标取当前col和endX的最大值
startY坐标取当前row和startY的最小值
endY坐标取当前row和endY的最大值
*/
if (colorExist) {
startX = Math.min(col, startX);
endX = Math.max(col, startX);
startY = Math.min(row, startY);
endY = Math.max(row, endY);
}
}
}
// 右下坐标需要扩展1px,才能完整的截取到图像
endX += 1;
endY += 1;
// 加上padding
startX -= padding;
startY -= padding;
endX += padding;
endY += padding;
// 根据计算的起点终点进行裁剪
const cropCanvas = document.createElement("canvas");
const cropCtx:any = cropCanvas.getContext("2d");
cropCanvas.width = endX - startX;
cropCanvas.height = endY - startY;
cropCtx.drawImage(
image,
startX,
startY,
cropCanvas.width,
cropCanvas.height,
0,
0,
cropCanvas.width,
cropCanvas.height
);
// rosolve裁剪后的图像字符串
resolve(cropCanvas.toDataURL());
}
});
}