当前位置: 首页 > news >正文

JavaScript全解析——canvas 入门(上)

●canvas 是 HTML5 新增的一个标签, 表示画布
●canvas 也是 h5 的画布技术, 我们通过代码的方式在画布上描绘一个图像

canvas 标签
●向进行 canvas 绘图, 首先我们先要了解到 canvas 标签
●是 html5 推出的一个标签

<html>
<head>...
</head>
<body><canvas></canvas>
</body>
</html>复制代码

○canvas 默认是一个行内块元素
○canvas 默认画布大小是 300 * 150
○canvas 默认没有边框, 背景默认为无色透明

canvas 画布大小
●我们在绘图之前, 先要确定一个画布的大小
○因为画布默认是按照比例调整
○所以我们调整宽度或者高度的时候, 调整一个, 另一个自然会按照比例自己调整
○我们也可以宽高一起调整
●调整画布大小有两种方案
○第一种 : 通过 css 样式 ( 不推荐 )

<html>
<head><style>canvas {width: 1000px;height: 500px;}</style>
</head>
<body><canvas></canvas>
</body>
</html>复制代码

○第二种 : 通过标签属性 ( 推荐 )

<html>
<head>...
</head>
<body><canvas width="1000" height="500"></canvas>
</body>
</html>复制代码

●两种方案的区别
○通过 css 样式的调整方案, 不推荐
是因为这个方案其实并没有设置了画布的大小
而是把原先 300 * 150 的画布, 将他的可视窗口变成了 1000 * 500
所以真实画布并没有放大, 只是可视程度变大了
举个例子 : 就是你把一个 300 * 150 的图片, 放大到 1000 * 500 的大小来看
所以这个方式我们及其不推荐

○通过属性的调整方案, 推荐
这个才是真正的调整画布的的大小
也就是我们会在一个 1000 * 500 的画布上进行绘制

●画布的坐标
○canvas 画布是和我们 css 的坐标系一样的
○从 canvas 的左上角为 0 0 左边, 分别向右向下延伸为正方向

canvas 初体验
●准备工作已经完成了, 我们可以开始体验一下绘制了
●其实 canvas 画布很简单, 就和我们 windows 电脑的画板工具是一样的道理

●思考 :
我们在 windows 这个画板上绘制内容的时候
我们一定是先选定一个工具 ( 画笔, 矩形, 圆形, ... )
设定好样式 ( 粗细, 颜色 )
然后开始绘制
●其实在 canvas 绘制也是一个道理
拿到一个画布工具箱
从工具箱中选定工具
设定样式
开始绘制
●初体验步骤
●index.html

<html>
<head>...
</head>
<body><canvas id="canvas" width="600" height="300"></canvas><script src="./index.js"></script>
</body>
</html>复制代码

●index.js

// 0. 获取到页面上的 canvas 标签元素节点
const canvasEle = document.querySelector('#canvas')// 1. 获取当前这个画布的工具箱
// 语法: canvas 元素.getContext('2d')
const ctx = canvasEle.getContext('2d')// 2. 开始绘制
// 2-1. 讲画笔移动到一个指定位置开始下笔
// 语法: 工具箱.moveTo(x轴坐标, y轴坐标)
ctx.moveTo(100, 100)// 2-2. 将笔移动到一个指定位置, 画下一条轨迹
// 注意: 这里是没有显示的, 因为只是画了一个轨迹
// 语法: 工具箱.lineTo(x轴坐标, y轴坐标)
ctx.lineTo(300, 100)// 2-3. 设定本条线的样式
// 设定线的宽度
// 语法: 工具箱.lineWidth = 数字
ctx.lineWidth = 10
// 设定线的颜色
// 语法: 工具箱.strokeStyle = '颜色'
ctx.strokeStyle = '#000'// 2-4. 描边
// 把上边画下的痕迹按照设定好的样式描绘下来
// 语法: 工具箱.stroke()
ctx.stroke()复制代码

