修正版头像上传组件

修正版头像上传组件

    • 文章说明
    • 核心源码展示
    • 运行效果展示
    • 源码下载

文章说明

在头像剪切上传一文中,我采用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;}}
}

运行效果展示

点击选择图片
在这里插入图片描述

可以拖动裁剪框
在这里插入图片描述

可以调整裁剪框大小
在这里插入图片描述

源码下载

头像上传组件

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/44482.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

永恒之蓝:一场网络风暴的启示

引言 在网络安全的漫长历史中&#xff0c;“永恒之蓝”&#xff08;EternalBlue&#xff09;是一个不可忽视的里程碑事件。它不仅揭示了网络世界的脆弱性&#xff0c;还促使全球范围内对网络安全的重视达到了前所未有的高度。本文将深入探讨“永恒之蓝”漏洞的起源、影响及其对…

【WebGIS】从设计层面设计系统

本项目在通过现代信息技术手段&#xff0c;对古村古镇进行多方位、多角度的数字化记录、展示与传播&#xff0c;实现文化遗产的数字化保护、活化利用与共享。项目内容主要包括&#xff1a;1&#xff09;古村古镇数据库的建立&#xff1a;通过多种渠道收集古村古镇的各类信息&am…

期货量化交易客户端开源教学第八节——TCP通信服务类

