Canvas:实现在线画板操作

想象一下,用几行代码就能创造出如此逼真的图像和动画,仿佛将艺术与科技完美融合,前端开发的Canvas技术正是这个数字化时代中最具魔力的一环,它不仅仅是网页的一部分,更是一个无限创意的画布,一个让你的想象力自由驰骋的平台。

目录

基础页面搭建

绘画操作

按钮点击事件

最后总结


基础页面搭建

接下来借助canvas实现一个简易的画版操作,首先这里我们设置一下画布的一些基础布局样式:

<body>
    <canvas id="canvas" width="800" height="600" style="border: 1px solid #ccc;"></canvas>
    <hr>
    <button id="boldBtn" type="button">粗线条</button>
    <button id="thinBtn" type="button">细线条</button>
    <button id="saveBtn" type="button">保持签名</button>
    <input type="color" name="" id="color" value="">
    <button class="clearBtn">橡皮擦</button>
    <button id="nullBtn">清空画布</button>
</body>

实现的效果如下所示,一个比较简陋的画布,以及一些基础操作的按钮:

绘画操作

接下来我们开始实现绘画操作的功能,通过监听鼠标不同情况的事件来实现画布上书写内容的操作,首先这里我们先绘制画布以及获取对应的dom元素进行操作:

<script>
    // 获取canvas画布绘制上下文对象以及获取2d画笔对象
    let canvas = document.getElementById("canvas");      
    let ctx = canvas.getContext("2d");
    ctx.lineJoin = 'round'; // 线条连接处为圆角
    ctx.lineCap = 'round'; // 线条端点为圆角

    // 获取DOM元素
    let boldBtn = document.getElementById("boldBtn");
    let thinBtn = document.getElementById("thinBtn");
    let saveBtn = document.getElementById("saveBtn");
    let color = document.getElementById("color");
    let clearBtn = document.getElementsByClassName("clearBtn")[0];
    let nullBtn = document.getElementById("nullBtn");
</script>

接下来开始开始监听鼠标的按下、弹起、移动以及离开画布的四种情况的操作,完整代码如下:

<script>
    // 获取canvas画布绘制上下文对象以及获取2d画笔对象
    let canvas = document.getElementById("canvas");      
    let ctx = canvas.getContext("2d");
    ctx.lineJoin = 'round'; // 线条连接处为圆角
    ctx.lineCap = 'round'; // 线条端点为圆角

    // 获取DOM元素
    let boldBtn = document.getElementById("boldBtn");
    let thinBtn = document.getElementById("thinBtn");
    let saveBtn = document.getElementById("saveBtn");
    let color = document.getElementById("color");
    let clearBtn = document.getElementsByClassName("clearBtn")[0];
    let nullBtn = document.getElementById("nullBtn");

    // 设置是否开始绘画的变量
    let isDraw = false;

    // 鼠标按下去函数
    canvas.onmousedown = function(e) {
        isDraw = true; // 开始绘画
        ctx.beginPath(); // 开始路径
        // 获取鼠标点击的坐标
        let x = e.pageX - canvas.offsetLeft; 
        let y = e.pageY - canvas.offsetTop; 
        ctx.moveTo(x, y); // 移动到当前点
    }
    // 鼠标移动函数
    canvas.onmousemove = function(e) {
        if (isDraw) { // 如果开始绘画
            let x = e.pageX - canvas.offsetLeft; 
            let y = e.pageY - canvas.offsetTop; 
            ctx.lineTo(x, y); // 绘制
            ctx.stroke()
        }
    }
    // 鼠标弹起函数
    canvas.onmouseup = function(e) {
        isDraw = false; // 结束绘画
        ctx.closePath(); // 结束路径
    }
    // 鼠标离开画布函数
    canvas.onmouseleave = function(e) {
        isDraw = false; // 结束绘画
        ctx.closePath(); // 结束路径
    }
</script>

最终呈现的效果如下所示:

按钮点击事件

接下来给画布下方的按钮设置对应的点击事件,让其能够实现对应的功能,如下:

粗线条:这里设置点击事件函数如下,遮盖策略设置在现有画布上下文之上绘制新图形,点击之后宽度增加到20,并添加对应激活类名active,删除其他按钮可能存在的active类名:

// 粗线条函数
boldBtn.onclick = function(e) {
    ctx.globalCompositeOperation = 'source-over'; 
    ctx.lineWidth = 20
    boldBtn.classList.add("active");
    thinBtn.classList.remove("active");
    clearBtn.classList.remove("active");
}

细线条:这里设置点击事件函数如下,遮盖策略设置在现有画布上下文之上绘制新图形,点击之后宽度减少到2,并添加对应激活类名active,删除其他按钮可能存在的active类名:

// 细线条函数
thinBtn.onclick = function(e) {
    ctx.globalCompositeOperation = 'source-over'; 
    ctx.lineWidth = 2
    thinBtn.classList.add("active");
    boldBtn.classList.remove("active");
    clearBtn.classList.remove("active");
}

保存签名:获取图片数据并创建一个a标签进行文件下载:

// 保存签名函数
saveBtn.onclick = function(e) {
    let urlData = canvas.toDataURL(); // 获取图片数据
    // let img = new Image()
    // img.src = urlData; // 图片地址
    // document.body.appendChild(img); // 添加图片到body

    let downloadA = document.createElement("a"); // 创建a标签
    downloadA.setAttribute("download", "签名"); // 设置下载文件名
    downloadA.href = urlData; // 设置a标签的地址
    downloadA.click(); // 点击a标签
}

修改颜色:设置监听函数,动态修改canvas颜色:

// 颜色选择函数
color.onchange = function(e) {
    ctx.strokeStyle = color.value; // 设置线条颜色
}

橡皮擦:这里设置点击事件函数如下,遮盖策略设置现有内容保持在新图形不重叠的地方,点击之后区域呈现空白,并添加对应激活类名active,删除其他按钮可能存在的active类名:

// 橡皮擦函数
clearBtn.onclick = function(e) {
    // 清除画布
    ctx.globalCompositeOperation = 'destination-out';
    ctx.lineWidth = 30
    clearBtn.classList.add("active");
    boldBtn.classList.remove("active");
    thinBtn.classList.remove("active");
}

清空画布:调用canvas函数直接清除整个画布的内容:

// 清空画布函数
nullBtn.onclick = function(e) {
    ctx.clearRect(0, 0, 800, 600);
}

最终呈现的效果如下所示:

最后总结

本次代码实现了一个简单的画板功能,让用户可以在网页上绘制、调整线条粗细、选择颜色、保存签名等操作。以下是代码的实现思路:

1)HTML结构部分

在<body>中包含一个 <canvas> 元素,用于绘制图形。

一组按钮和一个颜色选择器,用于控制画笔的粗细、颜色以及清除和保存绘制的功能。

2)CSS部分

定义了一个按钮的样式 .active,用于表示当前选中状态,例如粗线条、细线条、橡皮擦等按钮会根据点击状态变化背景颜色。

3)JavaScript部分

《1》获取元素:

使用 document.getElementById 和 document.getElementsByClassName 获取 <canvas> 和各种控制按钮。

《2》绘图相关事件:

canvas.onmousedown:鼠标按下时开始绘画,记录起始点。
canvas.onmousemove:鼠标移动时,如果正在绘画(isDraw为真),则根据当前鼠标位置绘制线条。
canvas.onmouseup 和 canvas.onmouseleave:鼠标抬起或离开画布时结束绘画路径。

《3》功能按钮事件:

粗线条 (boldBtn) 和 细线条 (thinBtn):设置线条宽度,并通过 classList 控制按钮的选中状态。
保存签名 (saveBtn):使用 canvas.toDataURL() 获取绘制内容的DataURL,创建一个下载链接,允许用户保存签名。
颜色选择 (color):通过 color.onchange 设置绘制线条的颜色。
橡皮擦 (clearBtn):使用 ctx.globalCompositeOperation = 'destination-out' 实现擦除效果,清除画布上的内容。
清空画布 (nullBtn):使用 ctx.clearRect() 清除整个画布的内容。

《4》绘图上下文:

使用 getContext('2d') 获取 Canvas 2D 渲染上下文对象 ctx,设置线条的样式(圆角连接和端点)。

这段代码整合了 HTML、CSS 和 JavaScript,提供了一个简单而功能丰富的在线绘图工具,用户可以通过点击不同的按钮和操作来绘制、擦除、保存签名等。完整代码如下:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        button.active {
            color: #fff;
            background-color: orange;
        }
    </style>
</head>

<body>
    <canvas id="canvas" width="800" height="600" style="border: 1px solid #ccc;"></canvas>
    <hr>
    <button id="boldBtn" type="button">粗线条</button>
    <button id="thinBtn" type="button">细线条</button>
    <button id="saveBtn" type="button">保存签名</button>
    <input type="color" name="" id="color" value="">
    <button class="clearBtn">橡皮擦</button>
    <button id="nullBtn">清空画布</button>
<script>
    // 获取canvas画布绘制上下文对象以及获取2d画笔对象
    let canvas = document.getElementById("canvas");      
    let ctx = canvas.getContext("2d");
    ctx.lineJoin = 'round'; // 线条连接处为圆角
    ctx.lineCap = 'round'; // 线条端点为圆角

    // 获取DOM元素
    let boldBtn = document.getElementById("boldBtn");
    let thinBtn = document.getElementById("thinBtn");
    let saveBtn = document.getElementById("saveBtn");
    let color = document.getElementById("color");
    let clearBtn = document.getElementsByClassName("clearBtn")[0];
    let nullBtn = document.getElementById("nullBtn");

    // 设置是否开始绘画的变量
    let isDraw = false;

    // 鼠标按下去函数
    canvas.onmousedown = function(e) {
        isDraw = true; // 开始绘画
        ctx.beginPath(); // 开始路径
        // 获取鼠标点击的坐标
        let x = e.pageX - canvas.offsetLeft; 
        let y = e.pageY - canvas.offsetTop; 
        ctx.moveTo(x, y); // 移动到当前点
    }
    // 鼠标移动函数
    canvas.onmousemove = function(e) {
        if (isDraw) { // 如果开始绘画
            let x = e.pageX - canvas.offsetLeft; 
            let y = e.pageY - canvas.offsetTop; 
            ctx.lineTo(x, y); // 绘制
            ctx.stroke()
        }
    }
    // 鼠标弹起函数
    canvas.onmouseup = function(e) {
        isDraw = false; // 结束绘画
        ctx.closePath(); // 结束路径
    }
    // 鼠标离开画布函数
    canvas.onmouseleave = function(e) {
        isDraw = false; // 结束绘画
        ctx.closePath(); // 结束路径
    }

    // 粗线条函数
    boldBtn.onclick = function(e) {
        ctx.globalCompositeOperation = 'source-over'; 
        ctx.lineWidth = 20
        boldBtn.classList.add("active");
        thinBtn.classList.remove("active");
        clearBtn.classList.remove("active");
    }
    // 细线条函数
    thinBtn.onclick = function(e) {
        ctx.globalCompositeOperation = 'source-over'; 
        ctx.lineWidth = 2
        thinBtn.classList.add("active");
        boldBtn.classList.remove("active");
        clearBtn.classList.remove("active");
    }
    // 保存签名函数
    saveBtn.onclick = function(e) {
        let urlData = canvas.toDataURL(); // 获取图片数据
        // let img = new Image()
        // img.src = urlData; // 图片地址
        // document.body.appendChild(img); // 添加图片到body

        let downloadA = document.createElement("a"); // 创建a标签
        downloadA.setAttribute("download", "签名"); // 设置下载文件名
        downloadA.href = urlData; // 设置a标签的地址
        downloadA.click(); // 点击a标签
    }
    // 颜色选择函数
    color.onchange = function(e) {
        ctx.strokeStyle = color.value; // 设置线条颜色
    }
    // 橡皮擦函数
    clearBtn.onclick = function(e) {
        // 清除画布
        ctx.globalCompositeOperation = 'destination-out';
        ctx.lineWidth = 30
        clearBtn.classList.add("active");
        boldBtn.classList.remove("active");
        thinBtn.classList.remove("active");
    }
    // 清空画布函数
    nullBtn.onclick = function(e) {
        ctx.clearRect(0, 0, 800, 600);
    }