●至此我们的第一个线段就绘制完毕, 画布上就会出现一条线段
○从坐标 ( 100, 100 ) 绘制到坐标 ( 300, 100 )
○线段长度为 200px
○线段宽度为 10px
○线段颜色为 '#000' ( 黑色 )

canvas 线宽颜色问题
●刚才我们经过了初体验, 画了一个线段
●看似没有问题, 也出现了线段, 但是其实内在是有一些问题的
●我们先来观察
●这次我们再来画一个线段
○从坐标 ( 100, 100 ) 绘制到坐标 ( 300, 100 )
○线段长度为 200px
○线段宽度为 1px
○线段颜色为 '#000' ( 黑色 )

// 0. 获取到页面上的 canvas 标签元素节点
const canvasEle = document.querySelector('#canvas')// 1. 获取当前这个画布的工具箱
const ctx = canvasEle.getContext('2d')// 2. 开始绘制
ctx.moveTo(100, 100)// 2-2. 将笔移动到一个指定位置, 画下一条轨迹
ctx.lineTo(300, 100)// 2-3. 设定本条线的样式
// 设定线的宽度
ctx.lineWidth = 10
// 设定线的颜色
ctx.strokeStyle = '#000'// 2-4. 描边
ctx.stroke()复制代码

●效果出现了, 没有什么问题
●只是看上去不太想 1px, 而且颜色有些浅
●不着急, 我们再来画一个线段
○从坐标 ( 100, 100 ) 绘制到坐标 ( 300, 100 )
○线段长度为 200px
○线段宽度为 2px
○线段颜色为 '#000' ( 黑色 )

●这个时候问题就出现了
○两次画出来的线段, 一次设置 1px 一次设置 2px
○感觉上 线宽度 一样
○两次画出来的线段, 两次都是设置为 '#000' 的颜色
○但是感觉上颜色不太一样

●这是因为浏览器在描述内容的时候, 最小的描述单位是 1px
●我们来模拟一下浏览器绘制的内容
○假设这是我们浏览器描述的画布中的像素点

○我们来做一个坐标的标记

○现在呢不关注线的长度和坐标, 我们就画一个宽度为 1px 的线段

○我们来剖析一下问题
因为在描绘这个线段的时候, 会把线段的最中心点放在这个像素点位上
也就是说, 在描述线宽的时候, 实际上会从 0.5px 的位置绘制到 1.5px 的位置
合计描述宽度为 1px
但是浏览器的最小描述为 1px
这里说的不是最小宽度为 1px, 是浏览器不能在非整数像素开始描述
也就是说浏览器没办法从 0.5 开始绘制, 也没有办法绘制到 1.5 停止
那么就只能是从 0 开始绘制到 2
所以线宽就会变成 2px 了
因为本身一个像素的黑色被强制拉伸到两个像素宽度, 所以颜色就会变浅
就像我们一杯墨水, 倒在一个杯子里面就是黑色
但是到在一个杯子里面的时候, 又倒进去一杯水, 颜色就会变浅
○实际描绘出来的样子

○这就变成了我们刚才看到的样子
●所以, 我们在进行 canvas 绘制内容的时候, 涉及到线段的时候
●我们一般不会把线段宽度设置成奇数, 一般都是偶数的

canvas 绘制平行线
●刚才我们绘制了线段, 接下来我们来绘制一个平行线, 也就是两个线段
●小伙伴: " 一个简单的效果, 想到就搞 "

// 0. 获取到页面上的 canvas 标签元素节点
const canvasEle = document.querySelector('#canvas')// 1. 获取当前这个画布的工具箱
const ctx = canvasEle.getContext('2d')// 2. 开始绘制第一个线段
ctx.moveTo(100, 100)
ctx.lineTo(300, 100)
ctx.lineWidth = 2
ctx.strokeStyle = '#000'
ctx.stroke()// 3. 开始绘制第二个线段
ctx.moveTo(100, 100)
ctx.lineTo(300, 100)
ctx.lineWidth = 2
ctx.strokeStyle = '#000'
ctx.stroke()复制代码

