canvas初学笔记(万字详细版)

一、canvas元素

基本概念

Canvas 是由 HTML 代码配合高度和宽度属性而定义出的可绘制区域。JavaScript 代码可以访问该区域,类似于其他通用的二维 API,通过一套完整的绘图函数来动态生成图形。注意canvas只是一个存放图形的容器。

基本使用

一、 只有宽高两个属性,也可以使用 css 属性来设置宽高,但是如宽高属性和初始比例不一致,会出现扭曲。所以,建议永远不要使用 css 属性来设置 <canvas> 的宽高。

二、在创建 canvas 画布之后,其坐标系如下

其中橘色是画布区域,x, y 坐标轴的原点在画布的左上方,正方向如图:
在这里插入图片描述

三、 获取渲染上下文(目前只研究2d)

<body>
    <!-- 添加画布 -->
    <canvas id="tutorial"></canvas>
</body>

<script>
    // 获取画布元素
    const canvas = document.getElementById('tutorial');
    //获得 2d 上下文对象
    const ctx = canvas.getContext('2d');
</script>

为了显示兼容性,代码模板一般为:

<body> 
    <canvas id="tutorial" width="300" height="300">
         这里是我的替代内容,在浏览器不支持canvas时渲染(Internet Explorer 8 及之前的版本)
    </canvas>
 </body><script type="text/javascript">
 function draw(){
     var canvas = document.getElementById('tutorial');
     const ctx = canvas.getContext("2d");
     if(!ctx) return;
       //开始代码
 }
 draw();
 </script>

二、canvas颜色、样式

  1. fillStyle属性

    • 作用:

      用于设置或返回用于填充绘画的颜色、渐变或模式。

      默认值:#000

    • 属性值:

      描述
      color指示绘图填充色的 CSS 颜色值。默认值是 #000000。
      gradient用于填充绘图的渐变对象(线性或 放射性)。
      pattern用于填充绘图的 pattern 对象。
  2. fillStyle 与strokeStyle

    如果想要给图形上色,有两个重要的属性可以做到。

    1. fillStyle = color 设置图形的填充颜色
    2. strokeStyle = color 设置图形轮廓的颜色

    注意:

    • color 可以是表示 css 颜色值的字符串(rgba、颜色名、十六进制字符串等)、渐变对象或者图案对象。
    • 默认情况下,线条和填充颜色都是黑色。
    • 一旦设置了 strokeStyle 或者 fillStyle 的值,那么这个新值就会成为新绘制的图形的默认值。如果要给每个图形上不同的颜色,需要重新设置 fillStyle 或 strokeStyle 的值。

三、线段

绘制线段

  • 基本步骤

    下面是需要用到的方法:

    1. beginPath()

      新建一条路径

    2. moveTo(x, y)

      把画笔移动到指定的坐标(x, y)。相当于设置路径的起始点坐标。

    3. lineTo(x, y)

      绘制一条上次画笔停下的位置到该位置的线段

    4. closePath()

      闭合路径之后,图形绘制命令又重新指向到上下文中

    5. stroke()

      通过线条来绘制图形轮廓

    6. fill()

      通过填充路径的内容区域生成实心的图形

  • 实例演示

    <body>
        <canvas id=“tutorial”></canvas>
    </body>
    
    <script>
    function draw(){
    	const canvas = document.getElementById("tutorial");
    	const ctx = canvas.getContext("2d");
    	if(!ctx) return;
    		else{
                // 创建一条新路径
                ctx.beginPath();
                // 定位到起点
                ctx.moveTo(50, 50);
                // 开始绘制线段
                ctx.lineTo(100, 50);
                // 继续绘制线段
                ctx.lineTo(100, 100);
                // 闭合路径
                ctx.closePath();
                // 渲染轮廓
                ctx.stroke();
                // 填充内部
                ctx.fill();
        	}
        }
    
       // 调用
       draw();
       
    </script>
    
属性描述属性值
lineCap设置或返回线条的结束端点样式字符串,默认 “butt”
lineJoin设置或返回两条线相交时,所创建的拐角类型字符串,默认"miter"
lineWidth设置或返回当前的线条宽度正值,默认1.0
miterLimit设置或返回最大斜接长度正值,无默认值
  1. lineWidth

    线宽在其轴线两端各占一半

  2. lineCap

    线条末端样式。

    共有 3 个值:

    • butt:线段末端以方形结束

    • round:线段末端以圆形结束

    • square:线段末端以方形结束,但是增加了一个宽度和线段相同,高度是线段厚度一半的矩形区域。

在这里插入图片描述

  1. lineJoin

    同一个 path 内,设定线条与线条间接合处的样式。

    共有 3 个值 round, bevelmiter

    • round 通过填充一个额外的,圆心在相连部分末端的扇形,绘制拐角的形状。 圆角的半径是线段的宽度。

    • bevel 在相连部分的末端填充一个额外的以三角形为底的区域, 每个部分都有各自独立的矩形拐角。

    • miter(默认) 通过延伸相连部分的外边缘,使其相交于一点,形成一个额外的菱形区域。

      在这里插入图片描述

  2. miterLimit

    miterLimit 属性设置或返回最大斜接长度。

    斜接长度指的是在两条线交汇处内角和外角之间的距离。

    在这里插入图片描述

    提示:只有当 lineJoin 属性为 “miter” 时,miterLimit 才有效。

    边角的角度越小,斜接长度就会越大。

    为了避免斜接长度过长,我们可以使用 miterLimit 属性。

    如果斜接长度超过 miterLimit 的值,边角会以 lineJoin 的 “bevel” 类型来显示(图解 3):

    在这里插入图片描述

四、虚线

setLineDash 方法和 lineDashOffset 属性来制定虚线样式。

  • setLineDash 方法接受一个数组,来指定线段与间隙的交替;

  • lineDashOffset属性设置起始偏移量。

注意: getLineDash() 返回一个包含当前虚线样式,长度为非负偶数的数组。

function draw(){
    var canvas = document.getElementById('tutorial');
    if (!canvas.getContext) return;
    var ctx = canvas.getContext("2d");
 
    ctx.setLineDash([20, 5]);  // [实线长度, 间隙长度]
    ctx.lineDashOffset = 5;
    ctx.strokeRect(50, 50, 210, 210);
}
draw();

五、弧线

绘制弧线

只是在绘制的过程中有所区别,在创建与渲染时与绘制线段前面一致。

两个方法:

(1)arc(x, y, r, startAngle, endAngle, anticlockwise): 以(x, y) 为圆心,以r 为半径,从 startAngle 弧度开始到endAngle弧度结束。anticlosewise 是布尔值,true 表示逆时针,false 表示顺时针(默认是顺时针)。

注意:

I. 度数使用弧度制。

II. 0 弧度是指的 x 轴正方向。

radians=(Math.PI/180)*degrees   //角度转换成弧度

(2)**arcTo(x1, y1, x2, y2, radius)**:由三个点加半径控制

​ 注意:

I. 第一个点是此次绘图的起点

II. 第二个点是(x1, y1)

III. 第三个点是(x2, y2)

IV. 其中,(x1, y1) 分别与起点和 (x2, y2) 的连线(射线)是以 radius 为半径的圆的切线,如下图所示:

在这里插入图片描述

// 首先绘制一个圆心(150, 150)且半径为150的圆
// 绘制圆弧一
ctx.beginPath();
ctx.moveTo(150, 0);
ctx.arcTo(300, 0, 300, 300, 150);
ctx.stroke();

// 绘制圆弧二 
ctx.beginPath();
ctx.moveTo(300, 150);
ctx.arcTo(300, 300, 150, 300, 150);
ctx.stroke();

// 绘制圆弧三
ctx.beginPath();
ctx.moveTo(150, 300);
ctx.arcTo(0, 300, 0, 150, 150);
ctx.stroke();

// 绘制圆弧四
ctx.beginPath();
ctx.moveTo(0, 150);
ctx.arcTo(0, 0, 150, 0, 150);
ctx.stroke();

// 绘制切线圆弧
ctx.beginPath();
ctx.moveTo(150, 0);
ctx.arcTo(300, 0, 300, 150, 50);
ctx.lineTo(300, 150);	// 默认是不会连到这里的
ctx.stroke();

六、矩形

<canvas> 只支持一种原生的图形绘制:矩形。所有其他图形都至少需要生成一种路径 (path)。

canvas 提供了三种方法绘制矩形:

  • **rect(x, y, width, height)**:定义一个矩形的路径,但不会立即绘制出矩形,需要 stroke 或 fill 一下。
  • **fillRect(x, y, width, height)**:绘制一个填充的矩形。
  • **strokeRect(x, y, width, height)**:绘制一个矩形的边框。
  • **clearRect(x, y, widh, height)**:清除指定的矩形区域,然后这块区域会变的完全透明。

**说明:**这 3 个方法具有相同的参数。

  • x, y:指的是矩形的左上角的坐标。(相对于canvas的坐标原点)
  • width, height:指的是绘制的矩形的宽和高。

七、文本

方法描述
fillText()在画布上绘制 填充文本
strokeText()在画布上绘制 描边文本(无填充,只有文本的轮廓)
measureText()返回包含指定文本宽度的对象
const ctx;
function draw(){
    const canvas = document.getElementById('tutorial');
    if (!canvas.getContext) return;
    ctx = canvas.getContext("2d");
    // 设置字体大小和类型
    ctx.font = "100px sans-serif"
    const txt = "天若有情"
    // 绘制填充文本
    ctx.fillText(txt, 10, 100);
    // 绘制描边文本
    ctx.strokeText(txt, 10, 200);
    // 获取文本的宽度
    ctx.measureText(txt).width;
}
draw();

在这里插入图片描述

属性描述
font设置或返回文本内容的当前字体属性
textAlign设置或返回文本内容的当前对齐方式
textBaseline设置或返回在绘制文本时使用的当前文本基线
  • font = value 当前我们用来绘制文本的样式。这个字符串使用和 CSS font 属性相同的语法。 默认的字体是 10px sans-serif

  • textAlign = value 文本对齐选项。 可选的值包括:start, end, left, right or center。 默认值是 start

  • textBaseline = value 基线对齐选项,默认值是 alphabetic。

    • "top":文本的顶部与指定位置对齐。(英文不紧贴)

    • "hanging":文本的悬挂部分(如某些字母的顶部装饰)与指定位置对齐。(英文紧贴)

    • "middle":文本的中间与指定位置对齐。

    • "alphabetic":文本的字母基线与指定位置对齐(默认值)。(英文紧贴)

    • "ideographic":文本的表意文字基线与指定位置对齐,通常用于东亚字符。

    • "bottom":文本的底部与指定位置对齐。(英文不紧贴)

      在这里插入图片描述

  • direction = value 文本方向。可能的值包括:ltr, rtl, inherit。默认值是 inherit

八、图像

绘制步骤:

  1. 创建 img 实例

  2. 设置 img 源

  3. 绘制 img 图像

    drawImage()函数大显神威

    // 绘制image图像的三个步骤
    const img = new Image();
    img.src = "地址";
    ctx.drawImage(img, 0, 0);	// 后面的两个参数是坐标(x, y)
    
    // 方式一:考虑到图片可能从网络加载,防止出现异常,采用以下方式
    const img = new Image();
    img.src = "地址";
    img.onload() = function(){
        // 省略创建canvas的步骤
        ctx.drawImage(img, 0, 0);
    }
    
    // 方式二:直接选中html的img元素,然后赋值给新创建的Image实例对象
    const image = document.getElementById("an_image");
    image.onload = function(){
        const img = new Image();
        img = image;
        ctx.drawImage(img, 0, 0)
    }
    
    //ps:drawImage(图像, x, y, width, height),通过后面宽高两个参数可以实现图像的缩放。
    

    图像的切片

    // 同样采用drawImage()函数,不过这次需要九个参数
    const targetCanvas = document.getElementById('targetCanvas');
    const sourceCanvas = document.getElementById('sourceCanvas'); // 确保这里也有获取sourceCanvas的代码
    
    const sourceCtx = sourceCanvas.getContext('2d');
    const targetCtx = targetCanvas.getContext('2d');
    
    // 指定裁剪的源坐标和尺寸
    const sx = 50; // 源图像裁剪起始 x 坐标
    const sy = 50; // 源图像裁剪起始 y 坐标
    const sWidth = 100; // 裁剪区域的宽度
    const sHeight = 100; // 裁剪区域的高度
    
    // 指定绘制到目标画布的尺寸
    const dWidth = 200; // 目标画布上的图像宽度
    const dHeight = 200; // 目标画布上的图像高度
    
    // 进行切片
    targetCtx.drawImage(
      sourceCanvas,
      sx, sy, sWidth, sHeight,
      0, 0, dWidth, dHeight
    );
    

九、状态

Canvas 的状态就是当前画面应用的所有样式和变形的一个快照。

状态由保存和恢复两个方法,保存:save();恢复:restore()。两方法均无参数。

关于 save()和restore() :Canvas状态存储在栈中,每当save()方法被调用后,当前的状态就被推送到栈中保存。

每当restore()方法被调用后,就会从栈中取出栈顶的那个状态并将其画布恢复成该状态。

一个绘画状态包括:

  • 当前应用的变形(即移动,旋转和缩放)
  • strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation 的值
  • 当前的裁切路径(clipping path

十、转换

  1. translate(x, y)

    作用:用来移动 canvas原点到指定的位置。

    ​ 在做变形之前先保存状态是一个良好的习惯。大多数情况下,调用 restore 方法比手动恢复原先的状态要简单得多。

  2. rotate(angle)

    作用:用来旋转坐标轴。

    ​ 这个方法只接受一个参数:旋转的角度(angle),它是顺时针方向的,以弧度为单位的值。旋转的中心是坐标原点。

  3. scale(x, y)

    作用:增减图形在 canvas 中的像素数目,对形状,位图进行缩小或者放大。

    scale方法接受两个参数。x,y 分别是横轴和纵轴的缩放因子,它们都必须是正值。值比 1.0 小表示缩 小,比 1.0 大则表示放大,值为 1.0 时什么效果都没有。

  4. transform(a, b, c, d, e, f)

    作用:实现图形的矩阵变换。

    参数解释:

    (1)水平缩放因子 a。值大于1表示放大,小于1表示缩小,负值表示水平翻转。

    (2)水平错切因子 b。非零值会导致垂直方向上倾斜和拉伸。

    (3)垂直错切因子 c。非零值会导致水平方向上倾斜和拉伸。

    (4)垂直缩放因子 d。与水平缩放类似,值大于1表示放大,小于1表示缩小,负值表示垂直翻转。

    (5)水平移动量 e。这个值表示在应用缩放和错切后,Canvas将沿x轴移动多少。

    (6)垂直移动量 f。这个值表示在应用缩放和错切后,Canvas将沿y轴移动多少。

    transform 方法不会清除当前路径,但是它会改变之后绘图命令的坐标系。如果你想要保存当前的绘图状态(包括变换矩阵),可以在调用 transform 之前使用 ctx.save(),并在之后使用 ctx.restore() 来恢复状态。

十一、合成

属性描述
globalAlpha设置或返回绘图的当前 alpha 或透明值
globalCompositeOperation设置或返回新图像如何绘制到已有的图像上
  1. globalAlpha属性

    属性值必须是介于 0.0(完全透明) 与 1.0(不透明) 之间的数字。

  2. globalCompositeOperation属性

    描述
    source-over默认。在旧图像上显示新图像。
    source-atop在旧图像顶部显示新图像。新图像位于旧图像之外的部分是不可见的。
    source-in在旧图像中显示新图像。只有旧图像内的新图像部分会显示,旧图像是透明的。
    source-out在旧图像之外显示新图像。只会显示旧图像之外新图像部分,旧图像是透明的。
    destination-over在新图像上方显示旧图像。
    destination-atop在新图像顶部显示旧图像。新图像之外的旧图像部分不会被显示。
    destination-in在新图像中显示旧图像。只有新图像内的旧图像部分会被显示,新图像是透明的。
    destination-out在新图像外显示旧图像。只有新图像外的旧图像部分会被显示,新图像是透明的。
    lighter显示新图像 + 旧图像。
    copy显示新图像。忽略旧图像。
    xor使用异或操作对新图像与旧图像进行组合。

十二、裁剪路径

clip()

作用:从原始画布中剪切任意形状和尺寸。只显示裁剪路径内的区域,裁剪路径外的区域会被隐藏。

–问:关于如何确定裁剪区域呢?

–答:紧挨着的上一次的闭合的绘图区域,如何上次的绘图区域没有闭合,那么系统会自动设置为闭合状态。

注意:一旦剪切了某个区域,则所有之后的绘图都会被限制在被剪切的区域内(不能访问画布上的其他区域)。也可以在使用 clip() 方法前通过使用 save() 方法对当前画布区域进行保存,并在以后的任意时间对其进行恢复(通过 restore() 方法)

十三、动画

我们看到的都是<script>代码加载完成之后的图像,所以想要在js的循环中实现动画效果是不可能的。

所以一般就使用函数回调的形式实现动画效果。

一般用到以下三个方法:

  1. setInterval()

    执行多次,效果不如requestAnimationFrame()函数平滑

  2. setTimeout()

    执行一次

  3. requestAnimationFrame()

    这个方法在浏览器进行重绘时执行,并且回调后面参数中的那个函数。

动画演示一:

<!-- 绘制简易的星球运转图(仅包括太阳、地球和月亮)-->

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

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>星球公转演示</title>
</head>

<body>
  <canvas id="solar_system" width="300px" height="300px"></canvas>
  <canvas id="clock" width="300px" height="300px"></canvas>
</body>
<script>
// 声明变量
const sun = new Image();
const earth = new Image();
const moon = new Image();

// 初始化函数,
function init() {
    // 获取画布
    const canvas = document.querySelector("#solar");
    ctx = canvas.getContext("2d");
    // 判断浏览器是否支持(能否正确获取到)
    if(ctx){
       return; 
    }else{
    // 初始化需要的图片
    sun.src = "images/sun.png";
    earth.src = "images/earth.png";
    moon.src = "images/moon.png";
    }

}

// 绘制函数
function draw() {
    // 空所有的内容
    ctx.clearRect(0, 0, 300, 300);
    
    // 绘制太阳
    ctx.drawImage(sun, 0, 0, 300, 300);

    // 保存初始状态
    ctx.save();
    ctx.translate(150, 150);

    // 制earth轨道
    ctx.beginPath();
    ctx.strokeStyle = "rgba(255,255,0,0.5)";
    ctx.arc(0, 0, 100, 0, 2 * Math.PI)
    ctx.stroke()

    const time = new Date();
    // 制地球
    ctx.rotate(2 * Math.PI / 60 * time.getSeconds() + 2 * Math.PI / 60000 * time.getMilliseconds())
    ctx.translate(100, 0);
    ctx.drawImage(earth, -18, -18, 36, 36)

    // 制月球轨道
    ctx.beginPath();
    ctx.strokeStyle = "rgba(255,255,255,0.3)";
    ctx.arc(0, 0, 40, 0, 2 * Math.PI);
    ctx.stroke();

    // 制月球
    ctx.rotate(2 * Math.PI / 6 * time.getSeconds() + 2 * Math.PI / 6000 * time.getMilliseconds());
    ctx.translate(40, 0);
    ctx.drawImage(moon, -6, -6, 12, 12);
    ctx.restore();

    requestAnimationFrame(draw);
}


// 调用
init();
// 可以使用Promise进行三张图片是否加载成功的判断,这里为了演示简单,就直接调用
draw();

</script>

</html>

动画演示二:

<!-- 绘制时钟演示 -->

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

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>时钟</title>
</head>

<body>
  <canvas id="clock" width="300px" height="300px"></canvas>
</body>
<script>
  // 获取时钟画布
  const clock = document.getElementById("clock");
  const ctx_clock = clock.getContext("2d");


  // 绘制时钟
  // 变换原点
  ctx_clock.translate(150, 150);

  // 初始化表盘
  function init_clock() {

    // 保存当前状态
    ctx_clock.save();

    // 绘制表盘线条

    for (let i = 1; i <= 60; i++) {
      // 每个小时要有5个间隔
      ctx_clock.rotate(Math.PI / 30);

      // 整点处的线条与两个整点之间的线条应有区别
      if (i % 5 == 0) {
        ctx_clock.strokeStyle = "rgb(255,0,0)";
        ctx_clock.lineWidth = 3;
      } else {
        ctx_clock.strokeStyle = "rgb(0,0,255)";
        ctx_clock.lineWidth = 1.5;
      }
      ctx_clock.beginPath();
      ctx_clock.moveTo(100, 0);
      ctx_clock.lineTo(120, 0);
      ctx_clock.stroke();
    }

    // 绘制表盘圆周;
    ctx_clock.strokeStyle = "rgb(0,0,0)";
    ctx_clock.lineWidth = 1;
    ctx_clock.beginPath();
    ctx_clock.arc(0, 0, 130, 0, 2 * Math.PI);
    ctx_clock.stroke();


    // 恢复状态
    ctx_clock.restore();
  }

  // 绘制指针
  function draw_hands() {
    ctx_clock.save();

    // 清除画布
    ctx_clock.clearRect(-150, -150, 300, 300);

    // 旋转x轴,使其正方向直线12
    ctx_clock.rotate(3 * Math.PI / 2);

    // 初始化表盘
    init_clock();

    // 获取当前时间
    const time = new Date();

    // 绘制时针
    // 保存
    ctx_clock.save();
    const hours = time.getHours() % 12; // 转换为12小时制
    ctx_clock.rotate(2 * Math.PI / 12 * hours + 2 * Math.PI / 12 / 60 * time.getMinutes());
    ctx_clock.lineWidth = 3;
    ctx_clock.beginPath();
    ctx_clock.moveTo(-5, 0);
    ctx_clock.lineTo(60, 0);
    ctx_clock.stroke();
    // 恢复
    ctx_clock.restore();


    // 绘制分针
    // 保存表盘方向
    ctx_clock.save();
    ctx_clock.strokeStyle = "green";
    ctx_clock.lineWidth = 2;
    ctx_clock.rotate(2 * Math.PI / 60 * time.getMinutes() + 2 * Math.PI / 60 / 60 * time.getSeconds());
    ctx_clock.beginPath();
    ctx_clock.moveTo(-5, 0);
    ctx_clock.lineTo(75, 0);
    ctx_clock.stroke();
    // 恢复
    ctx_clock.restore();

    // 绘制秒针
    // 保存
    ctx_clock.save();
    ctx_clock.strokeStyle = "red";
    ctx_clock.rotate(2 * Math.PI / 60 * time.getSeconds());
    ctx_clock.beginPath();
    ctx_clock.moveTo(-5, 0);
    ctx_clock.lineTo(90, 0);
    ctx_clock.stroke();
    // 恢复
    ctx_clock.restore();
    
    // 恢复最初始
    ctx_clock.restore();
    requestAnimationFrame(draw_hands);
  }

  draw_hands();

</script>

</html>
  • 32
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值