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

【第2章 绘制】2.8 线段

文章目录

  • 前言
  • 一、线段与像素边界
  • 二、网格的绘制
  • 三、坐标轴的绘制
  • 四、橡皮筋式的线条绘制
  • 五、虚线的绘制
  • 六、扩展 CanvasRenderingContext2D
  • 七、线段端点与连接点的绘制
    • 7.1 线段端点
    • 7.2 线段连接点
    • 7.3 线段绘制有关的属性


前言

Canvas 绘图环境提供了两个可以用来创建线性路径的方法:moveTo() 与 lineTo()。要使线性路径(线段)出现在 canvas 之中,必须在创建路径之后调用 stroke() 方法,这样才可以在 canvas 之中画出两条线段来。

不过,如果你仔细看,就会发现有个地方很奇怪。尽管代码在绘制之前已经先将 lineWidth 属性设置成 1 像素了,但是,上边那条线段画出来却是 2 个像素宽。下一小节将解释出现这种现象的原因。

在这里插入图片描述

// 如果要绘制一条真正1像素宽的线段,必须将该线段绘制在某两个像素之间的那个像素中,而不能将它绘制在两个像素的交界处
const canvas = document.getElementById('canvas'),context = canvas.getContext('2d')context.lineWidth = 1context.beginPath()
context.moveTo(20, 20)
context.lineTo(200, 20)
context.stroke()context.beginPath()
context.moveTo(20, 50.5)
context.lineTo(200, 50.5)
context.stroke()

一、线段与像素边界

如果你在某2个像素的边界处绘制一条 1 像素宽的线段,那么该线段实际上会占据 2 个像素的宽度。

因为在像素边界绘制一条 1 像素宽的垂直线段,那么 canvas 的绘图环境对象会试着将半个像素画在边界中线的右边,将另外半个像素画在边界中线的左边。

然而,在一个整像素的范围内绘制半个像素宽的线段是不可能的,所以左右两个方向上的半像素都会被扩展为 1 个像素,所以实际显示为 2 像素宽的线段。

在这里插入图片描述

我们可以将我们绘制的坐标向右偏移 0.5 像素,这样的话,中线左右两端的那半个像素就不会再延伸了,它们合起来恰好占据 1 个像素的宽度。

所以,如果要绘制一条真正 1 像素宽的线段,你必须将该线段绘制在某两个像素之间的那个像素中。

在这里插入图片描述


二、网格的绘制

这段代码不仅绘制在两个像素之间的像素上,而且绘制出的线段仅有0.5像素宽,虽说Canvas没有明文规定,不过所有的浏览器都实现了“抗锯齿”技术,以便创造出“亚像素”线段的绘制效果。

在这里插入图片描述

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>2-13-绘制网格</title><style>html,body {margin: 0;padding: 0;width: 100%;height: 100%;}#canvas {display: block;}</style></head><body><canvas id="canvas" width="600" height="400"> canvas not supported </canvas><script>const canvas = document.getElementById('canvas'),context = canvas.getContext('2d')// Functions ......const drawGrid = (context, color, stepX, stepY) => {context.strokeStyle = colorcontext.lineWidth = 0.5// 绘制垂直线for (let i = stepX + 0.5; i < canvas.width; i += stepX) {context.beginPath()context.moveTo(i, 0)context.lineTo(i, canvas.height)context.stroke()}// 绘制水平线for (let i = stepY + 0.5; i < canvas.height; i += stepY) {context.beginPath()context.moveTo(0, i)context.lineTo(canvas.width, i)context.stroke()}}// Initialization......drawGrid(context, 'lightgray', 10, 10)</script></body>
</html>

三、坐标轴的绘制

绘制具有x,y轴的直角坐标轴,并在轴上绘制相应的刻度条。

通过常量来计算坐标轴属性,例如坐标轴宽高,刻度线间距等等,设置好这些属性后,剩余代码基本上都在按照beginPath()、moveTo()、lineTo()、stroke()的顺序绘制坐标轴和其刻度线。