●没有问题, 效果实现了

●接下来, 咱们稍微增加一下需求
○第一个线段线宽 2px, 黑色
○第二个线段线宽 10px, 红色
●这也简单啊, 稍微修改一下

// 0. 获取到页面上的 canvas 标签元素节点
const canvasEle = document.querySelector('#canvas')// 1. 获取当前这个画布的工具箱
const ctx = canvasEle.getContext('2d')// 2. 开始绘制第一个线段
ctx.moveTo(100, 100)
ctx.lineTo(300, 100)
ctx.lineWidth = 2
ctx.strokeStyle = '#000'
ctx.stroke()// 3. 开始绘制第二个线段
ctx.moveTo(100, 100)
ctx.lineTo(300, 100)
ctx.lineWidth = 10
ctx.strokeStyle = 'red'
ctx.stroke()复制代码

●这是什么鬼, 为什么两个线段都变了, 不是应该只改变一个吗 ?
这是因为我们并没有告诉他这是两个不一样的线段
所以在设置线段样式的时候, 会默认按照最后一次设置的样式来绘制所有的线段
我们要想让第一个线段绘制完毕以后, 和第二个没有关系
我们需要告诉画布, 我的这个线段结束了, 后面的不要和我扯上关系

// 0. 获取到页面上的 canvas 标签元素节点
const canvasEle = document.querySelector('#canvas')// 1. 获取当前这个画布的工具箱
const ctx = canvasEle.getContext('2d')// 2. 开始绘制第一个线段
ctx.moveTo(100, 100)
ctx.lineTo(300, 100)
ctx.lineWidth = 2
ctx.strokeStyle = '#000'
ctx.stroke()// 3. 结束之前的绘制内容
// 语法: 工具箱.beginPath()
ctx.beginPath()// 4.. 开始绘制第二个线段
ctx.moveTo(100, 100)
ctx.lineTo(300, 100)
ctx.lineWidth = 10
ctx.strokeStyle = 'red'
ctx.stroke()复制代码

●这样才是我们的需求

canvas 绘制三角形
●画完了线段, 咱们就来画一个简单的图形, 画一个三角形
●其实就是由三个线段组成, 用三个线段围成一个封闭图形即可

// 0. 获取到页面上的 canvas 标签元素节点
const canvasEle = document.querySelector('#canvas')// 1. 获取当前这个画布的工具箱
const ctx = canvasEle.getContext('2d')// 2. 开始绘制第一个线段
// 三角形第一个点
ctx.moveTo(100, 100)
// 三角形第二个点
ctx.lineTo(200, 100)
// 三角形第三个点
ctx.lineTo(200, 200)
// 回到第一个点
ctx.lineTo(100, 100)
ctx.lineWidth = 2
ctx.strokeStyle = '#000'
// 描边
ctx.stroke()复制代码

●看似没啥问题, 一个三角形就出来了
●但是我们仔细观察一下三角形的第一个角

●因为这是两个线段, 只是画到了一个点, 不可能重叠出一个 尖儿~~
●这个时候, 我们就不能这样绘制三角形了
当我们要绘制闭合图形的时候
我们不要手动绘制最后一个路径, 而是描述出形状
通过 canvas 让他自动闭合
●首先, 我们绘制出形状, 不要闭合最终路径

// 0. 获取到页面上的 canvas 标签元素节点
const canvasEle = document.querySelector('#canvas')// 1. 获取当前这个画布的工具箱
const ctx = canvasEle.getContext('2d')// 2. 开始绘制第一个线段
// 三角形第一个点
ctx.moveTo(100, 100)
// 三角形第二个点
ctx.lineTo(200, 100)
// 三角形第三个点
ctx.lineTo(200, 200)ctx.lineWidth = 2
ctx.strokeStyle = '#000'
// 描边
ctx.stroke()复制代码

●接下来, 让 canvas 来帮我们闭合这个封闭图形

// 0. 获取到页面上的 canvas 标签元素节点
const canvasEle = document.querySelector('#canvas')// 1. 获取当前这个画布的工具箱
const ctx = canvasEle.getContext('2d')// 2. 开始绘制第一个线段
ctx.moveTo(100, 100)
ctx.lineTo(200, 100)
ctx.lineTo(200, 200)// 自动闭合图形
// 语法: 工具箱.closePath()
ctx.closePath()ctx.lineWidth = 2
ctx.strokeStyle = '#000'
// 描边
ctx.stroke()复制代码

●这个时候, 我们发现一个正常的三角形就出现了
●注意 : 闭合路径
closePath() 这个方法
是从当前坐标点, 直接用线段的方式连接到 modeTo() 的位置
也就是从当前坐标点直接连接到开始坐标点

相关文章:

JavaScript全解析——canvas 入门(上)

●canvas 是 HTML5 新增的一个标签, 表示画布 ●canvas 也是 h5 的画布技术, 我们通过代码的方式在画布上描绘一个图像 canvas 标签 ●向进行 canvas 绘图, 首先我们先要了解到 canvas 标签 ●是 html5 推出的一个标签 <html> <head>... </head> <body&…...

vue 插槽的用法

Vue的插槽&#xff08;Slot&#xff09;是一种可以让父组件向子组件传递内容的机制。插槽可以让开发者将组件的结构和内容分离开来&#xff0c;从而实现更好的可维护性和复用性。 在Vue中&#xff0c;插槽通过 标签实现。具体用法如下&#xff1a; 单个插槽 在子组件中使用一…...

【C++复习2】C++编译器的工作原理

如果你是一名newbird的话&#xff0c;建议观看如下视频加深你的理解&#xff0c;再看如下内容&#xff1a; https://www.bilibili.com/video/BV1N24y1B7nQ?p7 The cherno会额外告诉你如何将目标文件转换成汇编代码&#xff0c;CPU执行指令的过程以及编译器如何通过删除冗余变…...

回调函数_回顾

函数指针和回调函数 函数指针是指向函数的指针变量。 通常我们说的指针变量是指向一个整型、字符型或数组等变量&#xff0c;而函数指针是指向函数。 函数指针可以像一般函数一样&#xff0c;用于调用函数、传递参数。 直接用指针表示函数&#xff0c;而不是*指针&#xff…...

今天面了个字节跳动拿35K出来的,真是砂纸擦屁股,给我露了一手啊

今年的金三银四已经结束&#xff0c;很多小伙伴收获不错&#xff0c;拿到了心仪的 offer。 各大论坛和社区里也看见不少小伙伴慷慨地分享了常见的面试题和八股文&#xff0c;为此咱这里也统一做一次大整理和大归类&#xff0c;这也算是划重点了。 俗话说得好&#xff0c;他山之…...

8. 数据结构与算法

8. 数据结构与算法 常见面试题 说说一个算法有哪些时间复杂度?归并算法时间复杂度是多少?⭐⭐⭐ O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) 归并算法时间复杂度是O(nlogn) 说说数组时间复杂度,什么场景下使用?⭐⭐⭐⭐⭐ 从渐进趋势…...

Springboot +Flowable,任务认领和回退(一)

一.简介 有的时候&#xff0c;一个任务节点会存在多个候选人&#xff0c;例如&#xff1a;张三提交一个任务&#xff0c;这个任务即可以由李四处理&#xff0c;又可以由王五处理&#xff0c;那么针对这种多个任务候选人的情况&#xff0c;该如何处理&#xff1f; 二.绘制流程…...

机器学习 | MATLAB实现CNN-LSTM卷积长短期记忆神经网络模型答疑

机器学习 | MATLAB实现CNN-LSTM卷积长短期记忆神经网络模型答疑 目录 机器学习 | MATLAB实现CNN-LSTM卷积长短期记忆神经网络模型答疑问题汇总问题一答疑问题二答疑问题三答疑问题四答疑问题五答疑参考资料问题汇总 有几个问题咨询一下: 1.数据集划分时出现了问题如何解决? 2…...

卷首语:我决定从零开始,用C++手写自己的键值数据库

2023年5月1日&#xff0c;周一下午。 我为什么要自己写一个&#xff1f; 今天中午&#xff0c;我在CSDN上提出了一个关于如何学习C的观点——通过用C造东西来学习C&#xff0c;于是为了实践这个观点&#xff0c;我决定用C手写一个简单的属于自己的键值数据库。 我会把这个键值…...

【Vue】收集表单数据 过滤器

收集表单数据 收集表单数据&#xff1a; 若&#xff1a;<input type"text"/>,则v-model收集的是value值&#xff0c;用户输入的就是value值若&#xff1a;<input type"radio"/>,则v-model收集的是value值&#xff0c;且要给标签配置value值若…...

Linux线程:死锁

1. 死锁 &#xff08;1&#xff09;概念 死锁&#xff08;DeadLock&#xff09;指两个或两个以上的进程或线程执行时&#xff0c;由于竞争临界资源而造成阻塞的现象&#xff1b;若不干涉&#xff0c;则无法推进下去。 &#xff08;2&#xff09;死锁的原因 ① 竞争临界资源…...

thinkphp+vue+html超市零食品美食推荐系统零食购物商城网站7v281

本系统的开发使获取食品推荐系统信息能够更加方便快捷&#xff0c;同时也使食品推荐系统管理信息变的更加系统化、有序化。系统界面较友好&#xff0c;易于操作 运行环境:phpstudy/wamp/xammp等 开发语言&#xff1a;php 后端框架&#xff1a;Thinkphp5 前端框架&#xff1a;vu…...

思考外语学习的底层逻辑(以英语、法语为例)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言一、英语1.学习历程2.英语学习的心得3.理论检验&#xff08;持续更新&#xff09; 二、法语1.学习历程2.读入数据 总结 前言 提示&#xff1a;这里谈谈自己为什么要…...

命名ACL配置

命名ACL配置 【实验目的】 掌握命名ACL的配置。验证配置。 【实验拓扑】 实验拓扑如图1所示。 图1 实验拓扑 设备参数如表所示。 表1 设备参数表 设备 接口 IP地址 子网掩码 默认网关 R1 S0/3/0 192.168.1.1 255.255.255.252 N/A Fa0/0 192.168.2.1 255.255.…...

2008-2019年主要城市PITI指数

2008-2019年主要城市PITI指数 1、来源&#xff1a;附在文件内 2、时间区间&#xff1a;2008-2019年 3、具体时间分布&#xff1a;、2008、2009-2010、2011、2012、2013-2014、2014-2015、2015-2016、2016-2017、2017-2018、2018-2019、 4、范围&#xff1a;包括110个城市&a…...

拷贝构造函数和赋值重载函数详解

1.拷贝构造函数 1.1拷贝构造函数的概念 拷贝构造函数&#xff1a;只有单个形参&#xff0c;该形参是对本类类型对象的引用(一般常用const修饰)&#xff0c;在用已存在的类类型对象创建新对象时由编译器自动调用。拷贝构造函数也是特殊的成员函数&#xff0c;其特征如下&#…...

5件关于JavaScript中this参数的事

this 关键字是 JavaScript 中最令人困惑的部分之一&#xff0c;本文试图通过介绍有关它的五个重要事项来阐明其目的和用法。 1、它允许访问同一对象上的其他属性 在 JavaScript 中&#xff0c;函数可以是独立的单元&#xff0c;但它们也可以用作对象的值。考虑下一个对象。 …...

面试题30天打卡-day17

1、什么是内部类&#xff1f; 内部类的分类有哪些 &#xff1f;内部类的优点 &#xff0c;内部类有哪些应用场景&#xff1f; 内部类&#xff1a;在一个类中创建一个新的类 内部类主要分为以下4种&#xff1a;成员内部类、局部内部类、静态内部类、匿名内部类。 成员内部类 …...

对标世界一流|弹性应对“供应链不确定性常态化” ——快消与重资产行业的经验互鉴

1963年&#xff0c;气象学家洛伦兹提出的“蝴蝶效应”表示&#xff1a;“一只蝴蝶在巴西扇动翅膀&#xff0c;有可能会在美国德克萨斯州引起一场龙卷风”。本文希望通过提供快消行业的先进实践&#xff0c;帮助重资产企业从“蝴蝶扇动翅膀”之前就开始行动&#xff0c;避免“龙…...

【MPC|云储能】基于模型预测控制(MPC)的微电网调度优化的研究(matlab代码)

目录 1 主要内容 2 程序难点及问题说明 3 部分程序 4 下载链接 1 主要内容 该程序分为两部分&#xff0c;日前优化部分——该程序首先根据《电力系统云储能研究框架与基础模型》上面方法&#xff0c;根据每个居民的实际需要得到响应储能充放电功率&#xff0c;优化得到整体…...

Cursor实现用excel数据填充word模版的方法

cursor主页&#xff1a;https://www.cursor.com/ 任务目标&#xff1a;把excel格式的数据里的单元格&#xff0c;按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例&#xff0c;…...

Flask RESTful 示例

目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题&#xff1a; 下面创建一个简单的Flask RESTful API示例。首先&#xff0c;我们需要创建环境&#xff0c;安装必要的依赖&#xff0c;然后…...

镜像里切换为普通用户

如果你登录远程虚拟机默认就是 root 用户&#xff0c;但你不希望用 root 权限运行 ns-3&#xff08;这是对的&#xff0c;ns3 工具会拒绝 root&#xff09;&#xff0c;你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案&#xff1a;创建非 roo…...

JDK 17 新特性

#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持&#xff0c;不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的&#xff…...

JVM 内存结构 详解

内存结构 运行时数据区&#xff1a; Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器&#xff1a; ​ 线程私有&#xff0c;程序控制流的指示器&#xff0c;分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 ​ 每个线程都有一个程序计数…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

NPOI Excel用OLE对象的形式插入文件附件以及插入图片

static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...

计算机基础知识解析:从应用到架构的全面拆解

目录 前言 1、 计算机的应用领域&#xff1a;无处不在的数字助手 2、 计算机的进化史&#xff1a;从算盘到量子计算 3、计算机的分类&#xff1a;不止 “台式机和笔记本” 4、计算机的组件&#xff1a;硬件与软件的协同 4.1 硬件&#xff1a;五大核心部件 4.2 软件&#…...

WPF八大法则:告别模态窗口卡顿

⚙️ 核心问题&#xff1a;阻塞式模态窗口的缺陷 原始代码中ShowDialog()会阻塞UI线程&#xff0c;导致后续逻辑无法执行&#xff1a; var result modalWindow.ShowDialog(); // 线程阻塞 ProcessResult(result); // 必须等待窗口关闭根本问题&#xff1a…...

LLaMA-Factory 微调 Qwen2-VL 进行人脸情感识别(二)

在上一篇文章中,我们详细介绍了如何使用LLaMA-Factory框架对Qwen2-VL大模型进行微调,以实现人脸情感识别的功能。本篇文章将聚焦于微调完成后,如何调用这个模型进行人脸情感识别的具体代码实现,包括详细的步骤和注释。 模型调用步骤 环境准备:确保安装了必要的Python库。…...