Javascript实现的网页版绘图板

项目简介

这是一个基于HTML5 Canvas和jQuery实现的简单网页版绘图编辑器。提供了基础的图片编辑功能,包括画笔工具、橡皮擦、亮度/对比度调节等功能。可以用于简单的图片编辑和绘图需求。

主要功能

1. 基础绘图工具

  • 画笔工具:支持自定义颜色和大小
  • 橡皮擦工具:支持自定义大小
  • 撤销/重做:支持多步操作历史记录

2. 图片处理

  • 图片上传:支持上传本地图片
  • 图片调整:支持亮度和对比度调节
  • 图片保存:可将编辑后的图片保存到本地

技术实现要点

1. 画布初始化

const canvas = $('#imageCanvas')[0];
const ctx = canvas.getContext('2d');

使用HTML5 Canvas作为绘图基础,默认设置白色背景。

2. 绘图实现

绘图功能通过监听鼠标事件实现:

  • mousedown:开始绘制
  • mousemove:持续绘制
  • mouseup/mouseleave:结束绘制

关键代码:

function startDrawing(e) {isDrawing = true;ctx.beginPath();if (currentTool === 'eraser') {ctx.save();ctx.globalCompositeOperation = 'destination-out';} else {ctx.globalCompositeOperation = 'source-over';}ctx.moveTo(e.offsetX, e.offsetY);
}

3. 橡皮擦实现

橡皮擦通过设置 Canvas 的 globalCompositeOperation 属性为 ‘destination-out’ 来实现透明擦除效果:

if (currentTool === 'eraser') {ctx.save();ctx.globalCompositeOperation = 'destination-out';
}

4. 图片处理

图片上传后会自动调整大小以适应画布,保持原始比例:

const scale = Math.min(canvas.width / img.width, canvas.height / img.height);
const x = (canvas.width - img.width * scale) / 2;
const y = (canvas.height - img.height * scale) / 2;

5. 亮度和对比度调节

使用 Canvas 的 filter 属性实现:

ctx.filter = `brightness(${brightness}%) contrast(${contrast}%)`;

6. 撤销/重做功能

通过保存画布状态实现:

function saveState() {undoStack.push(canvas.toDataURL());redoStack = [];updateUndoRedoButtons();
}

使用说明

  1. 上传图片:点击"上传图片"按钮选择本地图片
  2. 基础编辑
    • 使用画笔工具进行绘制
    • 使用橡皮擦工具擦除内容
    • 调节亮度和对比度滑块修改图片效果
  3. 撤销/重做:使用对应按钮进行操作历史管理
  4. 保存图片:点击"保存"按钮将结果保存为PNG格式

技术依赖

  • HTML5 Canvas
  • jQuery 3.7.1
  • 现代浏览器(支持ES6+)

改进方向

1. 功能扩展

  • 添加图片旋转/翻转功能
  • 实现图片裁剪功能
  • 添加更多滤镜效果
  • 实现图层功能

2. 用户体验优化

  • 添加快捷键支持
  • 优化工具切换交互
  • 添加操作提示
  • 完善错误处理

3. 性能优化

  • 优化大图片处理
  • 改进撤销/重做机制
  • 添加操作状态缓存
  • 优化Canvas渲染性能

注意事项

  1. 图片上传大小没有限制,但建议不要超过5MB
  2. 保存的图片格式为PNG,支持透明度
  3. 浏览器需要支持Canvas的相关API

完整代码

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>网页版图片编辑器</title><!-- 引入 jQuery --><script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script><style>body {font-family: Arial, sans-serif;margin: 20px;background-color: #f0f0f0;}.container {max-width: 1200px;margin: 0 auto;}.toolbar {background: white;padding: 15px;border-radius: 8px;box-shadow: 0 2px 4px rgba(0,0,0,0.1);margin-bottom: 20px;}.toolbar button {padding: 8px 15px;margin: 0 5px;border: 1px solid #ddd;border-radius: 4px;background: #fff;cursor: pointer;transition: all 0.3s ease;}.toolbar button:hover {background: #f5f5f5;}.toolbar button.active {background: #e0e0e0;}.canvas-container {position: relative;margin: 20px 0;background: white;padding: 20px;border-radius: 8px;box-shadow: 0 2px 4px rgba(0,0,0,0.1);}#imageCanvas {border: 1px solid #ddd;margin: 0 auto;display: block;}.controls {background: white;padding: 15px;border-radius: 8px;box-shadow: 0 2px 4px rgba(0,0,0,0.1);margin-top: 20px;}.control-group {margin: 10px 0;}.control-group label {display: block;margin-bottom: 5px;}input[type="range"] {width: 200px;}input[type="color"] {width: 50px;height: 30px;padding: 0;border: none;}.hidden {display: none;}</style>
</head>
<body><div class="container"><div class="toolbar"><input type="file" id="imageInput" class="hidden" accept="image/*"><button id="uploadBtn">上传图片</button><button id="undoBtn" disabled>撤销</button><button id="redoBtn" disabled>重做</button><button id="saveBtn" disabled>保存</button><button id="brushBtn">画笔</button><button id="eraserBtn">橡皮擦</button></div><div class="canvas-container"><canvas id="imageCanvas" width="800" height="600"></canvas></div><div class="controls"><div class="control-group"><label for="brightnessRange">亮度:</label><input type="range" id="brightnessRange" min="0" max="200" value="100"><span id="brightnessValue">100%</span></div><div class="control-group"><label for="contrastRange">对比度:</label><input type="range" id="contrastRange" min="0" max="200" value="100"><span id="contrastValue">100%</span></div><div class="control-group"><label for="brushSize">画笔大小:</label><input type="range" id="brushSize" min="1" max="50" value="5"><span id="brushSizeValue">5px</span></div><div class="control-group"><label for="brushColor">画笔颜色:</label><input type="color" id="brushColor" value="#000000"></div></div></div><script>$(document).ready(function() {const canvas = $('#imageCanvas')[0];const ctx = canvas.getContext('2d');let isDrawing = false;let currentTool = 'brush';let originalImage = null;let undoStack = [];let redoStack = [];// 初始化画布,设置白色背景ctx.fillStyle = 'white';ctx.fillRect(0, 0, canvas.width, canvas.height);saveState();// 工具选择$('#brushBtn').click(function() {currentTool = 'brush';$(this).addClass('active');$('#eraserBtn').removeClass('active');});$('#eraserBtn').click(function() {currentTool = 'eraser';$(this).addClass('active');$('#brushBtn').removeClass('active');});// 绘图功能function startDrawing(e) {isDrawing = true;ctx.beginPath();if (currentTool === 'eraser') {ctx.save();ctx.globalCompositeOperation = 'destination-out';} else {ctx.globalCompositeOperation = 'source-over';}ctx.moveTo(e.offsetX, e.offsetY);}function draw(e) {if (!isDrawing) return;if (currentTool === 'eraser') {ctx.save();ctx.globalCompositeOperation = 'destination-out';} else {ctx.globalCompositeOperation = 'source-over';}ctx.lineTo(e.offsetX, e.offsetY);ctx.stroke();if (currentTool === 'eraser') {ctx.restore();}}function stopDrawing() {if (isDrawing) {isDrawing = false;ctx.closePath();if (currentTool === 'eraser') {ctx.restore();}saveState();}}$('#imageCanvas').mousedown(startDrawing).mousemove(draw).mouseup(stopDrawing).mouseleave(stopDrawing);// 画笔控制$('#brushColor').change(function() {ctx.strokeStyle = $(this).val();});$('#brushSize').on('input', function() {const size = $(this).val();ctx.lineWidth = size;$('#brushSizeValue').text(size + 'px');});// 图片上传处理$('#uploadBtn').click(function() {$('#imageInput').click();});$('#imageInput').change(function(e) {const file = e.target.files[0];if (file) {const reader = new FileReader();reader.onload = function(event) {const img = new Image();img.onload = function() {// 保持图片比例const scale = Math.min(canvas.width / img.width, canvas.height / img.height);const x = (canvas.width - img.width * scale) / 2;const y = (canvas.height - img.height * scale) / 2;ctx.clearRect(0, 0, canvas.width, canvas.height);ctx.drawImage(img, x, y, img.width * scale, img.height * scale);originalImage = img;saveState();$('#saveBtn').prop('disabled', false);};img.src = event.target.result;};reader.readAsDataURL(file);}});// 图片调整function applyAdjustments() {if (!originalImage) return;const brightness = $('#brightnessRange').val();const contrast = $('#contrastRange').val();// 计算保持宽高比的缩放const scale = Math.min(canvas.width / originalImage.width, canvas.height / originalImage.height);const x = (canvas.width - originalImage.width * scale) / 2;const y = (canvas.height - originalImage.height * scale) / 2;ctx.filter = `brightness(${brightness}%) contrast(${contrast}%)`;ctx.clearRect(0, 0, canvas.width, canvas.height);ctx.drawImage(originalImage, x, y, originalImage.width * scale, originalImage.height * scale);ctx.filter = 'none';saveState();}$('#brightnessRange, #contrastRange').on('input', function() {const value = $(this).val();const id = $(this).attr('id');$(`#${id}Value`).text(value + '%');applyAdjustments();});// 撤销/重做功能function saveState() {undoStack.push(canvas.toDataURL());redoStack = [];updateUndoRedoButtons();}function updateUndoRedoButtons() {$('#undoBtn').prop('disabled', undoStack.length <= 1);$('#redoBtn').prop('disabled', redoStack.length === 0);}$('#undoBtn').click(function() {if (undoStack.length <= 1) return;redoStack.push(undoStack.pop());const lastState = undoStack[undoStack.length - 1];loadState(lastState);updateUndoRedoButtons();});$('#redoBtn').click(function() {if (redoStack.length === 0) return;const nextState = redoStack.pop();undoStack.push(nextState);loadState(nextState);updateUndoRedoButtons();});function loadState(state) {const img = new Image();img.onload = function() {ctx.clearRect(0, 0, canvas.width, canvas.height);ctx.drawImage(img, 0, 0);};img.src = state;}// 保存功能$('#saveBtn').click(function() {const link = document.createElement('a');link.download = '编辑后的图片.png';link.href = canvas.toDataURL();link.click();});// 设置初始画笔属性ctx.strokeStyle = $('#brushColor').val();ctx.lineWidth = $('#brushSize').val();ctx.lineCap = 'round';ctx.lineJoin = 'round';});</script>
</body>
</html>

本项目仅用于学习和参考。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/460217.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【界面改版】JimuReport 积木报表 v1.9.0 版本发布,填报能力和大屏能力

项目介绍 积木报表JimuReport&#xff0c;是一款免费的数据可视化报表&#xff0c;含报表、仪表盘和大屏设计&#xff0c;像搭建积木一样完全在线设计&#xff01;功能涵盖&#xff1a;数据报表、打印设计、图表报表、门户设计、大屏设计等&#xff01; Web版报表设计器&#x…

【网络】1.UDP通信

UDP通信 1 server1.1 server建立的步骤1.2 运行server 2 client2.1 client的建立步骤2.2 运行client 3 总结3.1 server3.2 client 1 server server的启动方式是&#xff1a;./udpserver 8080 --> 格式就是./proc port端口 port端口自己指定 1.1 server建立的步骤 获取文件描…

告别冰冷机器声:GLM-4-Voice开启情感语音交互新时代!

目录 引言一、GLM-4-Voice概述二、GLM-4-Voice的架构三、GLM-4-Voice的主要功能四、GLM-4-Voice的技术原理五、GLM-4-Voice的应用场景六、GLM-4-Voice体验快速开始结语 引言 在人工智能的不断进步中&#xff0c;语音交互技术正逐渐成为人机沟通的重要桥梁。它不仅极大地提升了…

MySQL定时异机备份

场景&#xff1a;将A机器MySQL数据库部分表每日定时备份到B机器上 &#xff08;只适用于Linux&#xff09; 实现方式算是比简单了&#xff0c;就是用mysqldump生成文件&#xff0c;使用scp命令传输到另一台机器上。 1. 编写备份shell脚本 在A机器新建脚本 (当然没有vim的话vi…

使用VS2019将C#代码生成DLL文件在Unity3D里面使用(一)

系列文章目录 untiy知识点 文章目录 系列文章目录&#x1f449;前言&#x1f449;一、首先你要先有VS&#x1f449;二、引用UnityAPI使用步骤&#x1f449;2-1.引用unitydll文件到项目里面&#x1f449;2-2.导入Dll文件 &#x1f449;三、编辑dll代码&#x1f449;四、导出dll…

平台化运营公司如何在创业市场招商

在当今商业环境中&#xff0c;平台化运营的公司正成为推动经济发展的重要力量。对于这类公司而言&#xff0c;在创业市场招商意义重大。 平台化运营公司具有独特特点&#xff1a;通过搭建开放共享平台连接供需双方&#xff0c;实现资源优化配置与价值创造。比如电子商务平台、社…

聚类分析算法——K-means聚类 详解

K-means 聚类是一种常用的基于距离的聚类算法&#xff0c;旨在将数据集划分为 个簇。算法的目标是最小化簇内的点到簇中心的距离总和。下面&#xff0c;我们将从 K-means 的底层原理、算法步骤、数学基础、距离度量方法、参数选择、优缺点 和 源代码实现 等角度进行详细解析。…

SpringMVC执行流程(视图阶段JSP、前后端分离阶段)、面试题

目录 1.SpringMVC执行流程分为以下两种 2.非前后端分离的SpringMVC的执行流程 3.前后端分离的项目SpringMVC执行流程 4. 面试题 1.SpringMVC执行流程分为以下两种 2.非前后端分离的SpringMVC的执行流程 流程图&#xff1a; 更加生动的描述&#xff1a; DisPatcherServlet…

十分钟Linux中的epoll机制

epoll机制 epoll是Linux内核提供的一种高效I/O事件通知机制&#xff0c;用于处理大量文件描述符的I/O操作。它适合高并发场景&#xff0c;如网络服务器、实时数据处理等&#xff0c;是select和poll的高效替代方案。 1. epoll的工作原理 epoll通过内核中的事件通知接口和文件…

GRE Over IPsec(华三)

GRE Over IPsec 顾名思义&#xff0c;GRE在内&#xff0c;IPsec在外 那么当数据进入tunnel隧道后&#xff0c;会先被GRE封装后再进行IPsec感兴趣流acl匹配&#xff0c;匹配上了则封装IPsec&#xff0c;没匹配上则丢包 实验&#xff1a; 需求&#xff1a;总部pc能够通过gre o…

echarts属性之xAxis

xAxis 直角坐标系 grid 中的 x 轴&#xff0c;一般情况下单个 grid 组件最多只能放上下两个 x 轴&#xff0c;多于两个 x 轴需要通过配置 offset 属性防止同个位置多个 x 轴的重叠。 所有属性 xAxis. id string 组件 ID。默认不指定。指定则可用于在 option 或者 API 中引…

盘点:2024年最新热门项目管理平台TOP11

一、项目管理平台的重要性 在当今竞争激烈的商业环境中&#xff0c;项目管理平台已成为企业提高效率和团队协作的关键工具。这主要是因为现代商业项目日益复杂&#xff0c;涉及多个部门、众多资源以及不断变化的需求。 首先&#xff0c;项目管理平台能够提高工作效率。例如&a…

PHP数据类型

几种常用的数据类型&#xff1a; String&#xff08;字符串&#xff09; Integer&#xff08;整型&#xff09; Float&#xff08;浮点型&#xff09; Boolean&#xff08;布尔型&#xff09; NULL&#xff08;空值&#xff09; Array&#xff08;数组&#xff09; Obje…

【大数据】Flink + Kafka 实现通用流式数据处理详解

目录 一、前言 二、流式数据处理场景介绍 2.1 流式数据处理概述 2.1.1 流式数据处理场景介绍 2.2 流式数据处理技术栈 2.2.1 数据采集 2.2.2 数据处理 2.2.3 数据存储 2.2.4 数据展示 2.3 流式数据处理场景面临的问题和挑战 三、通用的流式数据处理场景解决方案 3.1…

精准测试在基金团队应用实践

以下为作者观点&#xff1a; 一、引言 精准测试是一套计算机测试辅助分析系统&#xff0c;精准测试的核心组件包含&#xff0c;软件覆盖率分析、用例和代码的双向追踪、智能回归测试用例选取、缺陷定位、测试用例聚类分析、测试用例自动生成系统&#xff0c;这些功能完整的构…

参与国家标准制定对企业发展有哪些好处?

1. 提升企业竞争力&#xff1a; • 技术优势凸显&#xff1a;参与标准制定的过程中&#xff0c;企业能将自身先进的技术和管理理念融入标准&#xff0c;这不仅是对企业技术实力的认可&#xff0c;也能使企业在行业中占据技术制高点。 • 质量优势强化&#xff1a;国家标准对产品…

滚柱导轨出现异常损坏的原因

滚柱导轨是一种精密的直线滚动导轨&#xff0c;具有较高的承载能力和较高的刚性&#xff0c;对反复动作、起动、停止往复运动频率较高情况下可减少整机重量和传动机构及动力成本。滚柱导轨可获得较高的灵敏度和高性能的平面直线运动&#xff0c;在重载或变载的情况下&#xff0…

开发了一个成人学位英语助考微信小程序

微信小程序名称&#xff1a;石榴英语 全称&#xff1a;石榴英语真题助手 功能定位 北京成人学士学位英语辅助学习工具&#xff0c;包含记高频单词&#xff0c;高频词组&#xff0c;专项练习&#xff0c;模拟考试等功能。 开发背景 个人工作需要提高学习英文水平&#xff…

基于Matlab 火焰识别技术

Matlab 火焰识别技术 课题介绍 森林承担着为人类提供氧气以及回收二氧化碳等废弃气体的作用&#xff0c;森林保护显得尤其重要。但是每年由于火灾引起的事故不计其数&#xff0c;造成重大的损失。如果有一款监测软件&#xff0c;从硬件处获得的图像中监测是否有火焰&#xff…

同声传译器什么好用?哪款是你的会议利器推荐榜?

眨眼之间&#xff0c;冬日的脚步悄然而至&#xff0c;又可以踏上前往东北的旅程&#xff0c;去欣赏那银装素裹的绝美雪景。 在这样一个充满异域风情和语言挑战的旅途中&#xff0c;一款顶尖的同声传译器软件无疑是旅行者的最佳伴侣。 它能帮助我们跨越语言的鸿沟&#xff0c;…