vue+canvas實現逐字手寫效果

在pc端進行逐字手寫的功能。用戶可以在一個?inputCanvas?上書寫單個字,然后在特定時間后將這個字添加到?outputCanvas?上,形成一個逐字的手寫效果。用戶還可以保存整幅圖像或者撤銷上一個添加的字。

<template><div class="container" v-if="!disabled"><div class="tipCn"><div>請您在右側區域內逐字手寫以下文字,全部寫完后點擊保存!</div><div>{{ ruleForm.sqcn }}</div></div><div style="margin: 0px 20px"><span class="dialog-footer"><el-button @click="undoChar" type="danger" :icon="RefreshRight">撤銷上一個字</el-button><el-button @click="saveImage" type="primary" :icon="Check">保存</el-button></span><canvasref="inputCanvas"class="input-canvas":width="canvasWidth":height="canvasHeight"@mousedown="startDrawing"@mousemove="draw"@mouseup="stopDrawing"@mouseleave="stopDrawing"></canvas></div><canvas ref="outputCanvas" class="output-canvas" :width="outputWidth" :height="outputHeight"></canvas></div><img class="Signature" v-else :src="resultImg" alt="commitment Image" />
</template><script setup>
import { ref, onMounted, nextTick, watch } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import fileService from "@/api/sys/fileService.js";
import { Check, RefreshRight } from "@element-plus/icons-vue";
import knsService from "@/api/sys/kns/knsService";const canvasWidth = 300;
const canvasHeight = 300;
const isDrawing = ref(false);
const startX = ref(0);
const startY = ref(0);
const charObjects = ref([]);
const timer = ref(null);
const delay = 1000; // 1秒延遲
let outputWidth = 300;
let outputHeight = ref(50);
let resultImg = ref("");
let context = null;
let outputContext = null;const inputCanvas = ref(null);
const outputCanvas = ref(null);let ruleForm = ref({});const emit = defineEmits(["update:modelValue"]);
const props = defineProps({modelValue: {type: [Number, String],default: ""},disabled: {type: Boolean,default: false}
});// 當輸入框內容變化時觸發更新父組件的 value
watch(resultImg,(newValue) => {emit("update:modelValue", newValue);},{ deep: true }
);watch(() => props.modelValue,(newValue) => {resultImg.value = newValue;},{ deep: true, immediate: true }
);onMounted(() => {if (!props.disabled) {getData();context = inputCanvas.value.getContext("2d");outputContext = outputCanvas.value.getContext("2d");context.strokeStyle = "#000000";context.lineWidth = 4;context.lineCap = "round";context.lineJoin = "round";outputContext.strokeStyle = "#000000";outputContext.lineWidth = 3;outputContext.lineCap = "round";outputContext.lineJoin = "round";}
});// 獲取承諾
const getData = async () => {const res = await knsService.getSettingData();ruleForm.value = res[0];
};const startDrawing = (e) => {if (timer.value) {clearTimeout(timer.value);timer.value = null;}isDrawing.value = true;startX.value = e.offsetX;startY.value = e.offsetY;context.beginPath();context.moveTo(startX.value, startY.value);
};const draw = (e) => {if (!isDrawing.value) return;context.lineTo(e.offsetX, e.offsetY);context.stroke();
};const stopDrawing = () => {if (isDrawing.value) {isDrawing.value = false;context.closePath();timer.value = setTimeout(addChar, delay);}
};const addChar = () => {const canvas = inputCanvas.value;const dataUrl = canvas.toDataURL("image/png");charObjects.value.push(dataUrl);clearCanvas();redrawOutputCanvas();
};const clearCanvas = () => {const canvas = inputCanvas.value;context.clearRect(0, 0, canvas.width, canvas.height);
};const undoChar = () => {if (charObjects.value.length > 0) {charObjects.value.pop();redrawOutputCanvas();if (charObjects.value.length === 0) {outputHeight.value = 50; // 如果字符對象為空,則將輸出畫布高度設置為 50outputCanvas.value.height = outputHeight.value; // 更新畫布高度}}
};const redrawOutputCanvas = () => {outputContext.clearRect(0, 0, outputWidth, outputHeight.value);const charSize = 50; // 調整字符大小const charSpacing = 50; // 調整字符間距const maxCharsPerRow = Math.floor(outputWidth / charSize); // 每行最大字符數const numRows = Math.ceil(charObjects.value.length / maxCharsPerRow); // 計算行數const newOutputHeight = numRows * charSize; // 動態計算輸出畫布的高度if (newOutputHeight !== outputHeight.value) {outputHeight.value = newOutputHeight;outputCanvas.value.height = outputHeight.value; // 更新畫布高度}nextTick(() => {charObjects.value.forEach((char, index) => {const rowIndex = Math.floor(index / maxCharsPerRow); // 當前字符的行索引const colIndex = index % maxCharsPerRow; // 當前字符的列索引const img = new Image();img.onload = () => {outputContext.drawImage(img, colIndex * charSpacing, rowIndex * charSpacing, charSize, charSize); // 繪制字符圖片到輸出畫布上};img.src = char;});});
};const saveImage = () => {if (charObjects.value.length === 0) {ElMessage.error("請輸入!");return false;}const canvas = outputCanvas.value;const dataUrl = canvas.toDataURL("image/png");console.log(dataUrl, "dataUrldataUrldataUrl"); // 您可以將此圖片上傳或保存// 生成帶有當前日期和時間的文件名const now = new Date();const filename = `承諾-${now.getFullYear()}${padZero(now.getMonth() + 1)}${padZero(now.getDate())}${padZero(now.getHours())}${padZero(now.getMinutes())}${padZero(now.getSeconds())}.jpg`;const blob = dataURLtoBlob(dataUrl);const tofile = blobToFile(blob, filename);setTimeout(async () => {const formData = new FormData();formData.append("file", tofile, tofile.name);formData.append("fileType", 9);console.log(formData, "formDataformData");const res2 = await fileService.uploadFile(formData);resultImg.value = res2;console.log(resultImg.value, "resultImg.value");});ElMessage.success("保存成功!");
};const dataURLtoBlob = (dataurl) => {const arr = dataurl.split(",");const mime = arr[0].match(/:(.*?);/)[1];const bstr = atob(arr[1]);let n = bstr.length;const u8arr = new Uint8Array(n);while (n--) {u8arr[n] = bstr.charCodeAt(n);}return new Blob([u8arr], { type: mime });
};const blobToFile = (theBlob, fileName) => {theBlob.lastModifiedDate = new Date();theBlob.name = fileName;return theBlob;
};const padZero = (num) => {return num < 10 ? "0" + num : num;
};
</script><style scoped lang="scss">
.container {display: flex;align-items: flex-start;justify-content: flex-start;.output-canvas {border: 1px solid #ddd;}img {width: 50px;height: 50px;margin: 1px;}.input-canvas {border-radius: 5px;border: 1px dashed #dddee1;}.dialog-footer {display: flex;justify-content: space-between;margin-bottom: 10px;}.tipCn {div:nth-child(1) {color: #ff6f77;font-size: 12px;}div:nth-child(2) {background-color: #ecf5ff;padding: 0px 10px;border-radius: 4px;color: #3c9cff;font-size: 14px;text-align: left;}}
}
.Signature {width: 500px;height: 150px;margin-top: 10px;border: 1px solid #dddee1;}
</style>

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/14325.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/14325.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/14325.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

小紅書-社區搜索部 (NLP、CV算法實習生) 一面面經

&#x1f604; 整個流程按如下問題展開&#xff0c;用時60min左右面試官人挺好&#xff0c;前半部分問問題&#xff0c;后半部分coding一道題。 各位有什么問題可以直接評論區留言&#xff0c;24小時內必回信息&#xff0c;放心~ 文章目錄 1、自我介紹2、介紹下項目&#xff…

金額計算導致的錯誤問題匯總解決

在日常的開發中&#xff0c;前端計算金額是非常常見&#xff0c;如果不夠仔細&#xff0c;考慮不夠周全的話&#xff0c;很容易犯錯的&#xff0c;金額這個東西一但錯了是很嚴重的&#xff0c;因此總結一些常見的錯誤&#xff1a; 1.最重要的&#xff0c;涉及到計算的參數一定要…

關于做事方式的小討論

大家好&#xff0c;我是阿趙。 ??之前五一勞動節期間&#xff0c;看到了這么一個新聞&#xff1a;某動物園內部收費項目五一期間漲價&#xff0c;喂長頸鹿的樹葉&#xff0c;一枝需要30元。然后新聞下面的評論就炸鍋了&#xff0c;紛紛的指責動物園太黑心&#xff0c;一枝樹葉…

淄博公司商標駁回復審條件及流程

商標是人工審查的&#xff0c;所以不同的人會有不同的想法和意見&#xff0c;導致同一案件的審查結果不同。特別是商標審查周期縮短到5個月&#xff0c;全國平均每個工作日有1萬多個商標提交申請&#xff0c;而全國只有一個商標審查單位——國家商標局提交申請。這種情況下&…

從入門到精通:掌握Scrapy框架的關鍵技巧

在當今信息爆炸的時代&#xff0c;獲取并利用網絡數據成為了許多行業的核心競爭力之一。而作為一名數據分析師、網絡研究者或者是信息工作者&#xff0c;要想獲取網絡上的大量數據&#xff0c;離不開網絡爬蟲工具的幫助。而Scrapy框架作為Python語言中最為強大的網絡爬蟲框架之…

ubuntu當前登錄用戶IP驗證

設置一個白名單列表檢查到登錄用戶IP信息不在白名單&#xff0c;發送信息到指定郵箱 #!/bin/bash# 定義常用IP地址列表文件 KNOWN_IP_FILE"/path/to/known_ips.txt" # 替換為實際路徑# 定義郵件接收者 EMAIL_TO"test163.com"# 定義日志文件 LOG_FILE&quo…

2024-5-23

今日安排&#xff1a; 繼續審計 nf_tables 源碼 && iptables 相關學習?????復現 CTF 相關題目????mount 的使用&#xff0c;學習 namespace (昨昨昨昨昨昨昨昨昨昨昨昨昨天殘留的任務)&#xff08;&#xff1a;看我能擱到什么時候???靜不下心學習新知識就…

qmt量化交易策略小白學習筆記第11期【qmt編程之獲取股票訂單流數據--原生Python】

qmt編程之獲取股票訂單流數據 qmt更加詳細的教程方法&#xff0c;會持續慢慢梳理。 也可找尋博主的歷史文章&#xff0c;搜索關鍵詞查看解決方案 &#xff01; 感謝關注&#xff0c;需免費開通量化回測與咨詢實盤權限&#xff0c;可以和博主聯系&#xff01; 獲取股票訂單流…

Java版工程行業管理系統-提升工程項目的綜合管理能力

工程項目管理涉及眾多環節和角色&#xff0c;如何實現高效協同和信息共享是關鍵。本文將介紹一個采用先進技術框架的Java版工程項目管理系統&#xff0c;該系統支持前后端分離&#xff0c;功能全面&#xff0c;可滿足不同角色的需求。從項目進度圖表到施工地圖&#xff0c;再到…

Java泛型類和方法聲明

泛型方法 protected <E> TableDataInfo<E> getDataTable(List<E> list){TableDataInfo<E> rspData new TableDataInfo();rspData.setCode(HttpStatus.SUCCESS);rspData.setMsg("查詢成功");rspData.setRows(list);rspData.setTotal(new Pag…

C++_vector操作使用

文章目錄 &#x1f680;1.1 vector介紹&#x1f680;1.2 vector的初始化&#x1f680;1.3 vector的常用內置函數&#x1f680;1.4 vector的遍歷 &#x1f680;1.1 vector介紹 vector是表示可變大小數組的序列容器。就像數組一樣&#xff0c;vector也采用的連續存儲空間來存儲元…

MySQL主從復制(docker搭建)

文章目錄 1.MySQL主從復制配置1.主服務器配置1.拉取mysql5.7的鏡像2.啟動一個主mysql&#xff0c;進行端口映射和目錄掛載3.進入/mysql5.7/mysql-master/conf中創建my.cnf并寫入主mysql配置1.進入目錄2.執行命令寫入配置 4.重啟mysql容器&#xff0c;使配置生效5.進入主mysql&a…

python篇-pywinauto使用-持續更新

1- pywinauto 中的uia是什么意思&#xff1f; 在pywinauto庫中&#xff0c;uia指的是UI Automation&#xff0c;這是Windows操作系統提供的一種技術框架&#xff0c;用于實現用戶界面(UI)的自動化測試和輔助功能訪問。UI Automation是微軟從Windows Vista開始引入的核心技術&am…

2024年電工杯高校數學建模競賽(B題) 建模解析| 大學生平衡膳食食譜的優化設計 |小鹿學長帶隊指引全代碼文章與思路

我是鹿鹿學長&#xff0c;就讀于上海交通大學&#xff0c;截至目前已經幫200人完成了建模與思路的構建的處理了&#xff5e; 本篇文章是鹿鹿學長經過深度思考&#xff0c;獨辟蹊徑&#xff0c;實現綜合建模。獨創復雜系統視角&#xff0c;幫助你解決電工杯的難關呀。 本題&…

面試八股之MySQL篇5——主從同步原理篇

&#x1f308;hello&#xff0c;你好鴨&#xff0c;我是Ethan&#xff0c;一名不斷學習的碼農&#xff0c;很高興你能來閱讀。 ??目前博客主要更新Java系列、項目案例、計算機必學四件套等。 &#x1f3c3;人生之義&#xff0c;在于追求&#xff0c;不在成敗&#xff0c;勤通…

IP地址的風險畫像及其應用

在現代互聯網環境中&#xff0c;IP地址不僅是設備在網絡中的唯一標識符&#xff0c;還是分析網絡安全和風險管理的重要工具。IP地址的風險畫像通過分析IP地址的行為和相關數據&#xff0c;揭示潛在的安全威脅&#xff0c;為企業和組織提供有效的風險管理方案。本文將探討IP地址…

齊業成工程行業數字化預算費控方案:編制、執行、數據分析全過程閉環管理

工程建設企業具備項目周期長、業務復雜的特點&#xff0c;預算費控涉及內部管理、項目、客戶、收支等&#xff0c;賬目多、且難控。 在工程企業日常預算費控過程中存在著諸多挑戰&#xff1a; ? 數據核對難&#xff1a;涉及數據多&#xff0c;需多部門協同填寫&#xff0c;需…

人工智能的陰暗面:犯罪分子如何利用 AI 進行欺詐

在當今數字化時代&#xff0c;人工智能&#xff08;AI&#xff09;正迅速成為推動各行各業生產力和創新的關鍵力量&#xff0c;而一些不法分子也開始探索如何將這些先進的工具用于他們自己的非法目的。從網絡釣魚到深度偽造&#xff0c;再到人肉搜索、越獄服務和身份驗證系統的…

【動態維護樹的直徑】【HBCPC2023】I. Colorful Tree

題目 https://codeforces.com/gym/105139/problem/I 思路 其實相當于是分別求黑色點和白色點所構成的樹的直徑。 當兩個連通塊連在了一起&#xff0c;假設它們的直徑是 ( u 1 , v 1 ) &#xff0c; ( u 2 , v 2 ) (u_1,v_1)&#xff0c;(u_2,v_2) (u1?,v1?)&#xff0c;(u…

【程序填空】三維點坐標平移(增量運算符重載)

題目描述 定義一個三維點Point類&#xff0c;利用友元函數重載""和"--"運算符&#xff0c;并區分這兩種運算符的前置和后置運算。 表示x\y\z坐標都1&#xff0c;--表示x\y\z坐標都-1 請完成以下程序填空 輸入 只有一行輸入&#xff0c;輸入三個整數&a…