应该是CropperJS的版本问题,2和1的操作方式不一样,使用2版本的方式 功能ok,可以测试
This commit is contained in:
@@ -1,131 +1,217 @@
|
||||
<template>
|
||||
<div class="image-cropper">
|
||||
<div class="cropper-actions">
|
||||
<input
|
||||
type="file"
|
||||
ref="fileInput"
|
||||
@change="handleFileSelect"
|
||||
accept="image/*"
|
||||
style="display: none"
|
||||
<div class="basic_container">
|
||||
<div class="tool_wrap">
|
||||
<button :class="{ active: isCropperMove }" @click="handleMove">
|
||||
移动
|
||||
</button>
|
||||
<button @click="handleRotate">旋转</button>
|
||||
<button :class="{ active: isCropperSelection }" @click="handleCropper">
|
||||
{{ isCropperSelection ? '重置选区' : '裁剪' }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="dialog_wrap">
|
||||
<div class="image_wrap" ref="imageWrap">
|
||||
<cropper-canvas ref="croppercanvas" background>
|
||||
<cropper-image
|
||||
:src="fileObj.fileShow"
|
||||
alt="Picture"
|
||||
ref="cropperimage"
|
||||
rotatable
|
||||
scalable
|
||||
skewable
|
||||
translatable
|
||||
></cropper-image>
|
||||
<cropper-shade hidden ref="cropperShade"></cropper-shade>
|
||||
<cropper-handle :action="currentType" plain></cropper-handle>
|
||||
<cropper-selection
|
||||
id="cropperSelection"
|
||||
ref="cropperselection"
|
||||
movable
|
||||
resizable
|
||||
hidden
|
||||
outlined
|
||||
@change="onCropperSelectionChange"
|
||||
>
|
||||
<cropper-crosshair centered />
|
||||
<cropper-handle
|
||||
action="move"
|
||||
theme-color="rgba(255, 255, 255, 0.35)"
|
||||
/>
|
||||
<button @click="$refs.fileInput.click()">选择图片</button>
|
||||
|
||||
<div v-if="haveImageSrc" class="action-buttons">
|
||||
<button @click="setAspectRatio(1)">1:1</button>
|
||||
<button @click="setAspectRatio(16 / 9)">16:9</button>
|
||||
<button @click="setAspectRatio(4 / 3)">4:3</button>
|
||||
<button @click="rotate(-90)">↺</button>
|
||||
<button @click="rotate(90)">↻</button>
|
||||
<button @click="crop">裁剪</button>
|
||||
<button @click="reset">重置</button>
|
||||
<cropper-handle action="n-resize" />
|
||||
<cropper-handle action="e-resize" />
|
||||
<cropper-handle action="s-resize" />
|
||||
<cropper-handle action="w-resize" />
|
||||
<cropper-handle action="ne-resize" />
|
||||
<cropper-handle action="nw-resize" />
|
||||
<cropper-handle action="se-resize" />
|
||||
<cropper-handle action="sw-resize" />
|
||||
</cropper-selection>
|
||||
</cropper-canvas>
|
||||
</div>
|
||||
<div class="info_wrap">
|
||||
<div class="cropper_preview">
|
||||
<cropper-viewer
|
||||
selection="#cropperSelection"
|
||||
style="width: 200px"
|
||||
></cropper-viewer>
|
||||
</div>
|
||||
<div class="btn_wrap">
|
||||
<input type="file" ref="input_form" @change="handleUploadSuccess" />
|
||||
<button type="primary" @click="handleConfirm">确 认</button>
|
||||
</div>
|
||||
点击确认后,看控制台,有信息
|
||||
</div>
|
||||
|
||||
<div v-if="haveImageSrc" class="cropper-container">
|
||||
<img ref="imageEl" alt="裁剪图片" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, nextTick } from "vue";
|
||||
import Cropper from "cropperjs";
|
||||
import 'cropperjs';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
const emit = defineEmits(["cropped", "error"]);
|
||||
const fileObj = ref({});
|
||||
|
||||
const imageEl = ref(null);
|
||||
const fileInput = ref(null);
|
||||
const haveImageSrc = ref(false);
|
||||
const resultImage = ref("");
|
||||
let cropperInstance = null;
|
||||
const croppercanvas = ref();
|
||||
const cropperimage = ref();
|
||||
const cropperselection = ref();
|
||||
|
||||
// 读取文件为DataURL
|
||||
const readFileAsDataURL = (file) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => resolve(e.target.result);
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
/**
|
||||
* 选区逻辑
|
||||
*/
|
||||
// 是否正在开始选区
|
||||
const isCropperSelection = ref(false);
|
||||
const isCropperMove = ref(true);
|
||||
|
||||
// 判断当前是移动还是选区
|
||||
const currentType = computed(() => (isCropperMove.value ? 'move' : 'select'));
|
||||
|
||||
/**
|
||||
* 按钮方法
|
||||
*/
|
||||
// 旋转
|
||||
function handleRotate() {
|
||||
cropperimage.value.$rotate('90deg');
|
||||
cropperimage.value.$center('contain');
|
||||
}
|
||||
// 裁剪
|
||||
function handleCropper() {
|
||||
isCropperMove.value = false;
|
||||
if (isCropperMove.value) {
|
||||
cropperselection.value.$clear();
|
||||
} else {
|
||||
const cropperCanvas = croppercanvas.value;
|
||||
const cropperCanvasRect = cropperCanvas.getBoundingClientRect();
|
||||
|
||||
const cropperImage = cropperimage.value;
|
||||
const cropperImageRect = cropperImage.getBoundingClientRect();
|
||||
const maxSelection = {
|
||||
x: cropperImageRect.left - cropperCanvasRect.left,
|
||||
y: cropperImageRect.top - cropperCanvasRect.top,
|
||||
width: cropperImageRect.width,
|
||||
height: cropperImageRect.height,
|
||||
};
|
||||
|
||||
// 选择文件
|
||||
const handleFileSelect = async (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
if (!file.type.startsWith("image/")) {
|
||||
emit("error", "请选择图片文件");
|
||||
return;
|
||||
cropperselection.value.$change(
|
||||
maxSelection.x,
|
||||
maxSelection.y,
|
||||
maxSelection.width,
|
||||
maxSelection.height,
|
||||
);
|
||||
}
|
||||
}
|
||||
// 移动
|
||||
function handleMove() {
|
||||
if (!isCropperMove.value) {
|
||||
isCropperMove.value = true;
|
||||
// 如果想要点击移动,清除选区,可以打开下面的代码注释
|
||||
// cropperselection.value.$clear();
|
||||
}
|
||||
console.log("选择了文件:", file.name);
|
||||
try {
|
||||
const dataUrl = await readFileAsDataURL(file);
|
||||
haveImageSrc.value = true;
|
||||
console.log("图片加载完成,初始化裁剪器");
|
||||
await nextTick();
|
||||
if (cropperInstance) {
|
||||
cropperInstance.destroy();
|
||||
console.log("销毁旧的Cropper实例");
|
||||
}
|
||||
console.log("加载图片进行裁剪");
|
||||
imageEl.value.src = dataUrl;
|
||||
|
||||
cropperInstance = new Cropper(imageEl.value, {
|
||||
aspectRatio: 1,
|
||||
viewMode: 2,
|
||||
autoCropArea: 0.8,
|
||||
zoomable: true,
|
||||
zoomOnWheel: true,
|
||||
zoomOnTouch: true,
|
||||
wheelZoomRatio: 0.1,
|
||||
ready: function () {
|
||||
console.log("Cropper 初始化成功");
|
||||
},
|
||||
crop: function (event) {
|
||||
// 可以在这里获取裁剪区域的信息
|
||||
console.log(event.detail);
|
||||
},
|
||||
/**
|
||||
* 监听选择区变化
|
||||
* @param event
|
||||
*/
|
||||
function onCropperSelectionChange(event) {
|
||||
if (event.detail.width && event.detail.height) {
|
||||
isCropperSelection.value = true;
|
||||
} else {
|
||||
isCropperSelection.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认裁剪
|
||||
*/
|
||||
const emit = defineEmits(['success']);
|
||||
async function handleConfirm() {
|
||||
if (isCropperSelection.value) {
|
||||
const res = await cropperselection.value.$toCanvas();
|
||||
|
||||
const dataImage = res.toDataURL('image/png');
|
||||
const file = dataURLtoFile(dataImage, fileObj.value.name);
|
||||
emit('success', {
|
||||
...fileObj.value,
|
||||
file: file,
|
||||
fileShow: dataImage,
|
||||
});
|
||||
} catch (error) {
|
||||
emit("error", "图片加载失败");
|
||||
}
|
||||
}
|
||||
// 将data:image转成新的file
|
||||
function dataURLtoFile(dataurl, filename) {
|
||||
var arr = dataurl.split(','),
|
||||
mime = arr[0].match(/:(.*?);/)[1],
|
||||
bstr = atob(arr[1]),
|
||||
n = bstr.length,
|
||||
u8arr = new Uint8Array(n);
|
||||
while (n--) {
|
||||
u8arr[n] = bstr.charCodeAt(n);
|
||||
}
|
||||
const blob = new Blob([u8arr], { type: mime });
|
||||
const file = new File([blob], filename, { type: mime });
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件上传
|
||||
*/
|
||||
const input_form = ref();
|
||||
function handleUploadSuccess() {
|
||||
const files = input_form.value.files;
|
||||
|
||||
if (files.length) {
|
||||
fileObj.value = {
|
||||
name: files[0].name,
|
||||
file: files[0],
|
||||
fileShow: URL.createObjectURL(files[0]),
|
||||
};
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.image-cropper {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.dialog_wrap {
|
||||
display: flex;
|
||||
.image_wrap {
|
||||
width: 400px;
|
||||
height: 300px;
|
||||
flex-shrink: 0;
|
||||
|
||||
.cropper-actions {
|
||||
margin-bottom: 20px;
|
||||
cropper-canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.action-buttons button {
|
||||
margin: 0 5px 5px 0;
|
||||
padding: 5px 10px;
|
||||
.info_wrap {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.cropper-container {
|
||||
max-height: 400px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.result-container {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
button {
|
||||
& + button {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.result-container img {
|
||||
max-width: 300px;
|
||||
max-height: 300px;
|
||||
border: 1px solid #ddd;
|
||||
margin: 10px 0;
|
||||
}
|
||||
button.active {
|
||||
background-color: #c6dff8;
|
||||
border-color: #409eff;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user