news 2026/10/6 10:05:52

.nvue页面实现画笔绘制功能,用原生html导入nvue页面使用还可以截图(画笔 清空 橡皮擦 改颜色 禁用画笔 截图-是视频画面加绘制合成一张图片截图)-我花80块钱找淘宝都没弄出来,自己写的

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
.nvue页面实现画笔绘制功能,用原生html导入nvue页面使用还可以截图(画笔 清空 橡皮擦 改颜色 禁用画笔 截图-是视频画面加绘制合成一张图片截图)-我花80块钱找淘宝都没弄出来,自己写的

功能 安卓app上面nvue 视频上方绘制(vue2)

①新建一个draw.html文件(里面功能有画笔 清空 橡皮擦 改颜色 禁用画笔 截图-是视频画面加绘制合成一张图片截图)

②

webViewUrl: '/static/draw.html',

③

<!-- 画布 绘制层 --> <web-view v-if="true" ref="drawWebview" :src="webViewUrl" class="draw-layer" ></web-view>

④.nvue页面方法html我吧方法挂在Windows上面的直接绑定ref使用
// 切换颜色 setColor(color) { this.huahuaAction=''; this.pen.color = color const wv = this.$refs.drawWebview; if (wv) wv.evalJS(`setColorFunc('${color}')`); }, // 切换橡皮擦 useEraser(val) { this.huahuaAction=val; const wv = this.$refs.drawWebview; if (wv && wv.evalJS) wv.evalJS('setEraserFunc()'); }, // 切换画笔 usePen() { const wv = this.$refs.drawWebview; if (wv && wv.evalJS) wv.evalJS('setPenFunc()'); }, // 清空画布 clearCanvas(val) { this.huahuaAction=val; const wv = this.$refs.drawWebview; if (wv && wv.evalJS) wv.evalJS('clearCanvasFunc()'); }, // 撤销 undo() { this.huahuaAction=''; // 撤销 const wv = this.$refs.drawWebview; if (wv && wv.evalJS) wv.evalJS('undoFunc()'); }, // 重做 redo() { this.huahuaAction=''; const wv = this.$refs.drawWebview; if (wv && wv.evalJS) wv.evalJS('redoFunc()'); }, // 画笔 handleHuabi() { this.huahuaAction=''; this.showDraw = true this.usePen() this.$refs.drawWebview.evalJS('enableDrawFunc()') //this.showDraw = !this.showDraw },
⑥关键的draw.html原生绘制代码如下
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>视频截图 + 涂鸦合成</title> <style> html, body { margin: 0; padding: 0; height: 100%; background: transparent; overflow: hidden; } #canvas { display: block; touch-action: none; background-color: transparent; } </style> </head> <body> <canvas id="canvas"></canvas> <script> /* ================= 基础初始化 ================= */ const canvas = document.getElementById("canvas"); const ctx = canvas.getContext("2d"); let drawEnabled = true; function resizeCanvas() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; } window.addEventListener("resize", resizeCanvas); resizeCanvas(); ctx.lineCap = "round"; ctx.lineJoin = "round"; /* ================= 状态变量 ================= */ let drawing = false; let lastX = 0; let lastY = 0; let strokeColor = "#ff0000"; let penWidth = 2; let strokeWidth = penWidth; let mode = "draw"; // draw | erase let currentStroke = null; const history = []; const redoStack = []; /* ================= 绘制核心 ================= */ function drawLine(x1, y1, x2, y2, stroke) { ctx.save(); if (stroke.mode === "erase") { ctx.globalCompositeOperation = "destination-out"; ctx.lineWidth = stroke.width * 2; } else { ctx.globalCompositeOperation = "source-over"; ctx.strokeStyle = stroke.color; ctx.lineWidth = stroke.width; } ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke(); ctx.restore(); } function redrawAll() { ctx.clearRect(0, 0, canvas.width, canvas.height); history.forEach((stroke) => { for (let i = 1; i < stroke.points.length; i++) { const p1 = stroke.points[i - 1]; const p2 = stroke.points[i]; drawLine(p1.x, p1.y, p2.x, p2.y, stroke); } }); } /* ================= 手势事件 ================= */ canvas.addEventListener("touchstart", (e) => { if (!drawEnabled) return; const t = e.touches[0]; drawing = true; lastX = t.clientX; lastY = t.clientY; currentStroke = { mode, color: strokeColor, width: strokeWidth, points: [{ x: lastX, y: lastY }], }; }); canvas.addEventListener("touchmove", (e) => { if (!drawing) return; const t = e.touches[0]; const x = t.clientX; const y = t.clientY; drawLine(lastX, lastY, x, y, currentStroke); currentStroke.points.push({ x, y }); lastX = x; lastY = y; }); canvas.addEventListener("touchend", () => { if (!drawing) return; drawing = false; if (currentStroke && currentStroke.points.length > 1) { history.push(currentStroke); redoStack.length = 0; } currentStroke = null; }); /* ================= 视频截图 + 涂鸦合成 ================= */ window.mergeWithVideo = function (videoSrc) { alert("收到路径:" + videoSrc); console.log("HTML: 收到视频截图路径", videoSrc); if (!videoSrc.startsWith("file://")) { videoSrc = "file://" + videoSrc; } const img = new Image(); img.onload = function () { try { ctx.clearRect(0, 0, canvas.width, canvas.height); // 1. 绘制视频截图 ctx.drawImage(img, 0, 0, canvas.width, canvas.height); // 2. 重绘历史涂鸦 history.forEach((stroke) => { for (let i = 1; i < stroke.points.length; i++) { const p1 = stroke.points[i - 1]; const p2 = stroke.points[i]; drawLine(p1.x, p1.y, p2.x, p2.y, stroke); } }); // 3. 导出 exportCanvas(); } catch (e) { alert("合成失败:" + e.message); } }; img.onerror = function () { alert("图片加载失败"); }; img.src = videoSrc; }; /* ================= 导出画布 ================= */ // 将 base64 转换为 File/Blob 对象,以便使用 fetch 上传 function base64ToFileObject(base64Data, fileName = "temp_image.png") { // 移除base64前缀 const base64WithoutPrefix = base64Data.replace( /^data:image\/\w+;base64,/, "" ); const fileType = base64Data.split("/")[1].split(";")[0] || "png"; // 将base64解码为二进制数据 const binaryString = atob(base64WithoutPrefix); const bytes = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) { bytes[i] = binaryString.charCodeAt(i); } // 创建 Blob 对象 const blob = new Blob([bytes], { type: `image/${fileType}` }); // 如果支持 File 构造函数,创建 File 对象 if (typeof File !== "undefined") { return new File([blob], `${fileName}_${Date.now()}.${fileType}`, { type: `image/${fileType}`, }); } return blob; } // 使用 fetch 上传 File/Blob 对象 async function uploadFileUsingFetch( fileObject, serverUrl = "http://47.666.666.234:8080/photo/upload" ) { const formData = new FormData(); formData.append( "file", fileObject, fileObject.name || "canvas_screenshot.png" ); try { const response = await fetch(serverUrl, { method: "POST", body: formData, }); if (response.ok) { const result = await response.json(); console.log("上传成功:", result); return result; } else { throw new Error(`上传失败,状态码: ${response.status}`); } } catch (error) { console.error("上传过程中发生错误:", error); throw error; } } // 使用方法 把视频截图的图片路径通过nvue 传进来就能把历史绘制上去然后合成一张file传给接口 async function exportCanvas() { try { const base64 = canvas.toDataURL("image/png"); const fileObject = base64ToFileObject(base64, "canvas_screenshot"); await uploadFileUsingFetch(fileObject); } catch (e) { alert("导出失败:" + e.message); } } /* ================= nvue 可调用接口 ================= */ window.clearCanvasFunc = function () { history.length = 0; redoStack.length = 0; redrawAll(); }; window.setColorFunc = function (color) { strokeColor = color; mode = "draw"; strokeWidth = penWidth; }; window.setEraserFunc = function () { mode = "erase"; strokeWidth = penWidth * 10; }; window.setPenFunc = function () { mode = "draw"; strokeWidth = penWidth; }; window.undoFunc = function () { if (!history.length) return; redoStack.push(history.pop()); redrawAll(); }; window.redoFunc = function () { if (!redoStack.length) return; history.push(redoStack.pop()); redrawAll(); }; window.enableDrawFunc = function () { drawEnabled = true; }; window.disableDrawFunc = function () { drawEnabled = false; drawing = false; currentStroke = null; }; window.exportCanvas = exportCanvas; </script> </body> </html>
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/5 16:55:17

