Canvas绘画、动画基础

基础

  • <canvas> 标签只是图形容器,您必须使用脚本来绘制图形

  • 标签通常需要指定一个id属性 (脚本中经常引用)

<canvas id="myCanvas" width="200" height="100"></canvas> <script> // 找到 <canvas> 元素 const c = document.getElementById('myCanvas') // 创建 context 对象 // getContext('2d') 对象是内建的 HTML5 对象,拥有多种绘制路径、矩形、圆形、字符以及添加图像的方法 const ctx = c.getContext('2d') ctx.fillStyle = '#ff0000' // 绘制矩形 ctx.fillRect(0, 0, 150, 75) </script>

Canvas 对象属性和方法

width 和 height

  • 画布的宽度和画布的高度

  • 可以指定为一个整数像素值或者是窗口高度的百分比

  • 当这个值改变的时候,在该画布上已经完成的任何绘图都会擦除掉

getContext(contextID)

返回一个用于在画布上绘图的环境

contextID:想要在画布上绘制的类型。当前唯一的合法值是'2d',二维绘图

未来,如果 <canvas> 标签扩展到支持 3D 绘图,getContext() 方法可能允许传递一个 '3d' 字符串参数

Context 对象API

颜色、样式和阴影

  • fillStyle:设置或返回用于填充绘画的颜色、渐变或模式
  • strokeStyle:设置或返回用于笔触的颜色、渐变或模式
  • shadowColor:设置或返回用于阴影的颜色
  • shadowBlur:设置或返回用于阴影的模糊级别
  • shadowOffsetX:设置或返回阴影距形状的水平距离
  • shadowOffsetY:设置或返回阴影距形状的垂直距离
  • createLinearGradient(x0, y0, x1, y1):创建线性渐变(用在画布内容上)
  • createPattern(image, pattern):在指定的方向上重复指定的元素
  • createRadialGradient(x0, y0, r0, x1, y1, r1):创建放射状/环形的渐变(用在画布内容上)
  • addColorStop(stop, color):规定渐变对象中的颜色和停止位置
/* context.fillStyle = color|gradient|pattern context.strokeStyle = color|gradient|pattern context.shadowColor = color context.shadowBlur = number context.shadowOffsetX = number context.shadowOffsetY = number context.createLinearGradient(x0, y0, x1, y1) x0、y0:渐变开始点的 x、y 坐标 x1、y1:渐变结束点的 x、y 坐标 context.createPattern(image, "repeat|repeat-x|repeat-y|no-repeat") image:规定要使用的图片、画布或视频元素 context.createRadialGradient(x0, y0, r0, x1, y1, r1) x0、y0:渐变的开始圆的 x、y 坐标 r0:开始圆的半径 x1、y1:渐变的结束圆的 x、y 坐标 r1:结束圆的半径 gradient.addColorStop(stop, color) stop:介于 0.0 与 1.0 之间的值,表示渐变中开始与结束之间的位置 color:在结束位置显示的 CSS 颜色值 */ // color 颜色 ctx.fillStyle = '#0000ff' ctx.fillRect(20, 20, 150, 100) // gradient 渐变 const my_gradient = ctx.createLinearGradient(0 ,0, 170, 0) my_gradient.addColorStop(0, 'black') my_gradient.addColorStop(0.5, 'red') my_gradient.addColorStop(1, 'white') ctx.fillStyle = my_gradient ctx.fillRect(20, 20, 150, 100) // pattern 模式 - 填充图像 const img = document.getElementById('lamp') const pat = ctx.createPattern(img, 'repeat') ctx.rect(0, 0, 150, 100) ctx.fillStyle = pat ctx.fill() // color 颜色 ctx.strokeStyle = '#0000ff' ctx.strokeRect(20, 20, 150, 100) // gradient 渐变 - 矩形 const gradient = ctx.createLinearGradient(0, 0, 170, 0) gradient.addColorStop('0', 'magenta') gradient.addColorStop('0.5', 'blue') gradient.addColorStop('1.0', 'red') ctx.strokeStyle = gradient ctx.lineWidth = 5 ctx.strokeRect(20, 20, 150, 100) // gradient 渐变 - 文字 ctx.font = '30px Verdana' const gradient = ctx.createLinearGradient(0, 0, 170, 0) gradient.addColorStop('0', 'magenta') gradient.addColorStop('0.5', 'blue') gradient.addColorStop('1.0', 'red') ctx.strokeStyle = gradient ctx.strokeText('Big smile!', 10, 50)

线条样式

  • lineCap:设置或返回线条的结束端点样式
  • lineJoin:设置或返回两条线相交时,所创建的拐角类型
  • lineWidth:设置或返回当前的线条宽度
  • miterLimit:设置或返回最大斜接长度
/* context.lineCap = "butt|round|square" butt:默认。向线条的每个末端添加平直的边缘 round:向线条的每个末端添加圆形线帽 square:向线条的每个末端添加正方形线帽 context.lineJoin = "bevel|round|miter" bevel:创建斜角 round:创建圆角 miter:默认。创建尖角 context.lineWidth = number context.miterLimit = number */