在这里插入图片描述

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>2-14-绘制坐标轴</title><style>html,body {margin: 0;padding: 0;width: 100%;height: 100%;}#canvas {display: block;}</style></head><body><canvas id="canvas" width="600" height="400"> canvas not supported </canvas><script>const canvas = document.getElementById('canvas'),context = canvas.getContext('2d')const AXIS_MARGIN = 40,AXIS_ORGIN = { x: AXIS_MARGIN - 0.5, y: canvas.height - AXIS_MARGIN + 0.5 },AXIS_TOP = AXIS_MARGIN,AXIS_RIGHT = canvas.width - AXIS_MARGIN,HORIZONTAL_TICK_SPACING = 10,VERTICAL_TICK_SPACING = 10,AXIS_WIDTH = AXIS_RIGHT - AXIS_ORGIN.x,AXIS_HEIGHT = AXIS_ORGIN.y - AXIS_TOP,HORIZONTAL_TICK_COUNT = Math.floor(AXIS_WIDTH / HORIZONTAL_TICK_SPACING),VERTICAL_TICK_COUNT = Math.floor(AXIS_HEIGHT / VERTICAL_TICK_SPACING)// 轴线的颜色和线宽const AXIS_COLOR = 'blue',AXIS_LINEWIDTH = 1const TICK_WIDTH = 10,TICK_LINEWIDTH = 0.5,TICK_COLOR = 'navy'// Functions ......// 绘制网格const drawGrid = (context, color, stepX, stepY) => {context.strokeStyle = colorcontext.lineWidth = 0.5// 绘制垂直线for (let i = stepX + 0.5; i < canvas.width; i += stepX) {context.beginPath()context.moveTo(i, 0)context.lineTo(i, canvas.height)context.stroke()}// 绘制水平线for (let i = stepY + 0.5; i < canvas.height; i += stepY) {context.beginPath()context.moveTo(0, i)context.lineTo(canvas.width, i)context.stroke()}}// 绘制横向坐标轴const drawHorizontalAxis = () => {context.beginPath()context.moveTo(AXIS_ORGIN.x, AXIS_ORGIN.y)context.lineTo(AXIS_RIGHT, AXIS_ORGIN.y)context.stroke()}// 绘制纵向坐标轴const drawVerticalAxis = () => {context.beginPath()context.moveTo(AXIS_ORGIN.x, AXIS_ORGIN.y)context.lineTo(AXIS_ORGIN.x, AXIS_TOP)context.stroke()}// 绘制横向坐标轴刻度const drawHorizontalAxisTicks = () => {for (let i = 1; i < HORIZONTAL_TICK_COUNT; i++) {let deltaYif (i % 5 === 0) {deltaY = TICK_WIDTH} else {deltaY = TICK_WIDTH / 2}context.beginPath()context.moveTo(AXIS_ORGIN.x + i * HORIZONTAL_TICK_SPACING, AXIS_ORGIN.y)context.lineTo(AXIS_ORGIN.x + i * HORIZONTAL_TICK_SPACING, AXIS_ORGIN.y - deltaY)context.stroke()}}// 绘制纵向坐标轴刻度const drawVerticalAxisTicks = () => {for (let i = 1; i < VERTICAL_TICK_COUNT; i++) {let deltaYif (i % 5 === 0) {deltaY = TICK_WIDTH} else {deltaY = TICK_WIDTH / 2}context.beginPath()context.moveTo(AXIS_ORGIN.x, AXIS_ORGIN.y - i * VERTICAL_TICK_SPACING)context.lineTo(AXIS_ORGIN.x + deltaY, AXIS_ORGIN.y - i * VERTICAL_TICK_SPACING)context.stroke()}}const drawAxes = () => {context.save()context.strokeStyle = AXIS_COLORcontext.lineWidth = AXIS_LINEWIDTHdrawHorizontalAxis()drawVerticalAxis()context.lineWidth = TICK_LINEWIDTHcontext.strokeStyle = TICK_COLORdrawHorizontalAxisTicks()drawVerticalAxisTicks()context.restore()}// Initialization......drawGrid(context, 'lightgray', 10, 10)drawAxes()</script></body>
</html>