HBase与Quarkus:Kubernetes原生Java

《HBase + Quarkus 实战:构建Kubernetes原生Java应用的最佳实践》 一、引言:传统Java与云原生的“矛盾”,如何破解? 作为Java开发者,你是否遇到过这样的痛点: 写了一个连接HBase的Java应用,本地运行没问题,但部署到Kubernetes后,启动要等好几秒,内存占用高达500MB+…

作者头像 李华
网站建设 2026/10/5 23:30:38

nodejs基于vue网上订餐系统的设计与实现_i23b2

文章目录基于 Node.js 和 Vue 的网上订餐系统设计摘要技术栈与核心功能系统亮点性能与扩展性--nodejs技术栈--结论源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;基于 Node.js 和 Vue 的网上订餐系统设计摘要 该系统采用前后端分离架构…

作者头像 李华
网站建设 2026/10/4 11:29:18

掌握技巧:彻底消除错误代码0x80004005带来的困扰

在数字时代&#xff0c;几乎每个人都会遇到计算机错误&#xff0c;这些错误有时会阻碍我们的工作&#xff0c;甚至影响日常生活。在众多令人困惑的错误代码中&#xff0c;0x80004005是一个特别常见但通常又难以理解的错误。这个错误代码通常表示未指定的错误&#xff0c;它可能…

作者头像 李华
网站建设 2026/10/4 11:29:17

nodejs基于vue电子产品商城销售网站的设计与实现

文章目录设计目标技术架构核心功能性能优化测试与部署--nodejs技术栈--结论源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;设计目标 该系统旨在构建一个基于Node.js和Vue.js的电子产品商城&#xff0c;实现用户注册登录、商品展示、购…

作者头像 李华
网站建设 2026/10/6 21:32:19

APS概念-可承诺量 / 承诺能力拉动容差

一、核心概念解读可承诺量 / 承诺能力拉动容差是 APS 系统在计算 ATP&#xff08;可承诺量&#xff09;和 CTP&#xff08;承诺能力&#xff09;时的关键参数&#xff0c;它定义了系统在寻找最优交付日期时的时间搜索范围&#xff0c;直接影响计划的精准性和计算效率。二、关键…

作者头像 李华
网站建设 2026/10/6 21:32:25

问题记录与反思

一、问题复盘 正式版图片不显示问题&#xff1a;小程序开发版、体验版中图片展示正常&#xff0c;但发布至正式版后图片完全不显示。经排查&#xff0c;核心原因是后台返回的图片 src 为 //xxx.png 格式&#xff08;缺失 HTTP/HTTPS 协议&#xff09;&#xff0c;测试环境对协…

作者头像 李华