JS学习笔记(十四)动画和Canvas图形

JS学习笔记(十四)动画和Canvas图形

一、使用requestAnimationFrame

1.1 早期定时动画

早期基本用 setInterval()来控制动画的执行

(function() {
    
    
  function updateAnimations() {
    
    
    doAnimation1();
    doAnimation2();
    // 其他任务
  }
  setInterval(updateAnimations,100);
})();

       updateAnimations () 会周期性运行注册的动画任务,并反映出每个任务的变化。

       无论setInterval()还是setTimeout() 都不能保证时间精度。作为第二个参数的样式只能保证合适会把代码添加到浏览器的任务队列,不能保证添加到队列就会立即运行

       

1.2 requestAnimationFrame

       用来通知JS代码要执行动画了,接收一个参数,此参数是一个要在重绘屏幕前调用的函数。该函数就是修改DOM样式以反映下一次重绘有何变化的地方。为实现动画循环,可以把多个requestAnimationFrame()调用串联起来。传给requestAnimationFrame()的函数也可以是一个参数,此参数是一个DOMHighResTimeStamp的实例,表示下次重绘的时间。

       因为requestAnimationFrame()只会调用一次传入的函数,所以每次更新用户界面时需要再手动调用一次。

       

1.3 cancelAnimationFrame

       requestAnimationFrame()也返回一个请求ID,可以通过cancelAnimationFrame() 取消重绘任务

       

1.4 通过requestAnimationFrame节流

       支持requestAnimationFrame()的浏览器实际上会暴露出作为钩子的回调队列。钩子就是浏览器再执行下一次重绘之前的一个点。这个回调队列是一个可修改的函数列表,包含应该在重绘之前调用的函数。

​ 将回调限制为不超过50毫秒执行一次:

let enable = true;
function expensiveOperation() {
    
    
  console.log("invoked at",Date.now);
}

window.addEventListener("scroll",() => {
    
    
  if(enable) {
    
    
    enable = false;
    window.requestAnimationFrame(expensiveOperation);
    window.setTimeout(() => enable = true, 50);
  }
});

       计时器用来限制实际的操作执行间隔,requestAnimationFrame 控制在浏览器的哪个渲染周期中执行

       

1.5 缓动动画

       缓动动画就是让元素运动速度有所变化,常见的有让速度慢慢停下来

思路:

  1. 让盒子每次移动的距离慢慢变小,速度就会慢慢落下来
  2. 核心算法:(目标值-现在的位置)/10 作为每次移动距离的步长
  3. 停止的条件:让当前的盒子位置等于目标位置就停止
function animate(obj, target) {
    
    
  //先清除以前的定时器,只保留当前的一个定时器执行
  clearInterval(obj.timer);
  obj.timer = setInterval(function () {
    
    
    // 步长值写到定时器里面
    var step = (target - obj.offsetLeft) / 10;
    if (obj.offsetLeft >= target) {
    
    
      //停止
      clearInterval(obj.timer);
    }
    obj.style.left = obj.offsetLeft + step + 'px';
  }, 30);
}

       

1.5.1 缓动动画原理

step进阶一:

//把步长改成整数,不要出现小数
var step = Math.ceil((target - obj.offsetLeft) / 10);

不修改的话可能因为小数问题导致最终到不了target的位置

       

step进阶二:

向前向后都满足了

function animate(obj, target) {
    
    
  //先清除以前的定时器,只保留当前的一个定时器执行
  clearInterval(obj.timer);
  obj.timer = setInterval(function () {
    
    
    //把步长改成整数,不要出现小数
    // var step = Math.ceil((target - obj.offsetLeft) / 10);
    var step = (target - obj.offsetLeft) / 10;
    step = step > 0 ? Math.ceil(step) : Math.floor(step);
    if (obj.offsetLeft >= target) {
    
    
      //停止
      clearInterval(obj.timer);
    }
    obj.style.left = obj.offsetLeft + step + 'px';
  }, 30);
}

       

1.5.2 缓动动画添加回调函数

btn800.addEventListenr('click',function() {
    
    
    animate(span,800,function() {
    
    });//函数作为回调函数
})

function animate(obj, target) {
    
    
  //先清除以前的定时器,只保留当前的一个定时器执行
  clearInterval(obj.timer);
  obj.timer = setInterval(function () {
    
    
    //把步长改成整数,不要出现小数
    // var step = Math.ceil((target - obj.offsetLeft) / 10);
    var step = (target - obj.offsetLeft) / 10;
    step = step > 0 ? Math.ceil(step) : Math.floor(step);
    if (obj.offsetLeft >= target) {
    
    
      //停止
      clearInterval(obj.timer);
      // 回调函数写到定时器结束里面
      if(callback) {
    
    
          //调用函数
          callback();
      }
    }
    obj.style.left = obj.offsetLeft + step + 'px';
  }, 30);
}

       

