ddfwxt/src/components/uploader/cropper.vue

597 lines
17 KiB
Vue

<template>
<div>
<div v-if="news" class="font-box">
<span />
<span style="margin-right: 70px">热门预览</span>
<span style="margin-right: 6px">非热门预览</span>
</div>
<div class="cropper-content">
<!-- 剪裁框 -->
<div class="cropper">
<vueCropper
ref="cropper"
:img="option.img"
:output-size="option.size"
:output-type="option.outputType"
:info="true"
:full="option.full"
:can-move="option.canMove"
:can-move-box="option.canMoveBox"
:original="option.original"
:auto-crop="option.autoCrop"
:auto-crop-width="option.autoCropWidth"
:auto-crop-height="option.autoCropHeight"
:fixed-box="option.fixedBox"
:fixed="option.fixed"
:fixed-number="fixedNumber"
@realTime="realTime"
/>
</div>
<!-- 预览框 -->
<div class="show-preview" :style="{'width': '300px', 'height': '300px', 'overflow': 'hidden', 'margin': '0 25px', 'display':'flex', 'align-items' : 'center'}" @mousedown="previewContainerDown" @mousemove="previewContainerMove" @mouseup="previewContainerUp" @mouseleave="previewContainerLeave">
<div :style="previews.div" style="position: relative">
<div :ref="previewId" :style="previews.div" class="preview">
<img :src="previews.url" :style="previews.img">
<img v-if="water.url" :src="water.url" :style="water.img" class="water">
</div>
<div v-show="water.url" :style="previews.div" class="water-cropper">
<!-- @mousemove.self="cropMove" -->
<div class="crop-box" :style="water.img" @mousedown.self="cropDown">
<span class="crop-line line-w" />
<span class="crop-line line-a" />
<span class="crop-line line-s" />
<span class="crop-line line-d" />
<span class="crop-point point1" @mousedown.stop="pointDown($event, 'lt')" />
<span class="crop-point point2" @mousedown.stop="pointDown($event, 'rt')" />
<span class="crop-point point3" @mousedown.stop="pointDown($event, 'lb')" />
<span class="crop-point point4" @mousedown.stop="pointDown($event, 'rb')" />
</div>
</div>
</div>
</div>
<!-- 新闻预览框 -->
<div v-if="news" class="news-preview" :style="{'width': '200px', 'height': '300px', 'overflow': 'hidden', 'margin': '0', 'display':'flex', 'align-items' : 'center'}">
<!-- <div class="news-box"> -->
<div :ref="previewId" :style="previews.div" class="preview">
<img :src="previews.url" :style="previews.img" style="margin-left: -75px">
<img v-if="water.url" :src="water.url" :style="water.img" style="margin-left: -75px" class="water">
</div>
<!-- </div> -->
</div>
</div>
<div class="footer-btn">
<!-- 缩放旋转按钮 -->
<div class="scope-btn">
<el-button type="primary" icon="el-icon-zoom-in" @click="changeScale(1)" />
<el-button type="primary" icon="el-icon-zoom-out" @click="changeScale(-1)" />
<el-button type="primary" @click="rotateLeft">逆时针旋转</el-button>
<el-button type="primary" @click="rotateRight">顺时针旋转</el-button>
</div>
<!-- 确认上传按钮 -->
<div class="upload-btn">
<el-button type="primary" @click="uploadImg('blob')">上传</el-button>
<el-button type="primary" @click="selectWater">选择水印</el-button>
<el-button type="primary" @click="water.url = null">取消水印</el-button>
<!-- <input type="file" name="" id="" accept=""> -->
</div>
</div>
</div>
</template>
<script>
// import VueCropper from 'vue-cropper'
// import html2canvas from 'html2canvas'
// import { createElement } from 'dropzone'
export default {
props: ['imgFile', 'fixedNumber', 'news'],
data() {
name:'Cropper'
return {
previewId: 'waterProview' + Date.parse(new Date()),
previews: {}, // 预览数据
option: {
img: '', // 裁剪图片的地址 (默认:空)
size: 1, // 裁剪生成图片的质量 (默认:1)
full: true, // 是否输出原图比例的截图 选true生成的图片会非常大 (默认:false)
outputType: 'png', // 裁剪生成图片的格式 (默认:jpg)
canMove: true, // 上传图片是否可以移动 (默认:true)
original: false, // 上传图片按照原始比例渲染 (默认:false)
canMoveBox: true, // 截图框能否拖动 (默认:true)
autoCrop: true, // 是否默认生成截图框 (默认:false)
autoCropWidth: 400, // 默认生成截图框宽度 (默认:80%)
autoCropHeight: 400, // 默认生成截图框高度 (默认:80%)
fixedBox: false, // 固定截图框大小 不允许改变 (默认:false)
fixed: true, // 是否开启截图框宽高固定比例 (默认:true)
fixedNumber: [1.5, 1] // 截图框比例 (默认:[1:1])
},
downImg: '#',
// 水印
waterUrl: null,
water: {
url: null,
img: {
width: '100px',
height: '100px',
left: '0px',
top: '0px'
}
},
waterBox: {
left: 0,
top: 0,
width: 100,
height: 100,
scale: 1,
prop: 1
},
waterInfo: {
waterX: 0,
waterY: 0,
x: 0,
y: 0
},
waterMove: false,
pointInfo: {
pointX: 0,
pointY: 0,
x: 0,
y: 0,
width: 0,
height: 0
},
pointType: null,
pointIsMove: false,
successimg: null
}
},
watch: {
waterBox: {
handler(newValue, oldValue) {
// console.log(newValue)
this.water.img.left = newValue.left + 'px'
this.water.img.top = newValue.top + 'px'
this.water.img.width = newValue.width + 'px'
this.water.img.height = newValue.height + 'px'
},
deep: true
}
},
methods: {
changeScale(num) {
// 图片缩放
num = num || 1
this.$refs.cropper.changeScale(num)
},
rotateLeft() {
// 向左旋转
console.log(this.$refs.cropper)
this.$refs.cropper.rotateLeft()
},
rotateRight() {
// 向右旋转
console.log(this.$refs.cropper)
this.$refs.cropper.rotateRight()
},
Update() {
// this.file = this.imgFile
this.option.img = this.imgFile.url
},
realTime(data) {
// 实时预览
this.previews = data
},
uploadImg(type) {
// 将剪裁好的图片回传给父组件
event.preventDefault()
const that = this
this.$refs.cropper.getCropData(async data => {
// that.$emit('upload', data)
let cropInfo, waterInfo
if (!this.water.url) {
if (type === 'blob') {
this.$refs.cropper.getCropBlob(data => {
that.$emit('upload', data)
})
} else {
this.$refs.cropper.getCropData(data => {
that.$emit('upload', data)
})
}
}
await (() => {
const cropImg = new Image()
cropImg.src = data
cropImg.onload = () => {
cropInfo = {
img: cropImg,
width: cropImg.width,
height: cropImg.height,
x: 0,
y: 0
}
const waterImg = new Image()
waterImg.crossOrigin = ''
waterImg.src = this.water.url
waterImg.onload = () => {
waterInfo = {
img: waterImg,
width: this.waterBox.width,
height: this.waterBox.height,
x: this.waterBox.left,
y: this.waterBox.top
}
// console.log(cropInfo)
// console.log(waterInfo)
const canvas = document.createElement('canvas')
canvas.width = cropInfo.width
canvas.height = cropInfo.height
const ctx = canvas.getContext('2d')
ctx.drawImage(cropInfo.img, cropInfo.x, cropInfo.y, cropInfo.width, cropInfo.height)
// console.log(this.previews.div)
const prop = cropInfo.width / parseFloat(this.previews.div.width)
ctx.drawImage(waterInfo.img, waterInfo.x * prop, waterInfo.y * prop, waterInfo.width * prop, waterInfo.height * prop)
// console.log(this.previews)
if (type === 'blob') {
canvas.toBlob((blob) => {
that.$emit('upload', blob)
})
} else {
that.$emit('upload', canvas.toDataURL('image/png'))
}
/*
// 创建 canvas 标签
const canvas = document.createElement('canvas')
// 获取要生成图片的 DOM 元素
const canvasDom = this.$refs[this.previewId]
// 获取指定的宽高
const width = parseInt(window.getComputedStyle(canvasDom).width)
const height = parseInt(window.getComputedStyle(canvasDom).height)
// 宽高扩大 2 倍 处理图片模糊
canvas.width = width * 2
canvas.height = height * 2
canvas.style.width = width + 'px'
canvas.style.height = height + 'px'
const context = canvas.getContext('2d')
context.scale(2, 2)
const options = {
backgroundColor: null,
canvas: canvas,
useCORS: true
}
console.log(123)
html2canvas(canvasDom, options).then(canvas => {
// console.log(canvas.toDataURL('image/png'))
// that.successimg = canvas.toDataURL('image/png')
// 生成图片地址
if (type === 'blob') {
console.log('blob')
canvas.toBlob((blob) => {
that.$emit('upload', blob)
})
} else {
that.$emit('upload', canvas.toDataURL('image/png'))
}
})
*/
}
}
})()
})
// return
// 原版
// if (type === 'blob') {
// this.$refs.cropper.getCropBlob(data => {
// that.$emit('upload', data)
// })
// } else {
// this.$refs.cropper.getCropData(data => {
// that.$emit('upload', data)
// })
// }
},
// 水印框的操作
selectWater() { // 选择水印
const input = document.createElement('input')
input.type = 'file'
input.accept = 'image/*'
input.addEventListener('change', (e) => {
const file = e.path[0].files[0]
const imgURL = window.URL.createObjectURL(file)
const image = new Image()
image.src = imgURL
image.onload = () => {
this.water.url = imgURL
const { width, height } = image
const prop = width / height
this.waterBox.prop = prop
this.waterBox.height = 100
this.waterBox.width = 100 * prop
}
})
setTimeout(() => {
input.click()
}, 100)
},
previewContainerDown(e) {
},
previewContainerMove(e) {
if (this.waterMove) {
this.cropMove(e)
} else if (this.pointIsMove) {
this.pointMove(e)
}
},
previewContainerUp() {
this.waterMove = false
this.pointIsMove = false
},
previewContainerLeave() {
this.waterMove = false
this.pointIsMove = false
},
cropDown(e) {
const { clientX, clientY } = e
this.waterInfo.x = this.waterBox.left
this.waterInfo.y = this.waterBox.top
this.waterInfo.waterX = clientX
this.waterInfo.waterY = clientY
this.waterMove = true
},
cropMove(e) {
if (this.waterMove) {
const { clientX, clientY } = e
this.waterBox.left = (clientX - this.waterInfo.waterX) + this.waterInfo.x
this.waterBox.top = (clientY - this.waterInfo.waterY) + this.waterInfo.y
}
},
// 水印框角的操作
pointDown(e, type) {
const { clientX, clientY } = e
this.pointInfo.x = this.waterBox.left
this.pointInfo.y = this.waterBox.top
this.pointInfo.width = this.waterBox.width
this.pointInfo.height = this.waterBox.height
this.pointInfo.pointX = clientX
this.pointInfo.pointY = clientY
this.pointType = type
this.pointIsMove = true
},
pointMove(e) {
if (this.pointIsMove) {
const { clientX, clientY } = e
const left = (clientX - this.pointInfo.pointX) + this.pointInfo.x
const top = (clientY - this.pointInfo.pointY) + this.pointInfo.y
if (this.pointType === 'lt') {
this.waterBox.left = left
this.waterBox.top = top
const height = this.pointInfo.height - (clientY - this.pointInfo.pointY)
this.waterBox.height = height
this.waterBox.width = height * this.waterBox.prop
// this.waterBox.width = this.pointInfo.width - (clientX - this.pointInfo.pointX)
// this.waterBox.height = this.pointInfo.height - (clientY - this.pointInfo.pointY)
} else if (this.pointType === 'rt') {
this.waterBox.top = top
const height = this.pointInfo.height - (clientY - this.pointInfo.pointY)
this.waterBox.height = height
this.waterBox.width = height * this.waterBox.prop
// this.waterBox.width = this.pointInfo.width + (clientX - this.pointInfo.pointX)
// this.waterBox.height = this.pointInfo.height - (clientY - this.pointInfo.pointY)
} else if (this.pointType === 'lb') {
this.waterBox.left = left
const height = this.pointInfo.height + (clientY - this.pointInfo.pointY)
this.waterBox.height = height
this.waterBox.width = height * this.waterBox.prop
// this.waterBox.width = this.pointInfo.width - (clientX - this.pointInfo.pointX)
// this.waterBox.height = this.pointInfo.height + (clientY - this.pointInfo.pointY)
} else if (this.pointType === 'rb') {
const height = this.pointInfo.height + (clientY - this.pointInfo.pointY)
this.waterBox.height = height
this.waterBox.width = height * this.waterBox.prop
// this.waterBox.width = this.pointInfo.width + (clientX - this.pointInfo.pointX)
// this.waterBox.height = this.pointInfo.height + (clientY - this.pointInfo.pointY)
}
}
}
}
// components: { VueCropper }
}
</script>
<style>
.cropper-content {
display: flex;
display: -webkit-flex;
justify-content: flex-end;
-webkit-justify-content: flex-end;
}
.cropper-content .cropper {
width: 350px;
height: 300px;
}
.cropper-content .show-preview {
flex: 1;
-webkit-flex: 1;
display: flex;
display: -webkit-flex;
justify-content: center;
-webkit-justify-content: center;
overflow: hidden;
border: 1px solid #cccccc;
background: #cccccc;
margin-left: 40px;
}
.preview {
overflow: hidden;
border: 1px solid #cccccc;
background: #cccccc;
}
.footer-btn {
margin-top: 30px;
display: flex;
display: -webkit-flex;
justify-content: flex-end;
-webkit-justify-content: flex-end;
}
.footer-btn .scope-btn {
width: 380px;
display: flex;
display: -webkit-flex;
justify-content: space-between;
-webkit-justify-content: space-between;
}
.footer-btn .upload-btn {
flex: 1;
-webkit-flex: 1;
display: flex;
display: -webkit-flex;
justify-content: center;
-webkit-justify-content: center;
}
.footer-btn .btn {
outline: none;
display: inline-block;
line-height: 1;
white-space: nowrap;
cursor: pointer;
-webkit-appearance: none;
text-align: center;
-webkit-box-sizing: border-box;
box-sizing: border-box;
outline: 0;
margin: 0;
-webkit-transition: 0.1s;
transition: 0.1s;
font-weight: 500;
padding: 8px 15px;
font-size: 12px;
border-radius: 3px;
color: #fff;
background-color: #67c23a;
border-color: #67c23a;
}
/* 水印 */
.cropper-content .show-preview{
position: relative;
user-select: none;
}
.water-cropper{
position: absolute;
top: 0;
right: 0;
/* display: flex;
align-items: center; */
}
.water{
position: absolute;
top: 0;
right: 0;
}
.preview {
position: relative;
}
.crop-box{
/* position: relative; */
position: absolute;
width: 50px;
height: 50px;
cursor: move;
}
.crop-line, .crop-point{
background: #39f;
position: absolute;
user-select: none
}
.crop-line{
opacity: 0.5;
}
.line-w, .line-s{
height: 1px;
width: 100%;
}
.line-a, .line-d{
width: 1px;
height: 100%;
}
.line-w{
top: 0;
left: 0;
}
.line-s{
top: 100%;
left: 0;
}
.line-a{
top: 0;
left: 0;
}
.line-d{
top: 0;
left: 100%;
}
.crop-point{
width: 8px;
height: 8px;
opacity: 0.75;
background-color: #39f;
border-radius: 100%;
}
.water-cropper .point1{
top: -4px;
left: -4px;
cursor: nw-resize;
}
.water-cropper .point2{
top: -4px;
right: -4px;
cursor: ne-resize;
}
.water-cropper .point3{
left: -4px;
bottom: -4px;
cursor: sw-resize;
}
.water-cropper .point4{
right: -4px;
bottom: -4px;
cursor: se-resize;
}
/* #39f */
.news-preview{
border: 1px solid #cccccc;
background: #cccccc;
}
/* .show-preview.news{
display: flex;
align-items: center;
} */
/* .news .news-box{
overflow: hidden;
} */
.font-box{
display: flex;
justify-content: space-around;
padding-bottom: 10px;
}
</style>