基础
-
<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()
之类的方法时,值会添加到x
和y
坐标值上 - 该变换只会影响
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
中可以通过定时器setTimeout
、setInterval
来实现css3
可以使用transition
和animation
来实现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)
发表评论