修正版头像上传组件
- 文章说明
- 核心源码展示
- 运行效果展示
- 源码下载
文章说明
在头像剪切上传一文中,我采用div做裁剪效果,感觉会有一些小问题,在昨天基于canvas绘制的功能中改进了一版,让代码变得更简洁,而且通用性相对高一些,源码及效果展示如下;包含拖拽和调整裁剪框的效果
核心源码展示
主要包括App.vue中的元素和事件,以及Rectangle.js内的绘图方法和鼠标移动事件
App.vue
<script setup>
import {nextTick, reactive} from "vue";
import {Rectangle} from "@/Rectangle";const data = reactive({selectFile: false,imgWidth: 0,imgHeight: 0,
});let image;
let leftCanvas;
let leftContext;
let rightCanvas;
let rightContext;
let rect;
let rectangle;async function selectFile() {const pickerOpts = {types: [{description: "Images",accept: {"image/*": [".png", ".jpeg", ".jpg"],},},],excludeAcceptAllOption: true,multiple: false,};const fileHandle = await window.showOpenFilePicker(pickerOpts);const file = await fileHandle[0].getFile();const reader = new FileReader();reader.readAsDataURL(file);reader.onload = function (e) {data.src = e.target.result;data.selectFile = true;image = new Image();image.src = e.target.result;nextTick(() => {data.imgWidth = image.width;data.imgHeight = image.height;nextTick(() => {leftCanvas = document.getElementsByClassName("left-canvas")[0];rect = leftCanvas.getBoundingClientRect();rightCanvas = document.getElementsByClassName("right-canvas")[0];leftContext = leftCanvas.getContext("2d");rightContext = rightCanvas.getContext("2d");rectangle = new Rectangle(data.imgWidth, data.imgHeight);change = true;draw();leftCanvas.onmousedown = (e) => {omMouseDown(e);};leftCanvas.onmousemove = (e) => {changeSize(e);};});});};
}let change;function draw() {if (change) {drawLeftImage(image, rectangle);drawRightImage(image, rectangle);change = false;}requestAnimationFrame(draw);
}function drawLeftImage(image, rectangle) {leftContext.drawImage(image, 0, 0, data.imgWidth, data.imgHeight, 0, 0, data.imgWidth, data.imgHeight);rectangle.draw(leftContext);
}function drawRightImage(image, rectangle) {rightContext.drawImage(image, rectangle.startX, rectangle.startY, rectangle.width, rectangle.height, 0, 0, rightCanvas.width, rightCanvas.height);
}function omMouseDown(e) {const clickX = e.clientX - rect.left;const clickY = e.clientY - rect.top;const {startX, startY, endX, endY} = rectangle;const inGap = rectangle.inGap(clickX, clickY);if (inGap > 0) {leftCanvas.onmousemove = (e) => {rectangle.mouseMoveChangeSize(e, rect, startX, startY, endX, endY, clickX, clickY, inGap, leftCanvas);change = true;};} else {leftCanvas.onmousemove = (e) => {rectangle.mouseMoveChangePos(e, rect, startX, startY, endX, endY, clickX, clickY, leftCanvas);change = true;};}window.onmouseup = () => {leftCanvas.onmousemove = null;leftCanvas.onmousemove = (e) => {changeSize(e);};};
}function changeSize(e) {const clickX = e.clientX - rect.left;const clickY = e.clientY - rect.top;const inGap = rectangle.inGap(clickX, clickY);const {startX, startY, endX, endY} = rectangle;rectangle.mouseMoveChangeSize(e, rect, startX, startY, endX, endY, clickX, clickY, inGap, leftCanvas);
}
</script><template><div class="avatar-container"><div class="img-container"><div v-show="!data.selectFile" class="select-file" @click="selectFile"><p>jpg/png file with a size less than 5MB<em>click to upload</em></p></div><canvas v-show="data.selectFile" :height="data.imgHeight" :width="data.imgWidth" class="left-canvas"></canvas></div><canvas class="right-canvas" height="100" width="100"></canvas></div>
</template><style scoped>
.avatar-container {margin: 0 auto;width: fit-content;user-select: none;display: flex;justify-content: center;align-items: center;padding-top: 100px;.img-container {position: relative;width: fit-content;.select-file {width: 500px;height: 300px;border: 1px dashed #dcdfe6;border-radius: 20px;display: flex;justify-content: center;align-items: center;&:hover {border: 1px dashed #409eff;cursor: pointer;}p {font-size: 14px;color: #606266;em {color: #409eff;font-style: normal;margin-left: 5px;}}}}.left-canvas {margin-left: 30px;border: 1px dashed #409eff;float: left;}.right-canvas {margin-left: 30px;border: 1px dashed #409eff;float: left;}
}
</style>
Rectangle.js
const gap = 10;
const initValue = 100;export class Rectangle {constructor(imageWidth, imageHeight) {const startX = (imageWidth - initValue) / 2;const startY = (imageHeight - initValue) / 2;this.startX = startX;this.startY = startY;this.endX = this.startX + initValue;this.endY = this.startY + initValue;this.imageWidth = imageWidth;this.imageHeight = imageHeight;}get minX() {return Math.min(this.startX, this.endX);}get maxX() {return Math.max(this.startX, this.endX);}get minY() {return Math.min(this.startY, this.endY);}get maxY() {return Math.max(this.startY, this.endY);}get width() {return this.maxX - this.minX;}get height() {return this.maxY - this.minY;}draw(ctx) {ctx.beginPath();ctx.moveTo(this.minX, this.minY);ctx.setLineDash([3, 2]);ctx.lineTo(this.maxX, this.minY);ctx.lineTo(this.maxX, this.maxY);ctx.lineTo(this.minX, this.maxY);ctx.lineTo(this.minX, this.minY);ctx.strokeStyle = "#409eff";ctx.lineWidth = 1;ctx.lineCap = "square";ctx.stroke();}// 上1、下2、左4、右8// 得到上1、下2、左4、左上5、左下6、右8、右上9、右下10inGap(x, y) {let result = 0;if (Math.abs(this.minY - y) <= gap && (this.minX - gap <= x && this.maxX + gap >= x)) {result += 1;}if (Math.abs(this.maxY - y) <= gap && (this.minX - gap <= x && this.maxX + gap >= x)) {result += 2;}if (Math.abs(this.minX - x) <= gap && (this.minY - gap <= y && this.maxY + gap >= y)) {result += 4;}if (Math.abs(this.maxX - x) <= gap && (this.minY - gap <= y && this.maxY + gap >= y)) {result += 8;}return result;}mouseMoveChangePos(e, rect, startX, startY, endX, endY, clickX, clickY, canvas) {const disX = e.clientX - rect.left - clickX;const disY = e.clientY - rect.top - clickY;if (startX + disX >= 0) {this.startX = startX + disX;}if (endX + disX <= this.imageWidth) {this.endX = endX + disX;}if (startY + disY >= 0) {this.startY = startY + disY;}if (endY + disY <= this.imageHeight) {this.endY = endY + disY;}canvas.style.cursor = "move";}mouseMoveChangeSize(e, rect, startX, startY, endX, endY, clickX, clickY, inGap, canvas) {const disX = e.clientX - rect.left - clickX;const disY = e.clientY - rect.top - clickY;if (endX + disX < startX || endY + disY < startY || startX + disX > endX || startY + disY > endY) {return;}switch (inGap) {case 1:canvas.style.cursor = "n-resize";this.startY = startY + disY;break;case 2:canvas.style.cursor = "s-resize";this.endY = endY + disY;break;case 4:canvas.style.cursor = "w-resize";this.startX = startX + disX;break;case 5:canvas.style.cursor = "nw-resize";this.startX = startX + disX;this.startY = startY + disY;break;case 6:canvas.style.cursor = "sw-resize";this.startX = startX + disX;this.endY = endY + disY;break;case 8:canvas.style.cursor = "e-resize";this.endX = endX + disX;break;case 9:canvas.style.cursor = "ne-resize";this.endX = endX + disX;this.startY = startY + disY;break;case 10:canvas.style.cursor = "se-resize";this.endX = endX + disX;this.endY = endY + disY;break;default:canvas.style.cursor = "default";break;}}
}
运行效果展示
点击选择图片
可以拖动裁剪框
可以调整裁剪框大小
源码下载
头像上传组件