行业知识
头像剪切上传
2026-11-04 19:41:26
来源:互联网转载
头像剪切上传
- 文章说明
- 核心Api
- 示例源码
- 效果展示
- ���码下载
文章说明
本文主要为了学习头像裁剪功能,以及熟悉canvas绘图和转文件的相关操作,参考教程(Web渡一前端–图片裁剪上传原理)
核心Api
主要就一个在canvas绘图的操作
context.drawImage(image, imgX, imgY, rectangleWidth, rectangleHeight, 0, 0, canvas.width, canvas.height);
以及canvas转为file对象的操作
let formData = new FormData();
const file = new File([blob], data.selectFileName, {type: data.selectFileType})
formData.append(“file”, file);
关于其中的绘制区域的大小缩放以及移动,也算是一个小难点;一般也有另一种裁剪区域风格,即四条线风格,可通过代码进行理解
示例源码
AvatarUpload.vue
jpg/png file with a size less than 5MBclick to upload
上传 import {onBeforeUnmount, onMounted, reactive} from "vue";import {axiosRequest, message} from "@/util/api";import {MethodType} from "@/util/constant";export default { setup: function () { const data = reactive({ src: null, imgHeight: "300px", selectFile: false, selectFileName: "", selectFileType: "", }); async function selectFile() { const pickerOpts = { types: [ { description: "Images", accept: { "image/*": [".png", ".jpeg", ".jpg"], }, }, ], excludeAcceptAllOption: true, multiple: false, }; try { const fileHandle = await window.showOpenFilePicker(pickerOpts); const file = await fileHandle[0].getFile(); data.selectFileName = file.name; data.selectFileType = file.type; 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; setTimeout(() => { data.imgHeight = image.height + "px"; rectangle = imgContainer.getElementsByClassName("rectangle")[0]; rectangleWidth = rectangle.clientWidth; rectangleHeight = rectangle.clientHeight; context.drawImage(image, (image.width / 2 - rectangleWidth / 2), (image.height / 2 - rectangleHeight / 2), rectangleWidth, rectangleHeight, 0, 0, canvas.width, canvas.height); }, 0); }; } catch (e) { if (!(e.name === 'AbortError' && e.message === 'The user aborted a request.')) { throw e; } } } let isDragging = false; let mouseUpListener; let containerX; let containerY; let imgContainer; let imgWidth; let imgHeight; let rectangle; let initialX; let initialY; let rectangleWidth; let rectangleHeight; let imgX; let imgY; let headerHeight; let canvas; let context; let image; function dragStart(e) { isDragging = true; containerX = imgContainer.offsetLeft; containerY = imgContainer.offsetTop; imgWidth = imgContainer.clientWidth; imgHeight = imgContainer.clientHeight; initialX = e.offsetX; initialY = e.offsetY; rectangle = imgContainer.getElementsByClassName("rectangle")[0]; rectangleWidth = rectangle.clientWidth; rectangleHeight = rectangle.clientHeight; } function changePos(e) { if (!isDragging) { return; } const x = e.clientX - containerX - initialX + rectangleWidth / 2; const y = e.clientY - containerY - initialY - headerHeight + rectangleHeight / 2; if (x >= rectangleWidth / 2 + 3 && x = rectangleHeight / 2 + 3 && y { mouseUpListener = () => { isDragging = false; } document.addEventListener("mouseup", mouseUpListener); const containerList = document.getElementsByClassName("container"); const container = containerList[containerList.length - 1]; headerHeight = container.parentNode["getBoundingClientRect"]().y; imgContainer = container.getElementsByClassName("img-container")[0]; canvas = container.getElementsByClassName("canvas")[0]; context = canvas.getContext("2d"); }); onBeforeUnmount(() => { document.removeEventListener("mouseup", mouseUpListener); }); const gap = 2; const minRange = 20; let centerX; let centerY; function wheel(e) { if (!centerX) { centerX = image.width / 2; } if (!centerY) { centerY = image.height / 2; } if (e.deltaY > 0) { if (rectangleWidth + gap >= image.width || rectangleHeight + gap >= image.height) { return; } if ((centerX - rectangleWidth / 2 - gap { let formData = new FormData(); const file = new File([blob], data.selectFileName, {type: data.selectFileType}) formData.append("file", file); axiosRequest(MethodType.post, "/user/uploadAvatar", formData, (res) => { message(res.data.msg, "info"); }); }, data.selectFileType); } return { data, selectFile, dragStart, changePos, wheel, uploadAvatar, } }}.container { margin: 0 auto; padding-top: 100px; width: fit-content; user-select: none; display: flex; justify-content: center; align-items: center;}.img-container { position: relative; width: fit-content;}.rectangle { width: 100px; height: 100px; border: 1px dashed #409eff; position: absolute; left: 50%; top: 50%; transform: translateX(-50%) translateY(-50%); z-index: 999; box-shadow: #888888 0 0 1px 1px; cursor: pointer;}.select-file { width: 500px; height: 300px; border: 1px dashed #dcdfe6; border-radius: 20px; display: flex; justify-content: center; align-items: center;}.select-file:hover { border: 1px dashed #409eff; cursor: pointer;}.select-file p { font-size: 14px; color: #606266;}.select-file p em { color: #409eff; font-style: normal; margin-left: 5px;}.img { border-radius: 20px; border: 1px dashed #409eff;}.canvas { margin-left: 100px; border: 1px dashed #409eff; float: left; border-radius: 50%;}.upload-button { position: absolute; width: 180px; height: 50px; top: 460px; font-size: 20px;}效果展示
关于裁剪区域的风格,设置为四条线可移动那种,需要改动一些代码,考虑后续补充
源码下载
参见Gitee链接(WEB-OS-SYSTEM)