二、基本画布功能

绘制<canvas>元素至少要设置width和height属性

<canvas id="drawing" width="200" height="200">hhh</canvas>

注意:width 和 height 没有单位

       

2.1 getContext()

       在画布上绘制图形,首先要取得绘画上下文。使用getContext()获取绘画上下文的引用。对于平面图形,需要给该方法传入参数“2d”,表示要获取的2d上下文对象

let drawing = document.getElementById("drawing");

// 确保浏览器支持canvas
if (drawing.getContext) {
    
    
  let context = drawing.getContext('2d');
  //...
}

       

2.2 toDataURL()

       可使用该方法导出canvas元素上的图像,接收一个参数:要生成图像的MIME类型

       

三、2D绘画上下文

       2D上下文的坐标原点(0,0)在 <canvas>元素的左上角。

3.1 填充和描边

  • 填充:fillStyle 属性
  • 描边:strokeStyle 属性

       这两个属性可以是字符串、渐进对象或图案对象,默认值为“#000000”。

let drawing = document.getElementById("drawing");

// 确保浏览器支持canvas
if (drawing.getContext) {
    
    
  let context = drawing.getContext('2d');
  context.strokeStyle = "red";
  context.fillStyle = "#0000ff";
}

       

3.2 绘制矩形

方法有:

  • fillRect():用于以指定的颜色在画布上绘制并填充矩形,填充的颜色使用fillStyle属性指定
  • strokeRect():通过使用strokeStyle属性指定颜色绘制矩形轮廓
  • clearRect():可以擦除画布中某个区域。用于把绘画上下文中的某个区域变透明

       都接收4个参数:矩形x坐标、矩形y坐标、矩形宽度和矩形高度。单位都是像素

       

3.3 绘制路径

3.3.1 步骤

  1. 首先调用 beginPath() 方法以表示要开始绘制新路径
  2. 然后调用如下方法绘制路径:
    • arc(x,y,radius,startAngle,endAngle,counterclockwise):以坐标(x,y)为圆心,radius为半径绘制一条弧线,起始角度为startAngle,结束角度为endAngle。counterclockwise 表示是否逆时针计算起始角度和结束角度
    • arcTo(x1,y1,x2,y2,radius):以给定半径radius,经由(x1,y1)绘制一条从上一点到(x2,y2)的弧线
    • bezierCurveTo(c1x,c1y,c2x,c2y,x,y):以(c1x,c1y)和(c2x,c2y)为控制点,绘制一条从上一点到(x,y)的弧线(三次贝塞尔曲线)
    • lineTo(x,y):绘制一条从上一点到(x,y)的直线
    • moveTo(x,y):不绘制线条,只把绘制光标移动到(x,y)
    • quadraticCurveTo(cx,cy,x,y):以(x,y)为控制点,绘制一条从上一点到(x,y)的弧线(二次贝塞尔曲线)
    • rect(x,y,width,height):以给定的宽高度在坐标点(x,y)绘制一个矩形,与fillRect()和strokeRect()的区别在于,它创建的是一条路径,而不是独立的图形
  3. 最后一步调用 stroke() 描画路径

       

3.3.2 isPointInPath()

​ 接收x轴和y轴坐标作为参数。该方法用于确定指定的点是否在路径上,可以在关闭路径前随时调用

       

3.4 绘制文本

3.4.1 绘制文本的基本方法

       方法:fillText()和 strokeText(),都接收4个参数:要绘制的字符串、x坐标、y坐标和可选的最大像素宽度,4个属性如下:

  • font
  • textAlign:指定文本的对齐方式:值有:“start”、“end”、“left”、“right” 和 “center”,不推荐使用“left”、“right”
  • textBaseLine:指定文本的基线,可以改变文本的垂直对齐方式,值有:“top”、“hanging”,“middle”,“alphabetic”,“ideographic” 和 “bottom”
  • 文本的最大宽度(可选)

       

3.4.2 measureText()

       接收一个参数,即要绘制的文本,然后返回一个 TextMetrics 对象。返回的这个对象只有一个属性width,measureText()方法使用 font、textAlign 和 textBaseline 属性当前的值计算绘制指定文本的大小

例子:假设把文本“Hello world!”放到一个140像素框的矩形中,可从100像素的字体大小开始计算,不断递减,直到文本大小合适:

let fontSize = 100;
context.font = fontSize + 'px Arial';
while(context.measureText('Hello world!').width > 140) {
    
    
  fontSize--;
  context.font = fontSize + 'px Arial';
}
context.fillText('Hello world!',10,10);
context.fillText('Font size is' + fontSize + 'px',10,50);

       