路径

  • fill():填充当前绘图(路径)
  • stroke():绘制已定义的路径
  • beginPath():起始一条路径,或重置当前路径
  • moveTo(x, y):把路径移动到画布中的指定点,不创建线条
  • closePath():创建从当前点回到起始点的路径
  • lineTo(x, y):添加一个新点,然后在画布中创建从该点到最后指定点的线条
  • clip():从原始画布剪切任意形状和尺寸的区域
  • quadraticCurveTo(cpx, cpy, x, y):创建二次贝塞尔曲线
  • bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y):创建三次方贝塞尔曲线
  • arc(x, y, r, sAngle, eAngle, counterclockwise):创建弧/曲线(用于创建圆形或部分圆)
  • arcTo(x1, y1, x2, y2, r):创建两切线之间的弧/曲线
  • isPointInPath(x, y):如果指定的点位于当前路径中,则返回 true,否则返回 false

二次贝塞尔曲线

三次贝塞尔曲线

弧/曲线

/* context.quadraticCurveTo(cpx, cpy, x, y) cpx、cpy:贝塞尔控制点的 x、y 坐标 x、y:结束点的 x、y 坐标 context.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) cp1x、cp1y:第一个贝塞尔控制点的 x、y 坐标 cp2x、cp2y:第二个贝塞尔控制点的 x、y 坐标 x、y:结束点的 x、y 坐标 context.arc(x, y, r, sAngle, eAngle, counterclockwise) x、y:圆的中心的 x、y 坐标 r:圆的半径 sAngle:起始角,以弧度计(弧的圆形的三点钟位置是 0 度) eAngle:结束角,以弧度计 counterclockwise:可选。规定应该逆时针还是顺时针绘图。False = 顺时针,true = 逆时针 context.fillRect(x1, y1, x2, y2, r) x1、y1:弧的起点的 x、y 坐标 x2、y2:弧的终点的 x、y 坐标 r:弧的半径 context.isPointInPath(x, y) x、y:测试的 x、y 坐标 */

绘制矩形

  • rect(x, y, width, height):创建矩形
  • fillRect(x, y, width, height):绘制“被填充”的矩形
  • strokeRect(x, y, width, height):绘制矩形(无填充)
  • clearRect(x, y, width, height):在给定的矩形内清除指定的像素
/* context.rect(x, y, width, height) context.fillRect(x, y, width, height) context.strokeRect(x, y, width, height) context.clearRect(x, y, width, height) */ // rect 创建的矩形,需要绘制才能显示 ctx.beginPath() ctx.lineWidth = '6' ctx.strokeStyle = 'red' ctx.rect(5, 5, 290, 140) ctx.stroke() // 创建 + 绘制 ctx.strokeRect(20, 20, 150, 100)

绘制文本

  • font:设置或返回文本内容的当前字体属性
  • textAlign:设置或返回文本内容的当前对齐方式
  • textBaseline:设置或返回在绘制文本时使用的当前文本基线
  • fillText(text, x, y, maxWidth):在画布上绘制“被填充的”文本
  • strokeText(text, x, y, maxWidth):在画布上绘制文本(无填充)
  • measureText(text):返回包含指定文本宽度的对象
/* context.font = "italic small-caps bold 12px arial" font-style: normal italic oblique font-variant: normal small-caps font-weight: normal bold bolder lighter 100 ~ 900 font-size/line-height font-family context.textAlign = "center|end|left|right|start" context.textBaseline = "alphabetic|top|hanging|middle|ideographic|bottom" context.fillText(text, x, y, maxWidth) maxWidth: 可选 context.strokeText(text, x, y, maxWidth) maxWidth: 可选 context.measureText(text).width */

绘制图像

  • drawImage():向画布上绘制图像、画布或视频
/* context.drawImage(img, x, y) context.drawImage(img, x, y, width, height) context.drawImage(img, sx, sy, swidth, sheight, x, y, width, height) sx、 sy: 开始剪切的 x、y 坐标位置 swidth、sheight:被剪切图像的宽度、高度 x、y:在画布上放置图像的 x、y 坐标位置 width、height:要使用的图像的宽度、高度(伸展或缩小图像) */ // 视频-每隔20ms,绘制视频的当前帧 const v = document.getElementById('video1') const c = document.getElementById('myCanvas') ctx = c.getContext('2d') v.addEventListener('play', function() { var i = window.setInterval(function() { ctx.drawImage(v, 0, 0, 270, 135) }, 20) }, false) v.addEventListener('pause', function() { window.clearInterval(i) }, false) v.addEventListener('ended', function() { clearInterval(i) }, false)

转换

  • scale(scalewidth, scaleheight):缩放当前绘图至更大或更小
  • rotate(angle):旋转当前绘图
  • translate(x, y):重新映射画布上的 (0,0) 位置
  • transform(a, b, c, d, e, f):替换绘图的当前转换矩阵
  • setTransform(a, b, c, d, e, f):将当前转换重置为单位矩阵。然后运行 transform()
  • 如果对绘图进行缩放,所有之后的绘图也会被缩放。定位也会被缩放。如果您 scale(2,2),那么绘图将定位于距离画布左上角两倍远的位置。
  • 当您在 translate() 之后调用诸如 fillRect() 之类的方法时,值会添加到 xy 坐标值上
  • 该变换只会影响 transform() 方法调用之后的绘图
  • transform() 方法的行为相对于由 rotate(), scale(), translate(), 或者 transform() 完成的其他变换
