功能简介:在矩形框内拖拽图片到合适位置(图片可超过自身宽度的一半),并可对图片进行放大缩小操作。获取到图片相对于矩形框的位置及图片缩放比例。
效果图
完整代码
DragImg.vue文件
<template>
<div>
<div class="box margin-b15">
<img class="avatar" v-show="imageUrl" :src="imageUrl" draggable="false" />
</div>
<span class="margin-b15">请使用鼠标滚轮进行缩放操作,或输入缩放比例</span>
<div class="hander-box margin-b15">
缩放比例:
<a-input-number
:max="1.5"
:min="0.5"
:step="0.1"
:precision="2"
v-model="sizeProportion"
@change="changeProportion"
></a-input-number>
</div>
</div>
</template>
<script>
export default {
data() {
return {
DELTA: 1.1,
x: null,
y: null,
oBox: null,
oDiv: null,
skewing: {}, // 偏移量
sizeProportion: 1, // 缩放比
};
},
props: {
imageUrl: String,
imgInfo: {
type: Object,
default: () => {
return {};
},
},
},
watch: {
imgInfo: {
handler(imgInfo) {
this.sizeProportion = imgInfo?.sizeProportion || 1;
if (imgInfo?.transform) {
setTimeout(() => {
// 展示图片偏移调整
this.oDiv.style.transform = imgInfo.transform;
this.skewing = this.getElementTop(this.oBox, this.oDiv);
}, 100);
}
},
immediate: true,
deep: true,
},
imageUrl: {
handler() {
if (!this.imgInfo?.transform) {
setTimeout(() => {
this.oDiv.style.transform = "translate(-50%, -50%)";
}, 100);
}
},
immediate: true,
},
},
mounted() {
this.oBox = document.querySelector(".box");
this.oDiv = document.querySelector(".avatar");
this.init();
},
methods: {
getElementTop(parent, sub) {
// 获取偏移位置
const parentClient = parent?.getBoundingClientRect() || {};
const subClient = sub?.getBoundingClientRect() || {};
return {
y: parseInt(subClient.top - parentClient.top),
x: parseInt(subClient.left - parentClient.left),
};
},
init() {
// 初始化数据
this.DELTA = 1.1;
this.x = null;
this.y = null;
this.skewing = this.getElementTop(this.oBox, this.oDiv);
// 禁止选中文字/图片
document.addEventListener("selectstart", (e) => {
e.preventDefault();
});
// 鼠标按下事件
this.oDiv.addEventListener("mousedown", this.mouseDown);
// 图片缩放
this.oDiv.addEventListener("wheel", this.zoom);
},
// 鼠标按下 获取位置并添加事件监听
mouseDown(e) {
let transf = this.getTransform(this.oDiv);
this.x = e.clientX - transf.transX; // 图片初始位置
this.y = e.clientY - transf.transY; // 图片初始位置
document.addEventListener("mousemove", this.mouseMove);
document.addEventListener("mouseup", this.mouseUp);
},
// 鼠标拖动 更新transform
mouseMove(e) {
let multiple = this.getTransform(this.oDiv).multiple;
let moveX = e.clientX - this.x; // x向移动距离
let moveY = e.clientY - this.y; // y向移动距离
let newTransf = this.limitBorder(this.oDiv, this.oBox, moveX, moveY, multiple);
this.oDiv.style.transform = `matrix(${multiple}, 0, 0, ${multiple}, ${newTransf.transX}, ${newTransf.transY})`;
},
// 鼠标抬起 移除监听器
mouseUp() {
this.skewing = this.getElementTop(this.oBox, this.oDiv);
document.removeEventListener("mousemove", this.mouseMove);
document.removeEventListener("mouseup", this.mouseUp);
},
// 鼠标滚轮缩放 更新transform
zoom(e) {
let transf = this.getTransform(this.oDiv);
if (e.deltaY < 0) {
// deltaY属性在向下滚动时返回正值,向上滚动时返回负值,否则为0
if (transf.multiple > 1.37) return; // 放大不可超过150%
transf.multiple *= this.DELTA; // 放大DELTA倍
} else {
if (transf.multiple < 0.6) return; //缩小不可超过50%
transf.multiple /= this.DELTA; // 缩小DELTA倍
}
this.sizeProportion = transf.multiple || 1; // 缩放比
let newTransf = this.limitBorder(
this.oDiv,
this.oBox,
transf.transX,
transf.transY,
transf.multiple
);
this.oDiv.style.transform = `matrix(${transf.multiple}, 0, 0, ${transf.multiple}, ${newTransf.transX}, ${newTransf.transY})`;
},
/**
* 通过getComputedStyle获取transform矩阵 并用split分割
* 如 oDiv 的 transform: translate(100, 100);
* getComputedStyle可以取到"matrix(1, 0, 0, 1, 100, 100)"
* 当transform属性没有旋转rotate和拉伸skew时
* metrix的第1, 4, 5, 6个参数为 x方向倍数, y方向倍数, x方向偏移量, y方向偏移量
* 再分别利用 字符串分割 取到对应参数
*/
getTransform(DOM) {
let arr = getComputedStyle(DOM).transform.split(",");
return {
transX: isNaN(+arr[arr.length - 2]) ? 0 : +arr[arr.length - 2], // 获取translateX
transY: isNaN(+arr[arr.length - 1].split(")")[0])
? 0
: +arr[arr.length - 1].split(")")[0], // 获取translateX
multiple: +arr[3], // 获取图片缩放比例
};
},
/**
* 获取边框限制的transform的x, y偏移量
* innerDOM: 内盒子DOM
* outerDOM: 边框盒子DOM
* moveX: 盒子的x移动距离
* moveY: 盒子的y移动距离
*/
limitBorder(innerDOM, outerDOM, moveX, moveY, multiple) {
let {
clientWidth: innerWidth,
clientHeight: innerHeight,
offsetLeft: innerLeft,
offsetTop: innerTop,
} = innerDOM;
let { clientWidth: outerWidth, clientHeight: outerHeight } = outerDOM;
outerWidth = outerWidth + 100;
outerHeight = outerHeight + 100;
let transX;
let transY;
// 放大的图片超出box时 图片最多拖动到与边框对齐
if (innerWidth * multiple > outerWidth || innerHeight * multiple > outerHeight) {
if (innerWidth * multiple > outerWidth && innerWidth * multiple > outerHeight) {
transX = Math.min(
Math.max(moveX, outerWidth - (innerWidth * (multiple + 1)) / 2 - innerLeft),
-innerLeft + (innerWidth * (multiple - 1)) / 2
);
transY = Math.min(
Math.max(moveY, outerHeight - (innerHeight * (multiple + 1)) / 2 - innerTop),
-innerTop + (innerHeight * (multiple - 1)) / 2
);
} else if (
innerWidth * multiple > outerWidth &&
!(innerWidth * multiple > outerHeight)
) {
transX = Math.min(
Math.max(moveX, outerWidth - (innerWidth * (multiple + 1)) / 2 - innerLeft),
-innerLeft + (innerWidth * (multiple - 1)) / 2
);
transY = Math.max(
Math.min(moveY, outerHeight - (innerHeight * (multiple + 1)) / 2 - innerTop),
-innerTop + (innerHeight * (multiple - 1)) / 2
);
} else if (
!(innerWidth * multiple > outerWidth) &&
innerWidth * multiple > outerHeight
) {
transX = Math.max(
Math.min(moveX, outerWidth - (innerWidth * (multiple + 1)) / 2 - innerLeft),
-innerLeft + (innerWidth * (multiple - 1)) / 2
);
transY = Math.min(
Math.max(moveY, outerHeight - (innerHeight * (multiple + 1)) / 2 - innerTop),
-innerTop + (innerHeight * (multiple - 1)) / 2
);
}
}
// 图片小于box大小时 图片不能拖出边框
else {
transX = Math.max(
Math.min(moveX, outerWidth - (innerWidth * (multiple + 1)) / 2 - innerLeft),
-innerLeft + (innerWidth * (multiple - 1)) / 2 - 100 //最小到哪(左右)
);
transY = Math.max(
Math.min(moveY, outerHeight - (innerHeight * (multiple + 1)) / 2 - innerTop),
-innerTop + (innerHeight * (multiple - 1)) / 2 - 100 // 最大到哪(上下)
);
}
return { transX, transY };
},
changeProportion(multiple) {
// 手动修改缩放比例
let transf = this.getTransform(this.oDiv);
if (multiple > 1) {
// deltaY属性在向下滚动时返回正值,向上滚动时返回负值,否则为0
if (multiple > 1.37) return; // 放大不可超过150%
multiple *= this.DELTA; // 放大DELTA倍
} else {
if (multiple < 0.6) return; //缩小不可超过50%
multiple /= this.DELTA; // 缩小DELTA倍
}
let newTransf = this.limitBorder(
this.oDiv,
this.oBox,
transf.transX,
transf.transY,
multiple
);
this.oDiv.style.transform = `matrix(${multiple}, 0, 0, ${multiple}, ${newTransf.transX}, ${newTransf.transY})`;
},
submit() {
let submitInfo = {
sizeProportion: this.sizeProportion,
transform: this.oDiv.style.transform, // 用作记录位置,作为反选用
skewing: JSON.stringify({ x: 50, y: 50, ...this.skewing, avatarProportion: 200 }), // avatarProportion为头像大小
};
debugger;
this.$emit("getSubmitInfo", submitInfo);
},
},
};
</script>
<style lang="less" scoped>
.box {
position: relative;
width: 300px;
height: 300px;
background-image: url("https://static.mycplife.com/xka/static/wx/headImagBg.png");
overflow: hidden;
}
.avatar {
position: absolute;
width: 200px;
height: 200px;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.margin-b15 {
margin-bottom: 15px;
}
</style>
使用:
<template>
<div>
<div class="upload-content">
<div class="img-content">
<drag-img
ref="DragImg"
:imageUrl="submitInfo.image"
:imgInfo="submitInfo"
@getSubmitInfo="getSubmitInfo"
></drag-img>
</div>
</div>
<button @click="saveHead">保存</button>
图片信息{{ positinoInfo }}
</div>
</template>
<script>
import DragImg from "./DragImg.vue";
export default {
components: { DragImg },
data() {
return {
submitInfo: {},
positinoInfo: {}, // skewing: {x, y, avatarProportion: 头像大小}, transform: 用作记录位置,作为反选用, sizeProportion: 缩放比
};
},
mounted() {
this.initDraw({});
},
methods: {
initDraw(currentRow = {}) {
this.submitInfo = {
...currentRow,
image:
"https://image.mycplife.com/mycp888/w800h800/png/2022/11/16/23af3007b6dd41e3bcf152cd85a5d102.png",
};
},
// 保存
saveHead() {
this.$refs["DragImg"]?.submit();
},
getSubmitInfo(positinoInfo = {}) {
this.positinoInfo = positinoInfo;
},
},
};
</script>
<style lang="less" scoped></style>