修正版頭像上傳組件

修正版頭像上傳組件

    • 文章說明
    • 核心源碼展示
    • 運行效果展示
    • 源碼下載

文章說明

在頭像剪切上傳一文中,我采用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.pswp.cn/web/44482.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/44482.shtml
英文地址,請注明出處:http://en.pswp.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…