vue3 vue-draggable-resizable-gorkys 图片拼接导出
<template>
<div class="parInfo" :style="{ transform: `scale(${scale})`, transformOrigin: '0 0' }">
<vue-draggable-resizable
v-for="(image, index) in list"
:key="index"
:w="imageWidth"
:h="imageHeights[index]"
:parent="false"
:x="index * (imageWidth + 10)"
:debug="false"
:min-width="imageWidth"
:min-height="imageHeights[index]"
:isConflictCheck="true"
:snap="true"
:snapTolerance="10"
@refLineParams="getRefLineParams"
class="draggable-image">
<div
class="imgMain"
@click="getIndex(index)"
:style="{
backgroundImage: `url(${image})`,
backgroundSize: 'cover',
transform: `
${flipStates[index] ? 'scaleX(-1)' : 'scaleX(1)'}
rotate(${rotationStates[index]}deg)
`
}">
</div>
</vue-draggable-resizable>
<!-- 裁剪框 -->
<vue-draggable-resizable
v-if="showCropBox"
@dragging="onCropBoxDrag"
@resizing="onCropBoxResize"
:class="['crop-box', { transparent: !showCropBox }]">
<div class="crop-box-handle"></div>
</vue-draggable-resizable>
<!--辅助线-->
<span class="ref-line v-line"
v-for="item in vLine"
v-show="item.display"
:style="{ left: item.position, top: item.origin, height: item.lineLength}"
/>
<span class="ref-line h-line"
v-for="item in hLine"
v-show="item.display"
:style="{ top: item.position, left: item.origin, width: item.lineLength}"
/>
<!--辅助线END-->
<!-- 下载按钮 -->
<!-- <button @click="downloadCroppedImage">下载裁剪图片</button>-->
</div>
</template>
<script>
import VueDraggableResizable from 'vue-draggable-resizable-gorkys/src/components/vue-draggable-resizable.vue'
import 'vue-draggable-resizable-gorkys/dist/VueDraggableResizable.css'
import html2canvas from 'html2canvas'; // 引入 html2canvas
import {ElLoading} from 'element-plus'; // 引入 element-plus 的 Loading 组件
export default {
name: 'app',
components: {
VueDraggableResizable
},
props: {
list: {
type: Array,
required: true,
default: () => [] // 提供默认值,防止 undefined
}
},
data() {
return {
vLine: [],
hLine: [],
imageWidth: 200, // 固定宽度
imageHeights: [], // 动态计算的高度数组
scale: 1, // 缩放比例,默认是1
activeIndex: null, // 当前选中的图片索引
flipStates: [], // 记录每个图片的翻转状态
rotationStates: [], // 记录每个图片的旋转角度
cropBox: {
x: 50,
y: 50,
width: 200,
height: 200
},
showCropBox: false // 控制裁剪框的显示状态
}
},
watch: {
// 监听 list 的变化
list: {
handler(newList) {
this.initialize(newList); // 重新初始化
},
immediate: true // 立即执行一次
}
},
methods: {
getIndex(index) {
this.activeIndex = index;
},
// 辅助线回调事件
getRefLineParams(params) {
const {vLine, hLine} = params;
this.vLine = vLine;
this.hLine = hLine;
},
// 初始化方法
initialize(list) {
if (!list || !list.length) return; // 如果 list 为空或 undefined,直接返回
this.imageHeights = []; // 清空高度数组
this.flipStates = new Array(list.length).fill(false); // 初始化翻转状态数组
this.rotationStates = new Array(list.length).fill(0); // 初始化旋转角度数组
this.loadImageDimensions(list); // 重新加载图片宽高
},
// 动态加载图片并获取宽高
loadImageDimensions(list) {
list.forEach((image, index) => {
const img = new Image();
img.src = image;
img.onload = () => {
const aspectRatio = img.height / img.width; // 计算宽高比
const height = this.imageWidth * aspectRatio; // 根据宽度计算高度
this.imageHeights[index] = height; // 直接更新数组
};
});
},
// 设置缩放比例
setScale(scale) {
this.scale = scale;
},
// 翻转图片
flipHorizontal() {
if (this.activeIndex !== null) {
this.flipStates[this.activeIndex] = !this.flipStates[this.activeIndex];
}
},
// 逆时针旋转90度
rotateLeft() {
if (this.activeIndex !== null) {
this.rotationStates[this.activeIndex] = (this.rotationStates[this.activeIndex] - 90) % 360;
}
},
// 裁剪框拖动事件
onCropBoxDrag(x, y) {
this.cropBox.x = x;
this.cropBox.y = y;
},
// 裁剪框缩放事件
onCropBoxResize(x, y, width, height) {
this.cropBox.x = x;
this.cropBox.y = y;
this.cropBox.width = width;
this.cropBox.height = height;
},
// 控制裁剪框的显示和隐藏
getCrop() {
this.showCropBox = !this.showCropBox;
},
// 下载裁剪后的图片
async downloadCroppedImage() {
// 显示 loading
const loadingInstance = ElLoading.service({
lock: true,
text: '裁剪中,请稍候...',
background: 'rgba(0, 0, 0, 0.7)'
});
try {
// 隐藏裁剪框(设置透明度为 0)
this.showCropBox = false; // 隐藏裁剪框
await this.$nextTick(); // 等待 DOM 更新
const parInfo = document.querySelector('.parInfo');
const cropBoxRect = {
x: this.cropBox.x,
y: this.cropBox.y,
width: this.cropBox.width,
height: this.cropBox.height
};
// 使用 html2canvas 渲染裁剪框内的内容
const canvas = await html2canvas(parInfo, {
x: cropBoxRect.x,
y: cropBoxRect.y,
width: cropBoxRect.width,
height: cropBoxRect.height,
scale: 1, // 设置缩放比例
useCORS: true, // 支持跨域图片
ignoreElements: (element) => {
// 忽略不支持的元素
return element.tagName === 'IFRAME' || element.tagName === 'VIDEO';
},
onclone: (clonedDoc) => {
// 调试:打印克隆的 DOM 结构
console.log('Cloned DOM:', clonedDoc);
}
});
// 将 canvas 转换为图片并触发下载
const link = document.createElement('a');
link.href = canvas.toDataURL('image/png');
link.download = 'cropped-image.png';
link.click();
} catch (error) {
console.error('Error rendering canvas:', error);
} finally {
// 显示裁剪框(恢复透明度)
this.showCropBox = true; // 恢复裁剪框
await this.$nextTick(); // 等待 DOM 更新
// 关闭 loading
loadingInstance.close();
}
}
}
}
</script>
<style lang="scss" scoped>
.parInfo {
position: relative;
width: 5000px;
height: 5000px;
//transition: transform 0.3s ease; // 添加平滑过渡效果
pointer-events: none; /* 确保不拦截裁剪框的事件 */
}
.draggable-image {
background-position: center;
background-repeat: no-repeat;
pointer-events: all; /* 允许图片接收事件 */
}
.imgMain {
position: relative;
width: 100%;
height: 100%;
transition: transform 0.3s ease; // 添加平滑过渡效果
}
.crop-box {
border: 2px dashed rgba(red, 0.8);
z-index: 1000;
pointer-events: all; /* 确保裁剪框可以接收事件 */
transition: opacity 0.3s ease; // 添加透明过渡效果
}
.crop-box.transparent {
opacity: 0; // 裁剪框透明
}
.crop-box-handle {
width: 100%;
height: 100%;
background: rgba(red, 0.3);
}
button {
position: absolute;
bottom: 10px;
right: 10px;
z-index: 1000;
pointer-events: all; /* 确保按钮可以接收事件 */
}
</style>