</script>
</body>
</html>

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

谷粒商城学习笔记-22-分布式组件-SpringCloud-OpenFeign测试远程调用

文章目录 一&#xff0c;OpenFeign的简介二&#xff0c;OpenFeign的使用步骤1&#xff0c;场景说明2&#xff0c;引入依赖2&#xff0c;开启OpenFeign3&#xff0c;编写Feign接口4&#xff0c;使用feign调用远程接口5&#xff0c;验证 错误记录 上一节学习了注册中心&#xff0…

Linux-shell编程入门基础

文章目录 前言Shell编程bash特性shell作用域变量环境变量$特殊变量$特殊状态变量 $特殊符号(很重要)其他内置shell命令shell语法的子串截取统计 指令执行时间练习shell特殊扩展变量父子shell的理解内置和外置命令区别 数值计算双括号(())运算letexprexpr模式匹配 bcawk中括号 s…

ts语法---泛型和泛型约束

泛型 泛型&#xff0c;动态类型&#xff0c;是一个初始化不明确的类型&#xff0c;类似于函数中的形参&#xff08;不明确参数值&#xff09;&#xff0c; 泛型一般用在function定义函数时动态约束类型&#xff0c;和type定义类型时动态约束类型&#xff0c; 泛型一般使用任…

Jenkins教程-18-常用插件-description-setter

上一小节我们学习了Jenkin常用插件Environment Injector的使用方法&#xff0c;本小节我们讲解一下Jenkin常用插件description-setter的使用方法。 在某些情况下&#xff0c;用户可能希望根据构建过程中的某些关键信息来自定义构建的描述&#xff0c;比如部署的用户信息、提交…

​李白一生的过往轨迹矢量地图

今天我们来看一下“天子呼来不上船&#xff0c;自称臣是酒中仙”大诗人李白过往轨迹&#xff0c;看看他一生都去过哪些地方&#xff1f; 我们将李白一生去过的地方搜集整理了一份矢量地图&#xff0c;有需要请在文末查看该数据的领取方法。 李白一生的过往轨迹 李白&#xf…

stm32按键设置闹钟数进退位不正常?如何解决

&#x1f3c6;本文收录于《CSDN问答解惑-专业版》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收…

JavaScript-日期对象

日期对象 作用&#xff1a;用来表示时间的对象 获取当前时间 const datenew Date();console.log(date);可以得到日期对象&#xff0c;里面的属性有星期&#xff0c;年月日&#xff0c;时分秒 获取指定时间 const datenew Date(2023-05-01);console.log(date); 获取时间戳 时间…

Deepspeed : AttributeError: ‘DummyOptim‘ object has no attribute ‘step‘

题意&#xff1a;尝试在一个名为 DummyOptim 的对象上调用 .step() 方法&#xff0c;但是这个对象并没有定义这个方法 问题背景&#xff1a; I want to use deepspeed for training LLMs along with Huggingface Trainer. But when I use deepspeed along with trainer I get …

实习记录3

1.Mybaits懒加载 MyBatis 延迟加载&#xff08;懒加载&#xff09;一篇入门-腾讯云开发者社区-腾讯云 (tencent.com) 2.高级映射 106-高级映射之多对一映射第一种方式_哔哩哔哩_bilibili 3.TableId(type IdType.INPUT) Mybatis-plus 主键生成策略_mybatis-plus 自增主键等于…

和鲸科技荣耀入选2024 H1 「中国最具价值 AGI 创新机构 TOP 50」

以下文章来源于Founder Park&#xff0c;作者Founder Par 大模型的盛宴&#xff0c;不应该只属于那些无数光环加身的算法天才们。 模型的冰山一角下&#xff0c;是应用层的暗流涌动&#xff0c;这是一个更庞大&#xff0c;也更隐秘的蓝海。但发掘这一切的前提是&#xff0c;所…

redis哨兵模式搭建

先搭建主从结构 当需要运行多个Redis实例时&#xff0c;可以通过为每个实例使用不同的配置文件的方式来实现。 复制redis目录下的redis.conf文件将其重命名为redis6380.conf和redis6381.conf&#xff0c;或者将其放到单独文件夹中&#xff0c;这里为了偷懒&#xff0c;简单实现…

使用 MinIO 赢得 RAG 权利

人们常说&#xff0c;在人工智能时代&#xff0c;数据是你的护城河。为此&#xff0c;构建生产级 RAG 应用程序需要合适的数据基础架构来存储、版本控制、处理、评估和查询构成专有语料库的数据块。由于 MinIO 采用数据优先的 AI 方法&#xff0c;因此对于此类项目&#xff0c;…

TotalSegmentator---针对CT/MRI数据的自动分割

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ&#xff1a;870202403 公众号&#xff1a;VTK忠粉 前言 本文分享开源软件TotalSegmentator&#xff0c;该开源项目针对CT/MRI数据类型&#xff0c;对主要的解剖学结构进行自动分割&#xff0c;并且被集成到MITK中。希望对各…

Global Mapper:地理信息的温柔探索

引言 在这纷繁复杂的世界里&#xff0c;地理信息系统&#xff08;GIS&#xff09;如同一把利器&#xff0c;帮助我们剖析、理解和改造这个世界。而在众多GIS软件中&#xff0c;Global Mapper无疑是其中的佼佼者。作为一款功能全面且易于使用的GIS应用程序&#xff0c;Global M…

springboot养老院管理系统-计算机毕业设计源码00010

摘要 本文介绍了一种基于Spring Boot框架的养老院管理系统的设计与实现。该系统旨在帮助养老院管理者更有效地管理机构内的各项事务&#xff0c;并提供更好的服务于老年人。系统的设计考虑了养老院管理的特殊需求&#xff0c;包括系统用户、老人信息管理、服务分类管理、医疗服…

谷歌个人开发者账号14天封测审核通过技巧,你还不知道吗?

众所周知&#xff0c;目前在Google play应用商店上架应用已经不是那么容易了&#xff0c;谷歌各种政策的更新以及审核系统的升级&#xff0c;给开发者们带来了不少挑战。 尤其针对个人开发者账号需要20人连续14天的封测的要求&#xff0c;周期长&#xff0c;且随着政策执行力度…

[Vulnhub] IMF File Upload BypassBuffer Overflow

信息收集 IP AddressOpening Ports192.168.8.103TCP:80 $ nmap -p- 192.168.8.103 --min-rate 1000 -sC -sV PORT STATE SERVICE VERSION 80/tcp open http Apache httpd 2.4.18 ((Ubuntu)) |_http-title: IMF - Homepage |_http-server-header: Apache/2.4.18 (Ubunt…

揭秘!chatGPT核心技术应用

2022年11月30日&#xff0c;可能将成为一个改变人类历史的日子——美国人工智能开发机构OpenAI推出了聊天机器人ChatGPT-3.5&#xff0c;将人工智能的发展推向了一个新的高度。2023年11月7日&#xff0c;OpenAI首届开发者大会被称为“科技界的春晚”&#xff0c;吸引了全球广大…

个人面试总结

写在前面&#xff1a;以下是自己在拟录用后回顾总结的了一下当时面试题目&#xff0c;把标答写了出来&#xff0c;供以后复习所使用&#xff0c;希望大家理性食用~~ 预祝大家都能找到心仪的工作 笔试题目&#xff1a; 1.1. java中Collection和Collections的区别 Collection…

怎样将aac转换mp3格式?推荐四个aac转MP3的方法

怎样将aac转换mp3格式&#xff1f;当需要将aac格式音频转换为MP3格式时&#xff0c;有几种方法可以轻松实现这一目标。MP3是一种广泛支持的音频格式&#xff0c;几乎所有设备和平台都能播放MP3文件&#xff0c;包括各种音乐播放器、手机、平板电脑和汽车音响系统。而且它也提供…