当前位置: 首页 > news >正文

修正版头像上传组件

修正版头像上传组件

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

文章说明

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

运行效果展示

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

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

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

源码下载

头像上传组件

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 网络规划与设计————期末复习
  • 华为手机联系人不见了怎么恢复?3个解决方案
  • Go协程与通道的综合应用问题
  • 240707-Sphinx配置Pydata-Sphinx-Theme
  • Linux tputs
  • vb.netcad二开自学笔记9:界面之ribbon
  • linux源码安装mysql8.0的小白教程
  • Nginx和Tomcat实现负载均衡群集部署应用
  • k8s record 20240705
  • 视频号矩阵系统源码,实现AI自动生成文案和自动回复私信评论,支持多个短视频平台
  • Android Camera Framework:从基础到高级
  • vue3+springboot+mybatis+mysql项目实践--简单登录注册功能实现
  • seaweedfs + TiKV 部署保姆级教程
  • C语言文件操作技术详解
  • React组件间通信的几种方式
  • [nginx文档翻译系列] 控制nginx
  • 【划重点】MySQL技术内幕:InnoDB存储引擎
  • 0基础学习移动端适配
  • 2019.2.20 c++ 知识梳理
  • 5、React组件事件详解
  • CSS实用技巧
  • MySQL的数据类型
  • node.js
  • 代理模式
  • 警报:线上事故之CountDownLatch的威力
  • 开源SQL-on-Hadoop系统一览
  • 前嗅ForeSpider教程:创建模板
  • 如何选择开源的机器学习框架?
  • 思考 CSS 架构
  • 通过git安装npm私有模块
  • 主流的CSS水平和垂直居中技术大全
  • k8s使用glusterfs实现动态持久化存储
  • 阿里云重庆大学大数据训练营落地分享
  • 哈罗单车融资几十亿元,蚂蚁金服与春华资本加持 ...
  • 数据可视化之下发图实践
  • ​经​纬​恒​润​二​面​​三​七​互​娱​一​面​​元​象​二​面​
  • ‌分布式计算技术与复杂算法优化:‌现代数据处理的基石
  • #Linux(帮助手册)
  • #NOIP 2014# day.1 T2 联合权值
  • #调用传感器数据_Flink使用函数之监控传感器温度上升提醒
  • #我与Java虚拟机的故事#连载16:打开Java世界大门的钥匙
  • (06)Hive——正则表达式
  • (2)STM32单片机上位机
  • (day6) 319. 灯泡开关
  • (k8s)Kubernetes本地存储接入
  • (二)【Jmeter】专栏实战项目靶场drupal部署
  • (回溯) LeetCode 78. 子集
  • (精确度,召回率,真阳性,假阳性)ACC、敏感性、特异性等 ROC指标
  • (三)centos7案例实战—vmware虚拟机硬盘挂载与卸载
  • (十五)使用Nexus创建Maven私服
  • (算法)Travel Information Center
  • (一)kafka实战——kafka源码编译启动
  • (转)h264中avc和flv数据的解析
  • .axf 转化 .bin文件 的方法
  • .NET CLR Hosting 简介