3.5 变换

用于改变绘制上下文的变换矩阵:

  • rotate(angle):围绕原点把图像旋转angle弧度
  • scale(scaleX, scaleY):通过在 x 轴乘以 scaleX、在 y 轴乘以 scaleY来缩放图像。默认值都为1.0
  • translate(x,y):把原点(0,0)移动到(x,y)
  • transform(m1_1, m1_2, m2_1, m2_2, dx, dy):像下面这样通过矩阵乘法直接修改矩阵
  • setTransform(m1_1, m1_2, m2_1, m2_2, dx, dy): 把矩阵重置为默认值,再以传入的参数调用transform()

       

save() 和 restore()

  • save():所有当前时刻的设置暂存入栈,只保存应用到绘画上下文的设置和变换,不保存绘画上下文的内容
  • restore():从暂存栈中去除并恢复之前保存的设置

       

3.6 绘制图像

       若想把现有的图像绘制到画布上,可使用drawImage()

(1)接收3个参数

       传入一个HTML的<img>元素、绘制目标的x和y坐标,结果:把图像绘制到指定位置

let image = document.images[0];
context.drawImage(image,10,10);

       

(2)接收5个参数

       可改变所绘制图像的大小

       传入一个HTML的<img>元素、绘制目标的x和y坐标,目标宽度和目标高度。这里的缩放只影响绘制的图像,不影响上下文的变换矩阵:

context.drawImage(image,50,102030);

图像缩放到20像素宽、30像素高

       

(3)接收9个参数

       要绘制的图像,源图像x坐标,源图像y坐标,源图像宽度,源图像高度,目标区域x坐标,目标区域y坐标、目标区域宽度和目标区域高度

context.drawImage(image,0,10,50,50,0,100,40,60);

       从(0,10)开始,50像素宽、50像素高。而绘制到画布上是,会从(0,100)开始,变成40像素宽,60像素高

       

3.7 阴影

属性:

  • shadowColor
  • shadowOffsetX
  • shadowOffsetY
  • shadowBlur:像素,表示阴影的模糊量。默认为0

       

3.8 渐变

3.8.1 线性渐变

步骤:

  1. 使用 createLinearGradient(),接收4个参数:起点x坐标、起点y坐标、终点x坐标、终点y坐标。调用后该方法会以指定大小创建一个新的CanvasGradient对象并返回实例
  2. 使用 **addColorStop()**为渐变指定色标。接收两个参数:色标位置(0~1范围内的值表示,0是第一种颜色,1是最后一种颜色)和CSS字符串。
let gradient = context.createLinearGradient(30,30,70,70);
gradient.addColorStop(0,'white');
gradient.addColorStop(1,'black');

       

3.8.2 径向渐变

​ 使用 **createRadialGradient()**创建,接收6个参数:分别对应两个圆形圆心的坐标和半径

       

3.9 图案

       图案用于填充和描画图形的重复图像。创建新图案,可以调用 createPattern()并传入两个参数:(1)一个<img>元素 (2)一个表示该如何重复图像的字符串。第二个参数的值与CSS的background-repeat属性一样

       

3.10 图像数据

       使用 getImageData() 获取原始图像数据。接收4个参数:要取得数据中第一个像素的左上角坐标和要取得的像素宽度及高度。 返回对象是ImageData的实例,每个对象包含3个属性:width、height和data。 data属性是包含图像的原始像素信息的数组。每个像素在data数组中都由4个值表示,分别代表红、绿、蓝和透明度值

       

3.11 合成

2D上下文绘制的所有内容都会应用两个属性:globalAlpha 和 globalCompositionOperation。

  • globalAlpha :一个范围0~1的值(包括0和1),用于指定所有绘制内容的透明度,默认为0
  • globalCompositionOperation:表示新绘制的形状如何与上下文中已有的形状融合。是字符串,值有:

       

四、WebGL

画布的3D上下文,WebGL是OpenGL ES 2.0 的Web版

4.1 WebGL上下文

在使用上下文之前,应该先检测返回值是否存在:

let drawing = document.getElementById("drawing");
// 确保浏览器支持canvas
if (drawing.getContext) {
    
    
  let gl = drawing.getContext("webgl");
  if(gl) {
    
    
    //使用webgl
  }
}

4.2 WebGL基础

       可在getContext()取得WebGL上下文时指定一些选项。这些选项通过一个参数对象传入,选项就是参数对象的一个或多个属性

  • alpha:布尔,表示是否为上下文创建透明通道缓冲区,默认为true
  • depth:布尔,表示是否使用16位深缓冲区,默认为true
  • stencil:布尔,表示是否使用8位模板缓冲区,默认为false
  • antialias:布尔,表示是否使用默认机制执行抗锯齿操作,默认为true
  • premultipliedAlpha:布尔,表示绘图缓冲区是否预乘透明度值,默认为true
  • preserverDrawingBuffer:布尔,表示绘图完成后是否保留绘图缓冲区,默认为false
let drawing = document.getElementById("drawing");
// 确保浏览器支持canvas
if (drawing.getContext) {
    
    
    try{
    
    
        let gl = drawing.getContext("webgl",{
    
    alpha:false});
    } catch(ex){
    
    

    }
    if(gl) {
    
    
        //使用webgl
    }
}

       

1. 常量

       在OpenGL中常量以GL开头,在WebGL要换种方法访问,如:GL_COLOR_BUFFER_BIT 在WebGL中要访问 g.COLOR_BUFFER_BIT

       

2. 方法命名

  • 接收不同类型和不同数量参数的方法,会通过方法名的后缀体现
  • 表示参数数量的数字在前,表示数据类型的字符串(‘f’ 表示浮点数,‘i’ 表示整数)在后,如:gl.uniform4f() 表示需要4个浮点数值参数
  • 接收数组的方法用字母**‘v’**表示,如:gl.uniform3iv() 即要接收一个包含3个值得数组参数

       

3. 准备绘图

       准备使用WebGL上下文之前,通常需要先指定一种实心颜色清除<canvas>。为此需要调用clearColor()并传入4个参数,分别表示红、绿、蓝和透明值,参数范围都是(0~1范围),表示各个组件在最终颜色的强度

gl.clearColor(0,0,0,1);
gl.clear(gl.COLOR_BUFFER_BIT);

       

4. 视口和坐标

       要改变视口,可调用 viewport()并传入视口相对于 <canvas>元素事务x、y坐标及宽高度。如果绘图时用了视口外部的坐标,则绘制结果会被视口剪切
       视口的 x 和 y坐标起点(0,0)表示<canvas>
左下角
,向上,向右增长可以用点(width-1,height-1)定义:

 
例子:定义视口是<canvas>左上角四分之一区域:

gl.viewport(0,drawing.height/2,drawing.width/2,drawing.height/2);

       

5. 缓冲区

       在 JS 中,顶点信息保存在定性数组中。要使用这些信息,必须先将他们转换为WebGL缓冲区。创建缓冲区要调用 gl.createBuffer(),并使用 **gl.bindBuffer()**将缓冲区绑定到WebGL上下文,绑定之后就可以使用数据填充缓冲区了,如:

let buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER,buffer);
gl.bufferData(gl.ARRAY_BUFFER,new Float32Array([0,0.5,1]),gl.STATIC_DRAW);

gl.bufferData() 方法的最后一个参数表示如何使用缓冲区

  • gl.STATIC_DRAW:数据加载一次,可在多次绘制中使用(一般使用这个)
  • gl.STREAM_DRAW:数据加载一次,只能在几次绘制中使用
  • gl.DYNAMIC_DRAW:数据可以重复修改,在多次绘制中使用

不在使用缓冲区时,调用 gl.deleteBuffer() 释放其占用的内存

       

6. 错误

       WebGL中通常不会抛出错误**,必须调用可能失败的方法后,调用 gl.getError()**,该方法返回一个常量,表示发生的错误类型:

       

7. 着色器

       WebGL 中有两种着色器:顶点着色器和片段(或像素)着色器,WebGL是使用GLSL语言实现的

  • 顶点着色器:用于把3D顶点转换为可以渲染的2D点
  • 片段着色器:用于计算绘制一个像素的正确颜色。

7.1 编写着色器

       每个着色器都有一个 **main()**方法,在绘制期间会重复执行。给着色器传递参数的方式有两种:attribute 和 uniform。attribute :用于将顶点传入顶点着色器,uniform:用于将常量值传入任何着色器。。attribute 和 uniform 是在main()函数外部定义的。在值类型关键字之后是数据类型,然后是变量名

attribute vec2 aVertexPosition;
void main() {
    gl_Position = vec4(aVertexPosition,0.0,1.0);
}

       定义了名为aVertexPosition的attribute。该attribute是一个包含两项的数组(数据类型为vec2),代表x和y坐标

       

4.3 WebGL1 和 WebGL2

WebGL1 几乎与 WebGL12兼容,在WebGL12 中很多功能变成了默认功能,以下特性都已经成为WebGL12的标准特性:

猜你喜欢

转载自blog.csdn.net/weixin_45950819/article/details/120763824