四、橡皮筋式的线条绘制

用户可以通过拖拽鼠标的方式在 canvas 的背景上互动式地画线
在这里插入图片描述

<!DOCTYPE html>
<!-- 定义 HTML 文档的语言为英语 -->
<html lang="en"><head><!-- 设置文档的字符编码为 UTF-8 --><meta charset="UTF-8" /><!-- 设置视口,确保页面在不同设备上正确显示 --><meta name="viewport" content="width=device-width, initial-scale=1.0" /><!-- 设置页面标题 --><title>2-15-橡皮筋式的线条绘制</title><style>/* 设置页面的背景颜色为浅灰色 */body {background: #eee;}/* 设置控制区域的位置,绝对定位在页面左上角 */#controls {position: absolute;left: 25px;top: 25px;}/* 设置画布的样式,包括背景颜色、鼠标指针样式、外边距和阴影 */#canvas {background: #fff;cursor: pointer;margin-left: 10px;margin-top: 10px;box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.5);}</style></head><body><!-- 创建一个画布元素,指定宽度和高度,若浏览器不支持则显示提示信息 --><canvas id="canvas" width="600" height="400">canvas not supported in your brower, please use a new version of brower</canvas><!-- 创建一个控制区域,包含颜色选择框、辅助线复选框和清除按钮 --><div id="controls">Stroke color:<!-- 创建一个下拉列表,用于选择线条的颜色,默认选择红色 --><select name="strokeStyleSelect" id="strokeStyleSelect"><option value="red" selected>red</option><option value="green">green</option><option value="blue">blue</option><option value="orange">orange</option></select>Guidewires:<!-- 创建一个复选框,用于控制辅助线的显示,默认勾选 --><input type="checkbox" name="guidewireCheckbox" id="guidewireCheckbox" checked /><!-- 创建一个按钮,点击后可清除画布上的所有内容 --><input type="button" value="Erase All" id="eraseAllButton" /></div><script>// 获取画布元素、绘图上下文以及各个控制元素const canvas = document.getElementById('canvas'),context = canvas.getContext('2d'),strokeStyleSelect = document.getElementById('strokeStyleSelect'),guidewireCheckbox = document.getElementById('guidewireCheckbox'),eraseAllButton = document.getElementById('eraseAllButton')// 定义变量,用于保存绘图表面的图像数据、鼠标按下的位置、橡皮筋矩形、拖拽状态以及辅助线是否显示let drawingSurfaceImageData,mousedown = {},rubberbandRect = {},dragging = false,guidewires = guidewireCheckbox.checked// 定义绘制网格的函数// context: 绘图上下文// color: 网格线的颜色// stepX: 垂直网格线的间隔// stepY: 水平网格线的间隔const drawGrid = (context, color, stepX, stepY) => {context.save()// 设置网格线的颜色context.strokeStyle = color// 设置网格线的宽度context.lineWidth = 0.5// 绘制垂直线for (let i = stepX + 0.5; i < canvas.width; i += stepX) {// 开始一个新的路径context.beginPath()// 移动画笔到起始点context.moveTo(i, 0)// 绘制直线到终点context.lineTo(i, canvas.height)// 绘制路径context.stroke()}// 绘制水平线for (let i = stepY + 0.5; i < canvas.height; i += stepY) {// 开始一个新的路径context.beginPath()// 移动画笔到起始点context.moveTo(0, i)// 绘制直线到终点context.lineTo(canvas.width, i)// 绘制路径context.stroke()}context.restore()}/*** 将窗口坐标转换为画布坐标** 此函数的目的是将鼠标点击等事件发生的窗口坐标转换为画布上的坐标* 这是因为画布可能在页面中定位,其实际位置与画布元素在页面中的显示位置有关** @param {number} x - 窗口中的x坐标* @param {number} y - 窗口中的y坐标* @returns {Object} - 在画布坐标系中的坐标,包含x和y属性*/const windowToCanvas = (x, y) => {// 获取画布在窗口中的边界矩形,包括任何定位、边距、边框和填充const bbox = canvas.getBoundingClientRect()// 根据画布的实际尺寸和在窗口中的位置,将窗口坐标转换为画布坐标return {x: x - bbox.left * (canvas.width / bbox.width),y: y - bbox.top * (canvas.height / bbox.height),}}// 保存当前绘图表面的图像数据const saveDrawingSurface = () => {drawingSurfaceImageData = context.getImageData(0, 0, canvas.width, canvas.height)}// 恢复之前保存的绘图表面的图像数据const restoreDrawingSurface = () => {context.putImageData(drawingSurfaceImageData, 0, 0)}// 更新橡皮筋矩形的位置和大小// loc: 当前鼠标的坐标位置,包含 x 和 y 属性const updateRubberbandRecttangle = (loc) => {// 计算并设置橡皮筋矩形的宽度,确保宽度为正数rubberbandRect.width = Math.abs(loc.x - mousedown.x)// 计算并设置橡皮筋矩形的高度,确保高度为正数rubberbandRect.height = Math.abs(loc.y - mousedown.y)// 根据鼠标位置确定橡皮筋矩形的左边界if (loc.x > mousedown.x) rubberbandRect.left = mousedown.xelse rubberbandRect.left = loc.x// 根据鼠标位置确定橡皮筋矩形的上边界if (loc.y > mousedown.y) rubberbandRect.top = mousedown.yelse rubberbandRect.top = loc.y}// 绘制橡皮筋形状(这里是一条直线)// loc: 当前鼠标的坐标位置,包含 x 和 y 属性const drawRubberbandShape = (loc) => {// 开始一个新的路径context.beginPath()// 移动画笔到鼠标按下的位置context.moveTo(mousedown.x, mousedown.y)// 绘制直线到当前鼠标位置context.lineTo(loc.x, loc.y)// 绘制路径context.stroke()}// 更新橡皮筋的状态,包括矩形和形状// loc: 当前鼠标的坐标位置,包含 x 和 y 属性const updateRubberband = (loc) => {// 更新橡皮筋矩形的位置和大小updateRubberbandRecttangle(loc)// 绘制橡皮筋形状drawRubberbandShape(loc)}// 绘制水平辅助线// y: 辅助线的垂直位置const drawHorizontalLine = (y) => {// 开始一个新的路径context.beginPath()// 移动画笔到起始点context.moveTo(0, y + 0.5)// 绘制直线到终点context.lineTo(canvas.width, y + 0.5)// 绘制路径context.stroke()}// 绘制垂直辅助线// x: 辅助线的水平位置const drawVerticalLine = (x) => {// 开始一个新的路径context.beginPath()// 移动画笔到起始点context.moveTo(x + 0.5, 0)// 绘制直线到终点context.lineTo(x + 0.5, canvas.height)// 绘制路径context.stroke()}// 绘制辅助线// x: 鼠标的水平位置// y: 鼠标的垂直位置const drawGuidewires = (x, y) => {// 保存当前绘图状态context.save()// 设置辅助线的颜色和透明度context.strokeStyle = 'rgba(0, 0, 230, 0.4)'// 设置辅助线的宽度context.lineWidth = 0.5// 绘制水平辅助线drawHorizontalLine(y)// 绘制垂直辅助线drawVerticalLine(x)// 恢复之前保存的绘图状态context.restore()}// 画布的鼠标按下事件处理函数canvas.onmousedown = (e) => {// 将鼠标在窗口中的坐标转换为画布坐标const loc = windowToCanvas(e.clientX, e.clientY)// 阻止事件的默认行为e.preventDefault()// 保存当前绘图表面的图像数据saveDrawingSurface()// 记录鼠标按下的 x 坐标mousedown.x = loc.x// 记录鼠标按下的 y 坐标mousedown.y = loc.y// 设置拖拽状态为 truedragging = true}// 画布的鼠标移动事件处理函数canvas.onmousemove = (e) => {let loc// 如果处于拖拽状态if (dragging) {// 阻止事件的默认行为e.preventDefault()// 将鼠标在窗口中的坐标转换为画布坐标loc = windowToCanvas(e.clientX, e.clientY)// 恢复之前保存的绘图表面的图像数据restoreDrawingSurface()// 更新橡皮筋的状态updateRubberband(loc)// 绘制橡皮筋矩形context.strokeRect(rubberbandRect.left, rubberbandRect.top, rubberbandRect.width, rubberbandRect.height)// 如果辅助线开关打开,则绘制辅助线if (guidewires) drawGuidewires(loc.x, loc.y)}}// 画布的鼠标释放事件处理函数canvas.onmouseup = (e) => {// 将鼠标在窗口中的坐标转换为画布坐标let loc = windowToCanvas(e.clientX, e.clientY)// 恢复之前保存的绘图表面的图像数据restoreDrawingSurface()// 更新橡皮筋的状态updateRubberband(loc)// 设置拖拽状态为 falsedragging = false}// 清除按钮的点击事件处理函数eraseAllButton.onclick = () => {// 清除画布上的所有内容context.clearRect(0, 0, canvas.width, canvas.height)// 重新绘制网格drawGrid(context, 'lightgray', 10, 10)// 保存当前绘图表面的图像数据saveDrawingSurface()}// 颜色选择框的改变事件处理函数strokeStyleSelect.onchange = () => {// 设置绘图上下文的线条颜色为选择的颜色context.strokeStyle = strokeStyleSelect.value}// 辅助线复选框的改变事件处理函数guidewireCheckbox.onchange = () => {// 根据复选框的状态更新辅助线开关guidewires = guidewireCheckbox.checked}// 初始化操作// 设置绘图上下文的线条颜色为当前选择的颜色context.strokeStyle = strokeStyleSelect.value// 绘制网格drawGrid(context, 'lightgray', 10, 10)</script></body>
</html>

五、虚线的绘制

Canvas 提供了setLineDash() 方法用于在描线时使用虚线模式,它使用一组值来指定描述模式的线和间隙的交替长度。

在这里插入图片描述

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>2-17-虚线、线段断点与连接点</title><style>html,body {margin: 0;padding: 0;width: 100%;height: 100%;}#canvas {display: block;}</style></head><body><canvas id="canvas"> canvas not supported </canvas><script>const canvas = document.getElementById('canvas'),context = canvas.getContext('2d')let y = 40.5 // 绘制真正的1像素线段时,需要加上0.5// 设置canvas大小canvas.width = window.innerWidthcanvas.height = window.innerHeight// Functions ......// 绘制虚线const drawDashedLine = (pattern, lineCap) => {context.beginPath()context.setLineDash(pattern)context.lineCap = lineCap || 'butt'context.moveTo(40, y)context.lineTo(300, y)context.stroke()y += 20}// Initialization......drawDashedLine([])drawDashedLine([1, 1])drawDashedLine([10, 10])drawDashedLine([20, 5])drawDashedLine([15, 3, 3, 3], 'round')drawDashedLine([20, 3, 3, 3, 3, 3, 3, 3], 'square')drawDashedLine([12, 3, 3]) // 等于 [12, 3, 3, 12, 3, 3]</script></body>
</html>

六、扩展 CanvasRenderingContext2D

我们通过CanvasRenderingContext2D.prototype的属性,来增加新的属性和方法。

不过,在扩展绘图环境对象的功能时,最好还是小心一些。如果你在绘图环境对象中新加入了一个 drawDashedLine() 方法对其进行扩展,而不巧的是,将来的 CanvasRenderingContext2D 规范恰好也增加了一个 drawDashedLineTo() 方法,这样一来,你的绘图环境中加入的那些功能,就有可能对官方的 CanvasRenderingContext2D 规范所引入的新功能造成干扰。

var canvas = document.getElementById('canvas'),context = canvas.getContext('2d');
//添加新属性
CanvasRenderingContext2D.prototype.newProperty = '新属性';//添加新方法
CanvasRenderingContext2D.prototype.newFunction = function () {console.log('新方法');
};//测试新属性
console.log(context.newProperty);
//测试新方法
context.newFunction();

七、线段端点与连接点的绘制

7.1 线段端点

线段端点,是指线段两端的位置,也就是“线帽(line cap)”的样子。如图所示,下面是三种线帽,分别是“不加帽子”、“加个圆帽”、“加个方帽”。

在绘图环境中,控制线段端点的属性正好叫做lineCap,从上到下这三种样式分别对应“butt”、“round”、“square”。

在这里插入图片描述

7.2 线段连接点

绘制线段或矩形时,你可以控制两条线段连接处的那个拐弯,也就是线段连接点(line join)应该怎么画,在绘图环境中,由lineJion属性控制。

从上到下lineJoin这三种属性分别是“round(圆角)”、“bevel(平角)”、“miter(尖角)”。

在这里插入图片描述

  • round:在bevel的基础上补充一条圆弧
  • bevel:用一条直线连接两个拐角外部的点
  • miter(默认):沿两条线外边缘延长到相交至一个角,受miterLimit属性的影响,超过该值,则以bevel属性替代

在这里插入图片描述

当我们为lineJoin使用miter属性的时候,我们可以指定一个miterLimit属性,它表示斜接线(miter)长度与二分之一宽度的比值,斜接线长度计量方式如图所示。如果斜接线太长,浏览器就会以 bevel 样式来处理线段连接点。

在这里插入图片描述


7.3 线段绘制有关的属性

属性描述
lineGap设置或返回线条的结束端点样式
lineJoin设置或返回两条线相交时,所创建的拐角类型
lineWidth设置或返回当前的线条宽度
miterLimit设置或返回最大斜接长度
lineDashOffset设置或返回虚线绘制的偏移距离

相关文章:

  • 有关于常量的一节知识
  • 设计模式26——解释器模式
  • 腾控产品在油田间抽节能中的应用
  • 苍穹外卖 09 WebSocket来单提醒客户催单营业额统计
  • 第二章 1.5 数据采集安全风险防范之数据采集安全管理
  • Three.js 直线拐角自动圆角化(圆弧转弯)
  • electron开发百度桌面应用demo及如何打包应用
  • LabVIEW双光子荧光成像软件开发
  • 智能指针的使用及原理
  • 大模型-高通性能测试工具介绍-1
  • 基本面高股息策略
  • ros2--串口通信
  • Java开发经验——阿里巴巴编码规范实践解析4
  • 封装一个小程序选择器(可多选、单选、搜索)
  • windows安装启动elasticsearch
  • 数据拟合实验
  • TechCrunch 最新文章 (2025-05-28)
  • 【Halcon】 affine_trans_image 算子详解
  • 构建安全高效的邮件网关ngx_mail_ssl_module
  • 【iOS】源码阅读(五)——类类的结构分析
  • 百度网站怎么做信息/最佳的资源搜索引擎
  • 做网站哪个平台好/搜狗网址
  • 淮阳住房城乡建设局网站/如何查一个关键词的搜索量
  • wordpress jenn 主题/sem优化服务公司
  • 网站假设公司排名/网络营销官网
  • 局域网网站建设软件/新品推广活动方案