Canvas绘图
Canvas绘图
Canvas的意义
随着前端的不断发展,页面特效越来越炫酷,W3C组织也不断退出新的CSS特性:例如各种渐变,瀑布流布局,各种阴影,但是随着需求越来越花哨,W3C表示:我去你妈的,你自己画去吧。
于是浏览器就暴露出了Canvas API让用户自己实现各种炫酷的效果。
学习过浏览器的渲染过程,我们可以知道其实浏览器的窗口本身就是一个画布,他根据DOM和CSSOM不断得生成绘制指令来重绘页面。
Canvas其实就是浏览器将绘制指令封装成API给用户进行调用,这也是为什么Canvas的性能要比直接操作DOM的性能更高的原因。
这就是Canvas存在的意义,可以自定义炫酷的效果,可以有比DOM操作更好的性能。
图形绘制API
坐标系
讲绘图之前先讲解一下坐标系。
Canvas的坐标系与浏览器的坐标系相同,都是以左上角为原点,向右为x轴,向下为y轴。
上下文
想用Canvas进行绘制,首先需要拿到Canvas的上下文,它就相当于一个画笔,可以发出各种绘制指令。
<canvas id="canvas" />const canvas = document.querySelector("#canvas");
const ctx = canvas.getContext('2d');
这个 ctx
就是canvas的上下文,这个上下文共有4种类型:
- 2d: 绘制2d图形
- bitmaprenderer: 绘制位图
- webgl: 绘制3d图形,只在实现WebGL1的浏览器种可用
- webgl2: 绘制3d图形,只在实现WebGL2的浏览器种可用
这里只学习最基本的2d图形绘制。
绘制指令
canvas支持4种图形的绘制
- 直线和矩形
- 曲线和椭圆
- 文本
- 图片
不管绘制哪种图形,都是按照下面的步骤发出绘图指令,和AI还有PS的逻辑很相似:
- 开启路径
- 设置着色(描边和填充)
- 设置路径
- 闭合路径
- 绘制
ctx.beginPath(); // 开启路径
ctx.strokeStyle = '#aaaaaa'; // 设置描边颜色
ctx.fillStyle = '#111111'; // 设置填充颜色// ... 若干绘图指令ctx.closePath(); // 闭合路径,可选的,也可以不闭合,绘制一条开放的路径,如果使用填充指令来绘制的话,路径会自动闭合。ctx.fill(); // 填充
ctx.stroke(); // 描边
直线绘制
绘制一个三角形演示一下
ctx.moveTo(100, 100); // 移动画笔到100, 100的位置
ctx.lineTo(100, 200); // 从当前位置向100, 200画一条路径
ctx.lineTo(200, 200);
ctx.closePath(); // 闭合路径形成完整的三角形
ctx.stroke(); // 根据路径描边
矩形绘制
矩形的绘制有三种API
- 绘制矩形的路径
- 绘制带描边的矩形
- 绘制带填充的矩形
三种API不做演示,剩余的圆形,曲线等API也不做演示,可自行查阅文档。
Canvas 教程 - Web API 接口参考 | MDN (mozilla.org)
案例
利用图形API可以制作一个粒子连线效果。
首先定义一个类,用于表示粒子点:
class Point {constructor() {this.r = 8;this.x = getRandomInt(canvas.width - this.r, this.r);this.y = getRandomInt(canvas.height - this.r, this.r);}draw() {ctx.beginPath();ctx.fillStyle = 'rgba(11, 11, 11, 255)';ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2);ctx.closePath();ctx.fill();}}
然后再定义一个类,用于表示粒子图
class Graph {constructor(count = 30, maxDis = 200) {this.maxDis = maxDis;this.points = new Array(30).fill(null).map(item => new Point());}draw() {for(let i = 0; i < this.points.length; i++) {const p = this.points[i];p.draw();for(let j = i+1; j < this.points.length; j++) {const p2 = this.points[j];const d = Math.sqrt((p.x - p2.x) ** 2 + (p.y - p2.y) ** 2);ctx.beginPath();ctx.moveTo(p.x, p.y);ctx.lineTo(p2.x, p2.y);ctx.closePath();ctx.strokeStyle = `rgba(11, 11, 11, ${255 * (this.maxDis - d) / this.maxDis})`;ctx.stroke();}}}}function getRandomInt(max, min = 0) {return Math.floor( Math.random() * (max - min + 1) ) + min;}
加上最后的代码,就可以实现一副一直抽搐的粒子图。
function main() {ctx.fillStyle = '#fff';ctx.fillRect(0, 0, canvas.width, canvas.height);new Graph().draw();requestAnimationFrame(main);}main();
清晰度问题
随着页面的放大,Canvas绘制的图形会变得模糊。
要解决这个问题,首先要知道,其实Canvas绘制的结果,就是一张图片。
图像有两种尺寸,一种是自然尺寸,一种是样式尺寸。
自然尺寸就是图像原本的大小,样式尺寸是通过css或者JS设置的尺寸。
图片随着放大会变得模糊,是因为它的样式尺寸大于他的自然尺寸,使得原来的一个像素点需要两个或者更多像素点来显示,但是图片本身并没有包含这么多信息,因此就会变得模糊。
但是Canvas的尺寸是可以自己设置的,绘制的图形大小也是可以自己设置的,也就是自然尺寸是可以更改的。所以利用这个特性,我们只要满足 自然尺寸 = 样式尺寸 * 缩放倍率
,就可以做到Canvas不会模糊。
使用下面的代码可以在页面上绘制一个圆
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');function init() {ctx.width = 200;ctx.height = 200;
}init();ctx.beginPath();
const r = 80;
ctx.arc(canvas.width / 2, canvas.height / 2, r, 0, Math.PI * 2);
ctx.strokeStyle = '#fff';
ctx.lineWidth = 10; // 设置canvas线宽
ctx.stroke();
通过这一段代码可以绘制初始化一个200 * 200的canvas页面,并在canvas的中央绘制了一个半径为80的圆。
现在的代码,当浏览器的缩放倍率变化时,圆会变得模糊。
我们可以修改代码,使得 自然尺寸 = 样式尺寸 * 缩放倍率
这个等式永远成立,只要这个等式成立,图像一定是清晰的。
缩放倍率变化后,可以修改Canvas的自然尺寸使得图像依然清晰。
// 修改原始尺寸
function init() {// devicePixelRatio 可以获取当前浏览器的放大倍率canvas.width = 200 * devicePixelRatio; // 样式尺寸 * 缩放倍率 = 原始尺寸canvas.height = 200 * devicePixelRatio;
}
如果想让canvas上绘制的内容也跟着放大和缩小,只需要让绘制指令中的数值也跟着放大和缩小。
ctx.beginPath();
const r = 80 * devicePixelRatio;
ctx.arc(canvas.width / 2, canvas.height / 2, r, 0, Math.PI * 2);
ctx.strokeStyle = '#fff';
ctx.lineWidth = 10 * devicePixelRatio; // 设置canvas线宽
ctx.stroke();
动画
使用Canvas来做动画,原理很简单,就是每间隔一段时间重新绘制Canvas。
因为JS的定时器不准确,计时间隔小的时候可能会出现掉帧的现象,所以使用 requestAnimationFrame()
更合适。
有点类似递归,这样写就可以让浏览器每次刷新时都重新绘制Canvas。
function draw() {// 若干绘制指令reuqestAnimationFrame(draw);
}
文字绘制–代码雨效果
canvas绘制文字的方式很简单。只有下面的几个API
// 设置字体和对齐方式
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.font = fontSize + 'px Verdana';// 填充或者描边文字
ctx.fillText(text, x, y);
ctx.strokeText(text, x, y);
有了这几个API,我们可以制作一个代码雨效果,下面是实现的代码。
// 文字绘制函数function draw() {// 逐行绘制文字,绘制每行文字之前先绘制一层浅浅的遮罩来降低原有文字的不透明度// 每次绘制文字之前ctx.fillStyle = '#ffffff20';ctx.fillRect(0, 0, canvas.width, canvas.height);ctx.fillStyle = 'green';for(let i = 0; i < col; i++) {// 随机获取一个字符,并绘制,每列绘制一个// rows数组存储的是要绘制的文字的y坐标,有多少列就有多少项ctx.fillText(getRandomChar(), i * fontSize, rows[i]);// 当文字的y坐标同时满足两项时才将文字绘制的y坐标清0if(rows[i] > canvas.height && Math.random() > 0.99) {rows[i] = 0;} else {rows[i] += fontSize;}}}// 使用定时器频繁绘制,这里不使用requestAnimationFrame的原因是帧率太高。setInterval(() => {draw()}, 50);draw();// 辅助函数,获取一个随机字符function getRandomChar() {const chars = '0123456789qwertyuiopasdfghjklzxcvbnm'.split('');const index = Math.floor( Math.random() * chars.length );return chars[index];}
图片绘制–魔棒效果
图像相关的CanvasAPI如下:
ctx.drawImage(img, x, y); // 在canvas中绘制图像
ctx.getImageData(x, y, width, height); // 获取Canvas的像素信息
ctx.putImageData(imageData, x, y); // 根据像素信息绘制Canvas
imageData.data.set(greenColor, i); // 设置Canvas的像素信息
canvas不仅可以将图片绘制出来,甚至还可以拿到像素点的信息。
function init() {const img = new Image();img.src = '';// 加载完成时间,当图像加载完成后执行img.onload((e) => {// 设置canvas尺寸与图片一致canvas.width = img.width;canvas.height = img.height;ctx.drawImage(img, 0, 0); // 在Canvas中绘制图片})
}canvas.addEventListener('click', (e) => {// 获取用户点击的位置在Canvas中的坐标;const x = e.offsetX;const y = e.offsetY;// 取出Canvas的像素信息,这是一个大数组,每四项为一组,分别代表一个像素点的RGBA值const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
})
如果我们想做到点击后修改图片中的颜色,那我们需要先定义一些辅助函数,下面的两个辅助函数能帮助我们更方便的查找Canvas中的颜色。
// 坐标转下标
function point2Index() {return (y * canvas.width + x) * 4;
}// 根据坐标获取像素点信息
function getColor(x, y, imageData) {const i = point2Index(x, y);return [imageData[i],imageData[i+1],imageData[i+2],imageData[i+3]]
}
如果我希望我点击的位置变为绿色,那我们可以修改对应位置的RGBA值,然后将新的像素信息交给Canvas去绘制。
canvas.addEventListener('click', (e) => {// 获取用户点击的位置在Canvas中的坐标;const x = e.offsetX;const y = e.offsetY;// 取出Canvas的像素信息,这是一个大数组,每四项为一组,分别代表一个像素点的RGBA值const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);const clickColor = getColor(x, y, imageData);const greenColor = [0, 255, 0, 255]function _changeColor(x, y) {const i = point2Index(x, y);// 设置ImageData中的值imageData.data.set(greenColor, i);}_changeColor(x, y);// 上面修改的只是内存中的像素点信息,通过putImageData将数组应用到Canvas中。ctx.putImageData(imageData, 0, 0);
})// 因为需要多次获取Canvas中的像素信息,浏览器发出了警告,建议我们加上一个配置
const ctx = canvas.getContext('2d', {willReadFrequently: true // 告诉浏览器将会频繁的读取像素信息,需要做相应优化
})
需求还可以进一步提升,例如我需要像PS的魔棒一样,点击后,相邻相似的颜色都会改变。
我们只需要改写一下 _changeColor()
函数,递归得调用它即可。
function _changeColor() {// 如果超出边界,停止递归if(x<0 || x>canvas.width || y<0 || y>canvas.height) {return;}const color = getColor(x, y, imageData);// 如果颜色差值较大,则停止递归if(diffColor(color, clickColor) > 100) {return;}// 已经被改为绿色,不再更改if(diffColor(color, greenColor) === 0) {return;}const i = point2Index(x, y);// 设置ImageData中的值imageData.data.set(greenColor, i);// 递归调用,如果图片比较大,递归可能会栈溢出,可以改用循环来写。_changeColor(x + 1, y);_changeColor(x - 1, y);_changeColor(x, y + 1);_changeColor(x, y - 1);
}// 辅助函数,返回两个函数的颜色差值
function diffColor(color1, color2) {return Math.abs(color1[0] - color2[0]) + Math.abs(color1[1] - color2[1]) + Math.abs(color1[2] - color2[2]) + Math.abs(color1[3] - color2[3])
}
绘制和拖拽
有了前面的Canvas基础,可以来做两个综合案例。
下面制作一个使用画笔图板和一个图形画板,Canvas可以做的效果很多,主要是制作的思路。
画笔画板
图形画板
相关文章:
Canvas绘图
Canvas绘图 Canvas的意义 随着前端的不断发展,页面特效越来越炫酷,W3C组织也不断退出新的CSS特性:例如各种渐变,瀑布流布局,各种阴影,但是随着需求越来越花哨,W3C表示:我去你妈的&…...
逻辑回归评分卡
文章目录 一、基础知识点(1)逻辑回归表达式(2)sigmoid函数的导数损失函数(Cross-entropy, 交叉熵损失函数)交叉熵求导准确率计算评估指标 二、导入库和数据集导入库读取数据 三、分析与训练四、模型评价ROC曲线KS值再做特征筛选生成报告 五、行为评分卡模型表现总结 一、基础知…...
DPDK系列之三十三DPDK并行机制的底层支持
一、背景介绍 在前面介绍了DPDK中的上层对并行的支持,特别是对多核的支持。但是,大家都知道,再怎么好的设计和架构,再优秀的编码,最终都要落到硬件和固件对整个上层应用的支持。单纯的硬件好处理,一个核不…...
LVGL_基础控件滚轮roller
LVGL_基础控件滚轮roller 1、创建滚轮roller控件 /* 创建一个 lv_roller 部件(对象) */ lv_obj_t * roller lv_roller_create(lv_scr_act()); // 创建一个 lv_roller 部件(对象),他的父对象是活动屏幕对象// 将部件(对象)添加到组,如果设置了默认组,…...
王道考研操作系统——文件管理
磁盘的基础知识 .txt用记事本这个应用程序打开,文件最重要的属性就是文件名了 保护信息:操作系统对系统当中的各个用户进行了分组,不同分组的用户对文件的操作权限是不一样的 文件的逻辑结构就是文件内部的数据/记录应该被怎么组织起来&…...
商业智能系统的主要功能包括数据仓库、数据ETL、数据统计输出、分析功能
ETL服务内容包含: 数据迁移数据合并数据同步数据交换数据联邦数据仓库...
基于帝国主义竞争优化的BP神经网络(分类应用) - 附代码
基于帝国主义竞争优化的BP神经网络(分类应用) - 附代码 文章目录 基于帝国主义竞争优化的BP神经网络(分类应用) - 附代码1.鸢尾花iris数据介绍2.数据集整理3.帝国主义竞争优化BP神经网络3.1 BP神经网络参数设置3.2 帝国主义竞争算…...
将python项目部署在一台服务器上
将python项目部署在一台服务器上 1.服务器2.部署方法2.1 手动部署2.2 容器化技术部署2.3 服务器less技术部署 1.服务器 服务器一般为:物理服务器和云服务器。 我的是物理服务器:这是将服务器硬件直接放置在您自己的数据中心或机房的传统方法。这种方法需…...
【C语言】善于利用指针(二)
💗个人主页💗 ⭐个人专栏——C语言初步学习⭐ 💫点击关注🤩一起学习C语言💯💫 目录 导读:1. 字符指针1.1 字符串的引用方式1.2 有趣的面试题 2. 数组指针2.1 一维数组指针的定义2.2 一维数组…...
Python调用C++
https://www.cnblogs.com/renfanzi/p/10276997.html Linux使用Python调用C/C接口(一) - 代码先锋网 linux系统上使用Python调用C生成的.so动态链接库opencv_linux 下python 编译为so ,给c使用_比赛学习者的博客-CSDN博客 https://www.cnblogs.com/shuimuqingyang/p/13618105…...
自己实现扫描全盘文件的函数。
1.自己实现扫描全盘的函数 def scan_disk(dir): global count,dir_count if os.path.isdir(dir): files os.listdir(dir) for file in files: print(file) dir_count 1 if os.path.isdir(dir os.sep file): …...
JSON文件读写
1、依赖文件 #include <QFile> #include <QJsonDocument> #include <QJsonObject> #include <QDebug> #include <QStringList>2、头文件 bool ReadJsonFile(const QString& filePath""); bool WriteJsonFile(const QString&…...
VisualStudio2022环境下Release模式编译dll无法使用TLS函数问题
Debug x86环境下正常使用TLS回调函数 切换到Release发现程序没有使用tls 到C/C > 优化中将全程序优化关闭即可...
ChatGPT基础使用总结
文章目录 一、ChatGPT基础概念大型语言模型LLMs---一种能够以类似人类语言的方式“说话”的软件ChatGPT定义---OpenAI 研发的一款聊天机器人程序(2022年GPT-3.5,属于大型语言模型)ChatGPT4.0---OpenAI推出了GPT系列的最新模型ChatGPT典型使用…...
解决报错: require is not defined in ES module scope
用node启动mjs文件报错:require is not defined in ES module scope 现象如下: 原因: 文件后缀是mjs, 被识别为es模块,但是node默认是commonjs格式,不支持也不能识别es模块。 解决办法:把文件后缀从.mjs改…...
STM32 10个工程篇:1.IAP远程升级(六)
在IAP远程升级的最后一篇博客里,笔者想概括性地梳理总结IAP程序设计中值得注意的问题,诚然市面上或者工作后存在不同版本的IAP下位机和上位机软件,也存在不同定义的报文格式,甚至对于相似的知识点不同教程又有着完全不同的解读&am…...
【智能家居项目】裸机版本——字体子系统 | 显示子系统
🐱作者:一只大喵咪1201 🐱专栏:《智能家居项目》 🔥格言:你只管努力,剩下的交给时间! 今天实现上图整个项目系统中的字体子系统和显示子系统。 目录 🀄设计思路…...
PDF中跳转到参考文献后,如何回到原文
在PDF中,点击了参考文献的超链接可以直接跳至参考文献的位置。 如果想从当前参考文献在回到正文中对应位置时,可以通过 Alt \red{\text{Alt}} Alt ← \red{\leftarrow} ← 实现。...
了解基于Elasticsearch 的站内搜索,及其替代方案
对于一家公司而言,数据量越来越多,如果快速去查找这些信息是一个很难的问题,在计算机领域有一个专门的领域IR(Information Retrival)研究如何获取信息,做信息检索。在国内的如百度这样的搜索引擎也属于这个…...
【多模态融合】TransFusion学习笔记(2)
接上篇【多模态融合】TransFusion学习笔记(1)。 从TransFusion-L到TransFusion ok,终于可以给出论文中那个完整的框架图了,我第一眼看到这个图有几个疑问: Q:Image Guidance这条虚线引出的Query Initialization是什么意思? Q:图像分支中的…...
Pyhon-每日一练(1)
🌈write in front🌈 🧸大家好,我是Aileen🧸.希望你看完之后,能对你有所帮助,不足请指正!共同学习交流. 🆔本文由Aileen_0v0🧸 原创 CSDN首发🐒 如…...
MySQL:数据库的物理备份和恢复-冷备份(3)
介绍 物理备份: 直接复制数据文件进行的备份 优点:不需要其他的工具,直接复制就好,恢复直接复制备份文件即可 缺点:与存储引擎有关,跨平台能力较弱 逻辑备份: 从数据库中导出数据另存而进行的备…...
功能比较:Redisson vs Jedis
Redis最流行的两个Java客户端库是Redisson和Jedis。Redisson提供内存中的数据网格功能,支持Redis的各种分布式对象和服务。另一方面,Jedis是一个更轻量级的产品,它缺乏其他库的某些功能。 如果你正在为Redis寻找一个Java客户端库…...
Spring web security
儅使用spring的web security時,默認會轉向自帶的spring security example page。而不會轉向error page。 TODO: <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId> &l…...
SpringCloud(二)Docker、Spring AMQP、ElasticSearch
文章目录 DockerDocker与虚拟机Docker架构镜像、容器、镜像托管平台Docker架构Docker实践 Spring AMQP简单使用案例工作队列- WorkQueue发布订阅服务FanoutExchangeDirectExchangeTopicExchange 消息转换器 ElasticSearch倒排索引IK分词器IK分词拓展与停用字典 操作索引库mappi…...
7.Tensors For Beginneers - Convector Components
介绍协向量时,曾说过它们有点像 行向量, 行向量确实以某种方式代表了协向量, 这里说明一下: 协向量是不变的; 协向量组件是可变的。 协向量不依赖坐标系,协向量的组件取决于坐标系。 当我们说协向量具有组…...
直线导轨坏了可以维修吗?
直线导轨是工业自动化设备中常用的零部件,其性能和使用寿命对设备的稳定运行和产能有着直接的影响,在生产中,由于各种原因,直线导轨会出现各种问题,那么,直线导轨的维修方法究竟是怎样的呢?我们…...
Java基础--泛型详解
一、背景 java推出泛型之前,集合元素类型可以是object类型,能够存储任意的数据类型对象,但是在使用过程中,如果不知道集合里面的各个元素的类型,在进行类型转换的时候就很容易引发ClassCastException异常。 二、概念 …...
学习搜狗的workflow,MacBook上如何编译
官网说可以在MacBook上也可以运行,但是编译的时候却有找不到openssl的错误: 看其他博客也有类似的错误,按照类似的思路去解决 问题原因和解决办法 cmake编译的时候,没有找到openssl的头文件,需要设置cmake编译环境下…...
Ubuntu使用cmake和vscode开发自己的项目,引用自己的头文件和openCV
创建文件夹 mkdir my_proj 继续创建include 和 src文件夹,形成如下的目录结构 用vscode打开项目 创建add.h #ifndef ADD_H #define ADD_Hint add(int numA, int numB);#endif add.cpp #include "add.h"int add(int numA, int numB) {return numA nu…...
wordpress添加产品/磁力猫最好磁力搜索引擎
https://github.com/xuguohongai/android/tree/master/RxJavaRetrofixDemo转载于:https://blog.51cto.com/xuguohongai/2145221...
wordpress haiyuan/刷百度关键词排名
本文摘自:[url]http://news.ccw.com.cn/elife/htm2007/20070617_273592.shtml[/url]未来多核芯片将无处不在,针对多核的软件开发将是摆在软件产业界面前的一个大的挑战。 相对而言,多核对台式机、笔记本电脑和移动设备上的软件挑战更大。 6月…...
wordpress个人下载网站模板下载/网站seo去哪个网站找好
Mybatis-plus1、如何使用第三方组件,如mybatis-plus2、Mybatis-plus入门注意点2.1、创建springboot工程,在配置文件中连接mysql数据库2.2、在启动类上添加注解,注意扫描mapper接口3、配置日志4、插入测试及雪花算法4.1、int insert()5、主键生…...
哪里创建免费个人网站/seo智能优化系统
转载于:http://www.itxuexiwang.com/a/liunxjishu/2016/0225/162.html?1456480908摘要:GAMIT/GLOBK是一套安装于Unix/Linux操作系统高精度GPS数据处理分析软件,以Ubuntu12.04桌面版构建系统平台,在网络的支持下,安装最…...
产品网站策划书方案/山东服务好的seo
IT 之家9 月 4 日消息 谷歌 Nexus5 发布于 2013 年 10 月 31 日,初始搭载的系统为 Android 4.4,2015 年 12 月 8 日,谷歌为 Nexus 设备推送了 Android 6.0.1 更新,这也是 Nexus5 收到的来自谷歌的最后一个安卓版本更新。随后&…...
网站首页几天做完/百度霸屏推广靠谱吗
偶然帮朋友在win2003上设置asp网站,设置惯了.net网站,设置asp却怎么也不成功,搜了一篇很好的帖子,放在下面了。 其中关键之处是下面这张图,要启动父路径,否则asp网站里的包含文件就出问题,不知道…...