/* context.scale(scalewidth, scaleheight) scalewidth:缩放当前绘图的宽度 (1=100%, 0.5=50%, 2=200%, 依次类推) scaleheight:缩放当前绘图的高度 (1=100%, 0.5=50%, 2=200%, etc.) context.rotate(angle) angle:旋转角度,以弧度计。如需将角度转换为弧度,使用 degrees*Math.PI/180 公式进行计算 context.translate(x, y) x、y:添加到水平坐标(x)、垂直坐标(y)上的值 context.transform(a, b, c, d, e, f) a:水平缩放绘图 b:水平倾斜绘图 c:垂直倾斜绘图 d:垂直缩放绘图 e:水平移动绘图 f:垂直移动绘图 context.setTransform(a, b, c, d, e, f) a:水平旋转绘图 b:水平倾斜绘图 c:垂直倾斜绘图 d:垂直缩放绘图 e:水平移动绘图 f:垂直移动绘图 */

其他

  • globalAlpha:设置或返回绘图的当前 alpha 或透明值
  • globalCompositeOperation:设置或返回新图像如何绘制到已有的图像上
  • save():保存当前环境的状态
  • restore():返回之前保存过的路径状态和属性
  • createEvent()
  • getContext()
  • toDataURL()
/* context.globalAlpha = number number:透明值。必须介于 0.0(完全透明) 与 1.0(不透明) 之间 context.globalCompositeOperation = "source-in" source-over:默认。在目标图像上显示源图像。 source-atop:在目标图像顶部显示源图像。源图像位于目标图像之外的部分是不可见的。 source-in:在目标图像中显示源图像。只有目标图像内的源图像部分会显示,目标图像是透明的。 source-out:在目标图像之外显示源图像。只会显示目标图像之外源图像部分,目标图像是透明的。 destination-over:在源图像上方显示目标图像。 destination-atop:在源图像顶部显示目标图像。源图像之外的目标图像部分不会被显示。 destination-in:在源图像中显示目标图像。只有源图像内的目标图像部分会被显示,源图像是透明的。 destination-out:在源图像外显示目标图像。只有源图像外的目标图像部分会被显示,源图像是透明的。 lighter:显示源图像 + 目标图像。 copy:显示源图像。忽略目标图像。 xor:使用异或操作对源图像与目标图像进行组合 */

requestAnimationFrame

实现动画效果的方法有很多。

  • Javascript 中可以通过定时器 setTimeoutsetInterval 来实现
  • css3 可以使用 transitionanimation 来实现
  • html5 中的 canvas 也可以实现
  • requestAnimationFrame(请求动画帧)是 html5 还提供一个专门用于请求动画的API
  • requestAnimationFrame 最大的优势是由系统来决定回调函数的执行时机
  • requestAnimationFrame 的步伐跟着系统的刷新步伐走。它能保证回调函数在屏幕每一次的刷新间隔中只被执行一次,这样就不会引起丢帧现象,也不会导致动画出现卡顿的问题

由于requestAnimationFrame目前还存在兼容性问题,而且不同的浏览器还需要带不同的前缀。
因此需要通过优雅降级的方式对requestAnimationFrame进行封装,优先使用高级特性,然后再根据不同浏览器的情况进行回退,直止只能使用setTimeout的情况

// requestAnimationFrame 兼容 // [GitHub](https://github.com/darius/requestAnimationFrame) if (!Date.now) Date.now = function() { return new Date().getTime(); }; (function() { 'use strict'; var vendors = ['webkit', 'moz']; for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) { var vp = vendors[i]; window.requestAnimationFrame = window[vp+'RequestAnimationFrame']; window.cancelAnimationFrame = (window[vp+'CancelAnimationFrame'] || window[vp+'CancelRequestAnimationFrame']); } if (/iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent) // iOS6 is buggy || !window.requestAnimationFrame || !window.cancelAnimationFrame) { var lastTime = 0; window.requestAnimationFrame = function(callback) { var now = Date.now(); var nextTime = Math.max(lastTime + 16, now); return setTimeout(function() { callback(lastTime = nextTime); }, nextTime - now); }; window.cancelAnimationFrame = clearTimeout; } }()); // 简单的兼容处理 if (!window.requestAnimationFrame) { window.requestAnimationFrame = ( window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame|| window.oRequestAnimationFrame || function (callback) { return setTimeout(callback, Math.floor(1000 / 60)) } ) }
// requestAnimationFrame 使用 var progress = 0 // 回调函数 function render() { // 修改图像的位置 progress += 1 if (progress < 100) { // 在动画没有结束前,递归渲染 window.requestAnimationFrame(render) } } // 第一帧渲染 window.requestAnimationFrame(render)

Canvas 实例


创作不易,若本文对你有帮助,欢迎打赏支持作者!

 分享给好友: