移动端vue2项目图片裁剪(vue3没有尝试不清楚)(组件样式为移动端样式)
- vue2项目H5,做一个图片裁剪功能,我百度了一下找到了,但是用起来不方便,因此我自己封装了一下,用的时候只需要下载依赖,然后复制MyCropper组件的所以代码,然后就可以在你想用的地方使用,你不需要知道MyCropper组件的代码是怎么实现的,只需要知道:1.用的时候调用
this.$refs.croc.change(要裁剪的图片,参数可以是图片的url或者图片File)
会打开图片裁剪的一个东东;2:使用自定义方法returnCrop可以拿到裁剪后的图片数据。即可
依赖
"core-js": "^3.8.3",
"cropperjs": "^1.5.12",
"exif-js": "^2.3.0",
文档
1. api
名称 | 功能 | 参数 | 可选值 |
---|
this.$refs.croc.change(data) | 打开裁剪组件 | 必填,传递的参数 | url 地址 , File |
@returnCrop | 点击确认按钮裁剪,返回裁剪后的数据 | 接受返回的参数 | {baseb4Data:'base64类型',blobData:'blob类型'} |
2. props
名称 | 功能 | 默认值 | 可选值 |
---|
aspectRatioH | 裁剪框比例,如果有值则固定比例 | null | Number |
initialAspectRatioH | 裁剪框默认比例,aspectRatioH为null时才会生效 | 1 | Number |
guidesH | 是否显示裁剪框的虚线 | true | Boolean |
zoomableH | 是否允许放大图像 | true | Boolean |
zoomOnWheelH | 是否可以通过鼠标滚轮缩放图片,zoomableH为true时才会生效 | true | Boolean |
zoomOnTouchH | 是否可以通过拖动触摸来放大图像,zoomableH为true时才会生效 | true | Boolean |
cropBoxMovableH | 是否通过拖拽来移动剪裁框 | true | Boolean |
cropBoxResizableH | 是否通过拖动来调整剪裁框的大小 | true | Boolean |
viewModeH | 视图控制 | 2 | Number:0无限制,1限制裁剪框不能超出图片的范围,2限制裁剪框不能超出图片的范围且图片填充模式为cover最长边填充,3限制裁剪框不能超出图片的范围且图片填充模式为contain最短边填充 |
autoCropAreaH | 设置裁剪区域占图片的大小 值为 0-1 | 0.5 | Number |
movableH | 图片是否可以拖动 | true | Boolean |
3. 样式
名称 | 描述 | 默认值 | 可选值 |
---|
.container | 最外层容器 | - | - |
.cropper-container | 裁剪区域容器 | - | - |
#cancel_clip | 取消按钮 | - | - |
#clip_button | 确认按钮 | - | - |
使用
<template>
<div>
<button @click="onUploadFile(btn)">blob</button>
<button @click="vUrl">url</button>
<MyCropper ref="croc" @returnCrop="returnCrop"></MyCropper>
</div>
</template>
<script>
import MyCropper from './MyCropper.vue'
export default {
components: {
MyCropper
},
methods: {
btn(files) {
this.$refs.croc.change(files[0])
},
vUrl() {
this.$refs.croc.change('https://p9-passport.byteacctimg.com/img/mosaic-legacy/3791/5035712059~300x300.image')
},
returnCrop(data) {
console.log(data)
},
onUploadFile(callback, attribute = {}) {
let opiton = {
accept: attribute.accept ? attribute.accept : "image/*",
multiple: attribute.multiple ? attribute.multiple : false
}
const oInput = document.createElement('input')
oInput.type = 'file'
oInput.accept = opiton.accept
oInput.multiple = opiton.multiple
oInput.click()
oInput.addEventListener('change', onChange)
async function onChange() {
callback(this.files)
}
},
}
}
</script>
<style lang="less">
</style>
直接复制该组件
<template>
<div class="vue-box">
<img :src="imgUrl" alt="" id="img" v-show="false">
</div>
</template>
<script>
import Cropper from 'cropperjs'
import Exif from 'exif-js'
export default {
name: 'MyCropper',
props: {
widthRate: {
type: Number,
default: 1,
},
heightRate: {
type: Number,
default: 1
},
imgUrl: {
type: String,
default: ''
},
aspectRatioH: {
type: Number,
default: null
},
initialAspectRatioH: {
type: Number,
default: 1
},
guidesH: {
type: Boolean,
default: true
},
zoomOnWheelH: {
type: Boolean,
default: true
},
zoomOnTouchH: {
type: Boolean,
default: true
},
zoomableH: {
type: Boolean,
default: true
},
cropBoxMovableH: {
type: Boolean,
default: true
},
cropBoxResizableH: {
type: Boolean,
default: true
},
movableH: {
type: Boolean,
default: true
},
viewModeH: {
type: Number,
default: 2
},
autoCropAreaH: {
type: Number,
default: 0.5
},
},
methods: {
change(event) {
let image = document.getElementById('img');
this.clip(event, {
resultObj: image,
aspectWithRatio: 1,
aspectHeightRatio: 1
})
},
initilize(opt) {
let self = this;
this.options = opt;
this.createElement();
this.resultObj = opt.resultObj;
this.cropper = new Cropper(this.preview, {
aspectRatio: this.aspectRatioH,
initialAspectRatio: this.initialAspectRatioH,
autoCropArea: this.autoCropAreaH,
viewMode: this.viewModeH,
guides: this.guidesH,
cropBoxResizable: this.cropBoxResizableH,
cropBoxMovable: this.cropBoxMovableH,
dragCrop: false,
dragMode: "move",
center: true,
zoomable: this.zoomableH,
zoomOnTouch: this.zoomOnTouchH,
scalable: true,
background: false,
checkOrientation: true,
checkCrossOrigin: true,
movable: this.movableH,
zoomOnWheel: this.zoomOnWheelH,
center: false,
toggleDragModeOnDblclick: false,
ready: function () {
if (opt.aspectRatio == 'Free') {
let cropBox = self.cropper.cropBox;
cropBox.querySelector('span.cropper-view-box').style.outline = 'none';
self.cropper.disable();
}
}
});
},
createElement() {
this.preview = null;
let str = '<div><img id="clip_image" src=""></div><button type="button" id="cancel_clip">取消</button><button type="button" id="clip_button">确定</button>';
str += '<div class="crop_loading"><div class="crop_content"><div class="crop_text">图片修剪中...</div></div></div>';
str += '<div class="crop_success"><div class="crop_success_text">上传成功</div></div></div>';
let body = document.getElementsByTagName('body')[0];
this.reagion = document.createElement('div');
this.reagion.id = 'clip_container';
this.reagion.className = 'container';
this.reagion.innerHTML = str;
body.appendChild(this.reagion);
this.preview = document.getElementById('clip_image');
this.initFunction();
},
initFunction() {
let self = this;
this.clickBtn = document.getElementById('clip_button');
this.cancelBtn = document.getElementById('cancel_clip');
this.addEvent(this.clickBtn, 'click', function () {
self.crop();
})
this.addEvent(this.cancelBtn, 'click', function () {
self.destoried();
})
},
clip(e, opt) {
this.initilize(opt);
this.picValue = e;
if (typeof e == 'object') {
this.originUrl = this.getObjectURL(this.picValue);
} else {
this.originUrl = e
}
if (this.cropper) {
this.cropper.replace(this.originUrl);
}
},
getObjectURL(file) {
let url = null;
if (window.createObjectURL != undefined) {
url = window.createObjectURL(file);
} else if (window.URL != undefined) {
url = window.URL.createObjectURL(file);
} else if (window.webkitURL != undefined) {
url = window.webkitURL.createObjectURL(file);
}
return url;
},
crop() {
let self = this;
let image = new Image();
let croppedCanvas;
let roundedCanvas;
document.querySelector('.crop_loading').style.display = 'block';
setTimeout(function () {
croppedCanvas = self.cropper.getCroppedCanvas();
roundedCanvas = self.getRoundedCanvas(croppedCanvas);
let imgData = roundedCanvas.toDataURL();
image.src = imgData;
self.resultObj.src = imgData;
self.$emit('returnCrop', {
baseb4Data: imgData,
blobData: self.dataURItoBlob(imgData)
})
self.destoried()
}, 20)
},
getRoundedCanvas(sourceCanvas) {
let canvas = document.createElement('canvas');
let context = canvas.getContext('2d');
let width = sourceCanvas.width;
let height = sourceCanvas.height;
canvas.width = width;
canvas.height = height;
context.imageSmoothingEnabled = true;
context.drawImage(sourceCanvas, 0, 0, width, height);
context.globalCompositeOperation = 'destination-in';
context.beginPath();
context.rect(0, 0, width, height);
context.fill();
return canvas;
},
destoried() {
let self = this;
this.removeEvent(this.clickBtn, 'click', null);
this.removeEvent(this.cancelBtn, 'click', null);
this.reagion.parentNode.removeChild(this.reagion);
this.cropper.destroy();
this.cropper = null;
},
rotateImg(img, direction, canvas) {
const min_step = 0;
const max_step = 3;
if (img == null) return;
let height = img.height;
let width = img.width;
let step = 2;
if (step == null) {
step = min_step;
}
if (direction == 'right') {
step++;
step > max_step && (step = min_step);
} else {
step--;
step < min_step && (step = max_step);
}
let degree = step * 90 * Math.PI / 180;
let ctx = canvas.getContext('2d');
switch (step) {
case 0:
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0);
break;
case 1:
canvas.width = height;
canvas.height = width;
ctx.rotate(degree);
ctx.drawImage(img, 0, -height);
break;
case 2:
canvas.width = width;
canvas.height = height;
ctx.rotate(degree);
ctx.drawImage(img, -width, -height);
break;
case 3:
canvas.width = height;
canvas.height = width;
ctx.rotate(degree);
ctx.drawImage(img, -width, 0);
break;
}
},
addEvent(obj, type, fn) {
if (obj.addEventListener) {
obj.addEventListener(type, fn, false);
} else {
obj.attachEvent('on' + type, fn);
}
},
removeEvent(obj, type, fn) {
if (obj.removeEventListener) {
obj.removeEventListener(type, fn, false);
} else {
obj.detachEvent('on' + type, fn);
}
},
dataURItoBlob(dataURI) {
var base64Arr = dataURI.split(",");
var imgtype = "";
var base64String = "";
if (base64Arr.length > 1) {
base64String = base64Arr[1];
imgtype = base64Arr[0].substring(
base64Arr[0].indexOf(":") + 1,
base64Arr[0].indexOf(";")
);
}
var bytes = atob(base64String);
var bytesCode = new ArrayBuffer(bytes.length);
var byteArray = new Uint8Array(bytesCode);
for (var i = 0; i < bytes.length; i++) {
byteArray[i] = bytes.charCodeAt(i);
}
return new Blob([bytesCode], { type: imgtype });
}
}
}
</script>
<style scoped>
.vue-box {
position: relative;
width: 0;
height: 0;
}
img {
width: 100%;
height: 100%;
}
</style>
<style>
#clip_button {
position: absolute;
right: 10%;
bottom: 20px;
width: 80px;
height: 40px;
border: none;
border-radius: 2px;
background: #1AAD19;
color: #fff;
}
#cancel_clip {
position: absolute;
left: 10%;
bottom: 20px;
width: 80px;
height: 40px;
border: none;
border-radius: 2px;
color: #fff;
background: #E64340;
}
#clip_container.container {
z-index: 99999;
position: fixed;
padding-top: 60px;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 1);
}
#clip_container.container>div {
position: absolute;
width: 100%;
height: 100%;
top: 50%;
left: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
#clip_image {
max-width: 100%;
}
.cropper-container {
font-size: 0;
line-height: 0;
position: relative;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
direction: ltr;
-ms-touch-action: none;
touch-action: none
}
.crop_loading,
.crop_success {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 9;
}
.crop_loading .crop_content {
position: absolute;
top: 50%;
left: 50%;
text-align: center;
background: #000;
opacity: 0.9;
height: 66px;
width: 140px;
vertical-align: middle;
color: #fff;
padding-top: 20px;
font-size: 16px;
-webkit-border-radius: 3px;
border-radius: 3px;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
.crop_loading .crop_content img {
margin-top: 15px;
margin-bottom: 10px;
}
.crop_success .crop_success_text {
position: absolute;
top: 50%;
left: 50%;
text-align: center;
background: #000;
opacity: 0.9;
width: 120px;
height: 30px;
color: #fff;
line-height: 30px;
font-size: 16px;
-webkit-border-radius: 3px;
border-radius: 3px;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
.cropper-container img {
display: block;
min-width: 0 !important;
max-width: none !important;
min-height: 0 !important;
max-height: none !important;
width: 100%;
height: 100%;
image-orientation: 0deg
}
.cropper-wrap-box,
.cropper-canvas,
.cropper-drag-box,
.cropper-crop-box,
.cropper-modal {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.cropper-wrap-box {
overflow: hidden;
}
.cropper-drag-box {
opacity: 0;
background-color: #fff;
}
.cropper-modal {
opacity: .5;
background-color: #000;
}
.cropper-view-box {
display: block;
overflow: hidden;
width: 100%;
height: 100%;
outline: 1px solid #39f;
outline-color: rgba(51, 153, 255, 0.75);
}
.cropper-dashed {
position: absolute;
display: block;
opacity: .5;
border: 0 dashed #eee
}
.cropper-dashed.dashed-h {
top: 33.33333%;
left: 0;
width: 100%;
height: 33.33333%;
border-top-width: 1px;
border-bottom-width: 1px
}
.cropper-dashed.dashed-v {
top: 0;
left: 33.33333%;
width: 33.33333%;
height: 100%;
border-right-width: 1px;
border-left-width: 1px
}
.cropper-center {
position: absolute;
top: 50%;
left: 50%;
display: block;
width: 0;
height: 0;
opacity: .75
}
.cropper-center:before,
.cropper-center:after {
position: absolute;
display: block;
content: ' ';
background-color: #eee
}
.cropper-center:before {
top: 0;
left: -3px;
width: 7px;
height: 1px
}
.cropper-center:after {
top: -3px;
left: 0;
width: 1px;
height: 7px
}
.cropper-face,
.cropper-line,
.cropper-point {
position: absolute;
display: block;
width: 100%;
height: 100%;
opacity: .1;
}
.cropper-face {
top: 0;
left: 0;
background-color: #fff;
}
.cropper-line {
background-color: #39f
}
.cropper-line.line-e {
top: 0;
right: -3px;
width: 5px;
cursor: e-resize
}
.cropper-line.line-n {
top: -3px;
left: 0;
height: 5px;
cursor: n-resize
}
.cropper-line.line-w {
top: 0;
left: -3px;
width: 5px;
cursor: w-resize
}
.cropper-line.line-s {
bottom: -3px;
left: 0;
height: 5px;
cursor: s-resize
}
.cropper-point {
width: 5px;
height: 5px;
opacity: .75;
background-color: #39f
}
.cropper-point.point-e {
top: 50%;
right: -3px;
margin-top: -3px;
cursor: e-resize
}
.cropper-point.point-n {
top: -3px;
left: 50%;
margin-left: -3px;
cursor: n-resize
}
.cropper-point.point-w {
top: 50%;
left: -3px;
margin-top: -3px;
cursor: w-resize
}
.cropper-point.point-s {
bottom: -3px;
left: 50%;
margin-left: -3px;
cursor: s-resize
}
.cropper-point.point-ne {
top: -3px;
right: -3px;
cursor: ne-resize
}
.cropper-point.point-nw {
top: -3px;
left: -3px;
cursor: nw-resize
}
.cropper-point.point-sw {
bottom: -3px;
left: -3px;
cursor: sw-resize
}
.cropper-point.point-se {
right: -3px;
bottom: -3px;
width: 20px;
height: 20px;
cursor: se-resize;
opacity: 1
}
@media (min-width: 768px) {
.cropper-point.point-se {
width: 15px;
height: 15px
}
}
@media (min-width: 992px) {
.cropper-point.point-se {
width: 10px;
height: 10px
}
}
@media (min-width: 1200px) {
.cropper-point.point-se {
width: 5px;
height: 5px;
opacity: .75
}
}
.cropper-point.point-se:before {
position: absolute;
right: -50%;
bottom: -50%;
display: block;
width: 200%;
height: 200%;
content: ' ';
opacity: 0;
background-color: #39f
}
.cropper-invisible {
opacity: 0;
}
.cropper-bg {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMzTjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC');
}
.cropper-hide {
position: absolute;
display: block;
width: 0;
height: 0;
}
.cropper-hidden {
display: none !important;
}
.cropper-move {
cursor: move;
}
.cropper-crop {
cursor: crosshair;
}
.cropper-disabled .cropper-drag-box,
.cropper-disabled .cropper-face,
.cropper-disabled .cropper-line,
.cropper-disabled .cropper-point {
cursor: not-allowed;
}
</style>