private FReciveStr: AnsiString; {接收到的数据} IsConErr: Boolean; {网络连接是否失败} FSocket_LB: Integer; {TCP连接类别,0为交易,1为行情,2为查询} FRetryCount: Integer; {网络连接重试次数} FLoginErrEvent: TLoginErrEvent; {…

如何从 PDF 中删除背景

您是否曾经收到过充满分散注意力背景的扫描 PDF 文档&#xff1f;也许是带有繁忙水印的旧收据或背景光线不均匀的扫描文档。虽然这些背景可能看起来没什么大不了的&#xff0c;但它们会使您的工作空间变得混乱&#xff0c;并使您难以专注于重要信息。轻松删除这些不需要的元素并…

短视频SEO矩阵系统:源码开发与部署全攻略

在数字化时代&#xff0c;短视频已成为人们获取信息、娱乐休闲的重要方式。随着短视频平台的兴起&#xff0c;如何让自己的内容在众多视频中脱颖而出&#xff0c;成为每个创作者和内容运营者关注的焦点。本文将为您深入解析短视频SEO矩阵系统的源码开发与部署&#xff0c;助您在…

MT6825磁编码IC在智能双旋机器人中的应用

MT6825磁编码IC在智能双旋机器人中的应用&#xff0c;无疑为这一领域的创新和发展注入了新的活力。作为一款高性能的磁性位置传感器&#xff0c;MT6825以其独特的优势&#xff0c;在智能双旋机器人的运动控制、定位精度以及系统稳定性等方面发挥了关键作用。 www.abitions.com …

django ninja get not allowed 能用 put delete

遇到一个奇怪的问题&#xff0c;django-ninja 编写的 get post 方法不能使用 # 获取Material router.get(/material, responseList[MaterialSchemaOut]) paginate(MyPagination) def list_material(request, filters: Filters Query(...)):qs retrieve(request, Material, f…

Midjourney v6.5 可能会在“7月底”发布,并改进了真实感和皮肤纹理

Midjourney v6.5即将发布&#xff0c;这一更新将大幅提升图像的真实感和皮肤纹理&#xff0c;为用户带来更逼真的视觉体验。首席执行官David Holz在电话会议中宣布&#xff0c;新版本将提高图像清晰度&#xff0c;特别是在手部和皮肤细节上&#xff0c;同时改进Web应用程序和个…

ABAP调用BAPI时COMMIT WORK AND WAIT未按照预期同步提交问题分析

背景&#xff1a; 在做ABAP开发时&#xff0c;经常会有连续调用BAPI的需求&#xff0c;比如先创建销售订单&#xff0c;再依据销售订单创建交货单&#xff0c;再对交货单进行过账等类似的一连串调用&#xff0c;这种类似的场景往往需要前一步操作的数据完全写入数据库才能进行…

编译打包自己的云手机(redroid)镜像

前言 香橙派上跑云手机可以看之前的文章&#xff1a; 香橙派5plus上跑云手机方案一 redroid(带硬件加速)香橙派5plus上跑云手机方案二 waydroid 还有一个cuttlefish方案没说&#xff0c;后面再研究&#xff0c;cuttlefish的优势在于可以自定义内核且selinux是开启的&#xf…

vue3下载base64文件

如果后端明确告诉你返回的是base64&#xff0c;那请求头就不用带responseType: “blob”,和普通的接口一样发送就行 await materialsFile({ id: proxy.$route.query.id }).then((res) > {if (res) {// atob先解码base64数据const raw window.atob(res.data);// 获取解码后…

vscode 远程开发

目录 vscode 远程连接 选择 Python 环境 vscode 远程连接 按 CtrlShiftP 打开命令面板。输入并选择 Remote-SSH: Open SSH Configuration File...。选择 ~/.ssh/config 文件&#xff08;如果有多个选项&#xff09;。在打开的文件中添加或修改你的 SSH 配置。 这个可以右键…

Jupyter Notebook基础:用IPython实现动态编程

Jupyter Notebook基础&#xff1a;用IPython实现动态编程 1. 引言 Jupyter Notebook是一个基于Web的交互式计算环境&#xff0c;允许用户创建和共享包含实时代码、方程式、可视化和文本叙述的文档。它广泛应用于数据清洗与转换、数值模拟、统计建模、机器学习以及其他数据科学…

开放开源开先河(一)

2022年7月28日&#xff0c;以“软件定义世界 开源共筑未来”为主题的全球数字经济大会开放原子开源峰会在北京开幕&#xff0c;承办主峰会和为捐赠人进行授牌仪式的开放原子开源基金会再次进入公众视野。基金会秘书长孙文龙从汇聚全球产业链开源力量、核心链接能力、开发者分享…

Aop切面编程(2)--代理模式

1、代理模式的理解&#xff1a;不修改A对象的代码的基础上&#xff0c;对A代码块进行拓展。通过创建ProxyA代理对象&#xff0c;拓展A对象并调用A对象的核心功能&#xff1b; 即&#xff1a;不修改对象的源码基础上&#xff0c;创建代理对象&#xff0c;进行功能的附加和增强&…

端到端拥塞控制的本质

昨天整理了一篇 bbr 的微分方程组建模(参见 bbr 建模)&#xff0c;算是 bbr 算法终极意义上的一个总结&#xff0c;最后也顺带了对 aimd 的描述&#xff0c;算是我最近比较满意的一篇分享了。那么接下来的问题&#xff0c;脱离出具体算法&#xff0c;上升到宏观层面&#xff0c…

git reset hard和soft的使用和区别

在Git中&#xff0c;git reset命令用于撤销提交、回溯版本和调整工作目录或暂存区状态&#xff0c;而不是gitrestore。git reset主要有三种模式&#xff1a;--soft、--mixed&#xff08;默认&#xff09;和--hard。以下是关于--hard和--soft两种模式的使用方法和区别的详细解释…

uniapp微信小程序 TypeError: $refs[ref].push is not a function

我的写法 this.$refs.addPopup.open();报错 打印出来是这样的 解决 参考未整理 原因 在当前页面使用的v-for循环 并且循环体内也有组件使用了ref&#xff08;而我没有把每个ref做区别命名&#xff09; 这样就导致了我有很多同名的ref&#xff0c;然后就报错了 解决办法&a…

AI人工智能作词,为音乐注入未来之力

在当今的音乐世界中&#xff0c;创新的力量不断推动着边界的拓展&#xff0c;而人工智能作词正以其独特的魅力&#xff0c;成为引领音乐走向未来的强大动力。 “妙笔生词智能写歌词软件&#xff08;veve522&#xff09;”无疑是这股浪潮中的璀璨明星。它利用先进的人工智能技术…

记录一次Android推流、录像踩坑过程

背景&#xff1a; 按照需求&#xff0c;需要支持APP在手机息屏时进行推流、录像。 技术要点&#xff1a; 1、手机在息屏时能够打开camera获取预览数据 2、获取预览数据时进行编码以及合成视频 一、息屏时获取camera预览数据&#xff1a; ①Camera.setPreviewDisplay(SurfaceH…