news 2026/5/1 8:05:09

.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/4/23 15:00:19

小学生的题:求桌子多高

设桌子的高度为 h 厘米&#xff0c;坐着的猫高度为 a 厘米&#xff0c;趴着的猫高度为 b 厘米。根据图中的信息&#xff0c;我们可以得到两个方程&#xff1a;ha−bhb−a​11&#xff08;左图&#xff1a;桌子高度 坐猫高度 - 趴猫高度 11&#xff09;7&#xff08;右图&…

作者头像 李华
网站建设 2026/4/28 19:52:35

nodejs基于vue的数据库课程知识点在线教学网站系统_70teu

文章目录系统概述技术架构核心功能实现示例扩展性--nodejs技术栈--结论源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;系统概述 Node.js与Vue.js结合的在线教学网站系统&#xff0c;专为数据库课程设计&#xff0c;提供知识点学习、交…

作者头像 李华
网站建设 2026/5/1 0:23:21

YOLOv8目标检测:从理论到实战的飞跃之旅

目录一、YOLOv8&#xff0c;目标检测的新宠儿二、YOLOv8 核心探秘2.1 架构解析2.2 工作流程2.3 优势剖析三、实战项目开启3.1 环境搭建3.2 数据准备3.3 模型训练3.4 模型评估3.5 推理应用四、实战案例展示4.1 工业质检实例4.2 智能安防应用五、常见问题与解决方案5.1 显存不足5…

作者头像 李华
网站建设 2026/5/1 9:35:52

详解redis(8):数据结构Hash

一、Hash 是什么&#xff1f; 逻辑层面 key user:1 value {name: "xiaolin",age: 18,city: "beijing" }也就是&#xff1a; HSET user:1 name xiaolin age 18 city beijingHash vs String 用 String 存对象 SET user:1 {"name":"xia…

作者头像 李华
网站建设 2026/5/1 23:09:07

详解redis(9):数据结构set

一、Redis Set 是什么Set 不重复 无顺序的集合一个 自动去重、不关心顺序 的容器二、Set 和 List 的本质区别对比项ListSet是否允许重复✅ 允许❌ 不允许是否有顺序✅ 有顺序&#xff08;下标&#xff09;❌ 无序能否按下标访问✅ LINDEX❌ 不支持是否支持集合运算❌✅ 交集 …

作者头像 李华