引入
关于
canvas是HTML5新增的标签,用于动画、游戏画面、数据可视化、图片编辑以及实时视频处理等方面,canvas不支持IE8及IE8以下浏览器
创建画布
通过HTML标签
可以通过直接添加HTML标签的方式创建canvas,并设置画布的宽高,然后通过getContext()方法获得画布的 2D 渲染上下文对象,需要为该方法提供‘2d’作为参数,该对象提供了用于在画布上绘图的方法和属性
<body>
<canvas width="600" height="400" id="canvas"></canvas>
<script>
const canvas=document.getElementById('canvas');
const context = canvas.getContext('2d');
</script>
</body>
通过JavaScript动态创建
也可以通过JavaScript动态添加canvas并指定宽高,tips:通过JavaScript创建的canvas在写代码时IDE会有代码补全提示
const canvas=document.createElement("canvas");
canvas.width=600;
canvas.height=400;
document.body.appendChild(canvas);
const context = canvas.getContext('2d');
画布尺寸的说明
- canvas需要通过属性来设置宽度、高度,且不需要单位
- 通过CSS设置canvas的width和height属性,仅用于缩放图像,无法改变画布尺寸,当CSS指定的宽高与画布的宽高比例不一致时,图像会出现扭曲
- 默认画布大小为300px * 150px,宽高比为2:1,如果不指定画布的宽高,或者设置了无效值(如负数),则会使用默认值
兼容性检查
IE9之前的浏览器不支持Canvas,如果不兼容canvas,canvas 标签会被浏览器解析为自定义标签,显示标签内的提示信息
Canvas 2D渲染API
所有API都是基于canvas的2d渲染上下文对象,文中以context或ctx为对象名
绘制
路径
moveTo(x,y) 移动画笔到指定坐标
指定画笔最先开始绘制的点,此后Canvas的绘制方法都是基于上一次的路径终点进行的,仍可以使用moveTo(x,y)修改画笔的坐标到路径终点以外context.beginPath() 创建一个新路径
在同一个画布中绘制多个独立的图像,应当为每个图像创建不同的子路径,如:为不同的线段设置不同的颜色、线宽等样式,应当为每个线段创建独立的子路径,每个路径内部的样式、属性单独声明,否则后声明的样式会覆盖前面的样式,并且应当为每个独立的路径指定画笔初始点,执行绘制方法
在使用循环定时器创建动画时,往往需要在每次定时器执行时创建一个新路径,避免上次的路径影响到本次路径的绘制
- context.closePath(); 闭合当前子路径
将画笔坐标移回到当前子路径起始点,该方法会尝试从当前点到起始点绘制一条直线,如果图形已经是封闭的或者只有一个点,该方法不会做任何操作,可用于闭合图形
描边
- context.stroke() 绘制当前路径
- context.lineWidth=值; 修改描边线条的宽度
- context.strokeStyle=”颜色/渐变对象/Pattern对象”; 设置描边样式
填充
- context.fill() 填充已闭合的路径
- context.fillStyle=”颜色/渐变对象/Pattern对象”; 设置填充的样式
裁剪
context.clip() 根据当前路径进行裁剪
沿着路径进行裁剪,裁剪路径外的图形将不再显示在Canvas中
清除
context.clearRect(x,y,width,height) 清除指定区域内的画布内容
封装路径-Path2D对象
let myPath2D=new Path2D();
返回一个Path2D对象,之后可以将路径添加到该对象中,并直接重用对象中的所有路径,stroke()、fill()、clip()均接收该对象作为参数
位置判断
- context.isPointInStroke(x, y) 返回boolean值,判断(x,y)点是否在当前路径上
- context.isPointInStroke(path2D对象, x, y) 返回boolean值,判断(x,y)点是否在封装的path2D路径上
- context.isPointInPath(x, y) 返回boolean值,判断(x,y)点是否在当前路径内
- context.isPointInPath(path2D对象, x, y) 返回boolean值,判断(x,y)点是否在封装的path2D路径内
线段
实线线段
线段路径
context.lineTo(x,y) 连接直线路径到指定坐标
线段折点
context.lineJoin=”miter/round/bevel”;
设置或返回两条线交汇时,线段折线处的样式
- miter 尖锐折线(默认)
- round 圆角折线
- bevel 切角折线
ctx.miterLimit = value;
设置或返回边角斜切面的限制长度(默认为10),下图为miterLimit=2(左)以及miterLimit=10(右)的区别
线段末端
context.lineCap=”butt/round/square;”
设置或返回线段末端线帽的样式,”round” 和 “square” 值会使线条略微变长
- butt 末端以方形结束(默认)
- round末端添加圆形线帽
- square末端添加一个宽度相同,长度为宽度一半的矩形线帽
虚线线段
- context.setLineDash([数组]); 传递一个数组来指定虚线线段和间隙的交替长度,空数组将设置为实线
- context.getLineDash(); 返回一个数组,获取当前线段的样式
context.lineDashOffset = value;
设置虚线偏移值,可实现蚂蚁线效果
矩形
矩形路径
context.rect(x,y,width,height) 创建矩形路径
- x, y为矩形坐标
- width为矩形的宽度,正值矩形位于x坐标右侧,负值则位于左侧
- height为矩形的高度,正值矩形位于y坐标下方,负值则在上方
仅创建矩形路径,不会显示在画布中,可以使用stroke()方法或fill()方法进行描边绘制或者填充绘制
描边矩形
context.strokeRect(x, y, width, height) 绘制描边矩形
创建矩形路径并描边,参数同上,相当于rect()方法和stroke()方法同时执行,可以使用lineWidth修改线宽,使用strokeStyle修改线条样式
填充矩形
context.fillRect(x, y, width, height) 填充一个矩形
创建矩形路径并填充,参数同上,相当于rect()方法和fill()方法同时执行,可以使用fillStyle修改填充样式
渐变对象
添加渐变色
渐变对象名.addColorStop(偏移量,”颜色”)
- 对象名为以下三种渐变对象创建的实例
- 偏移量为0~1之间的值,代表渐变开始到渐变终止的位置,等同于CSS中的百分比位置
- 颜色取值同CSS
线性渐变对象
context.createLinearGradient(x1,y1,x2,y2)
在(x1,y1)到(x2,y2)矢量方向上创建径向渐变对象,并返回该对象
径向渐变对象
context.createRadialGradient(x0, y0, r0, x1, y1, r1);
以(x0,y0)为圆心,r0为半径确定一圆,以(x1,y1)为圆心,r1为半径确定另一圆,
根据参数确定两个圆的坐标,绘制放射性渐变的方法
锥形渐变对象
context.createConicGradient(弧度值,x,y)
以(x,y)为锥形中心创建锥形渐变对象,弧度值角度为渐变开始的位置,角度通过Math.PI*角度/180运算为弧度值,值可正可负
Pattern对象
创建模板对象,
createPattern(image,重复模式)
image为图像源,可以是<img>图像、<video>视频、<canvas>另外一个canvas对象、canvas的2d上下文对象(CanvasRenderingContext2D)等
图像的重复模式可以为repeat、repeat-x、repeat-y、no-repeat,用法同CSS
曲线
圆与圆弧线绘制
context.arc(x,y,r,startAngle, endAngle, anticlockwise)
- x,y为圆弧中心
- r为圆弧半径
- startAngle, endAngle为圆弧起始点和终点角度,弧度表示,角度为css坐标x轴与y轴夹角
- anticlockwise可选,true为逆时针绘制圆弧,false为顺时针绘制const canvasArc=document.getElementById("canvasArc"); const ctxArc = canvasArc.getContext('2d'); ctxArc.lineWidth=5; ctxArc.arc(150,150,100,0,90/180*Math.PI,true); ctxArc.stroke();
圆弧线绘制方法2
context.arcTo(x1, y1, x2, y2, radius)
- x1, y1为第一个控制点坐标
- x2, y2为第二个控制点坐标
- radius 为圆弧半径
将当前路径终点与控制点 1 连接的直线,和控制点 1 与控制点 2 连接的直线,作为使用指定半径的圆的切线,画出两条切线之间的弧线路径
椭圆
context.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise);
- x,y为椭圆圆心的坐标
- radiusX 为椭圆长轴的半径,radiusY 为椭圆短轴半径
- rotation 为椭圆的旋转角度,以弧度表示
- startAngle, endAngle为椭圆圆弧起始点和终点角度,弧度表示
- anticlockwise可选,true为逆时针绘制圆弧,false为顺时针绘制
贝塞尔曲线
二阶贝塞尔曲线
context.quadraticCurveTo(cpx, cpy, x, y)
- cpx, cpy为控制点的坐标
- x, y为曲线终点坐标
- 起始点坐标为当前路径所在终点,或者可以使用moveTo()控制
三阶贝塞尔曲线
context.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
- cp1x, cp1y为第一个控制点的坐标
- cp2x, cp2y为第二个控制点的坐标
- x, y为曲线终点坐标
- 起始点坐标为当前路径所在终点,或者可以使用moveTo()控制
文字绘制
绘制文字
context.fillText(text, x, y, [maxWidth]); 对文字进行填充
context.strokeText(text, x, y, [maxWidth]); 对文字进行描边
- text指定文本内容
- x, y为文本左下角在画布中开始绘制的坐标(因此坐标不应该为0,0)
- maxWidth(可选),指定绘制的最大宽度,会对文本进行水平缩放
获取文本宽度
context.measureText(“文本”);
返回文本的TextMetrics 对象,一般会从该对象中获得文本宽度,以判断文字是否需要在Canva中进行换行
文本属性
context.font = “value”;
指定文本属性,默认为 10px sans-serif,value为CSS中的font简写属性,可以按顺序设置[font-style] [font-variant] [font-weight] font-size[/line-height] font-family
- 必须包含font-size和font-family
- font-style:字体样式,常用取值normal(正常)、italic(斜体)、oblique(倾斜)
- font-variant:设置小型大写字母,将字母写为大写,但除首字母外的文本将缩小字号,默认为normal,可以修改为small-caps(小型大写字母)
- font-weight:设置文本的粗细,常用值:normal(正常),bold(粗体),bolder(再加粗),lighter(细体),以及100-900的整百数值
- font-size必须,line-height为非必须,有line-height时要写为如:16px/20px 的形式
- font-family:字体族,5个通用字体:Serif(衬线字体)、Sans-serif(无衬线字体)、Monospace(等宽字体)、Cursive(草书字体)、Fantasy(幻想字体)
文本方向
context.direction =”ltr/rtl/inherit”; 设置当前文本方向
- ltr ,从左往右
- rtl ,从右往左(部分国家读写习惯)
- inherit(默认),从父元素继承
context.textAlign = “left/right/center/start/end”; 定义文本水平方向上的对齐方式
- center以绘制文本时的x坐标为基准,一半位于x左边,一半位于右边
- start与end属性以direction定义的文本方向为基准
context.textBaseline = “tophanging/middle/alphabetic/ideographic/bottom”; 定义文本垂直方向上的对齐方式
- 与CSS相同,以文本基线为基准,详见MDN文档
图像与视频绘制
drawImage(image, dx, dy)
drawImage(image, dx, dy, dWidth, dHeight)
drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
- image为图像源,可以是图片、SVG矢量图、视频、canvas等
- dx, dy为图片左上角在画布中开始绘制的位置
- dWidth, dHeight为图像在画布上绘制出来的尺寸,会对其进行缩 放、拉抻
- sx, sy为裁剪时,距离图像左上角的开始裁剪的位置
- sWidth, sHeight为裁剪的宽度和高度,省略该参数则默认裁剪到>右下角,sHeight为负值将从sy反向裁剪
移动、旋转、缩放
context.translate(x, y); 进行水平和垂直位移
修改坐标系的原点,默认原点位于(0,0),由此移动图像在Canvas中的相对位置,可以在上一次translate(x, y)的基础上再次移动坐标系,多次修改坐标原点
context.rotate(弧度值); 进行旋转变换
修改坐标系的旋转角度,参数为弧度值
context.scale(x, y); 进行水平和垂直缩放
对坐标系x轴和y轴进行伸缩,0-1进行缩小,大于1进行放大,负值则进行水平/垂直翻转后进行缩放
阴影
类似于CSS的阴影效果,注意,阴影应当设置在图形绘画之前,如果设置阴影效果之前已经有图形和文字存在,则阴影不会对这些图形和文字生效
- context.shadowOffsetX = value; 阴影的水平偏移距离
- context.shadowOffsetY = value; 阴影的垂直偏移距离
- context.shadowBlur = value; 阴影的模糊值
- context.shadowColor = “颜色”; 阴影颜色const ctx=canvas.getContext('2d'); ctx.shadowOffsetX=16; ctx.shadowOffsetY=8; ctx.shadowBlur=5; ctx.shadowColor="#575656"; ctx.moveTo(50,50); ctx.bezierCurveTo(300, 50, 50, 300,250, 290); ctx.stroke()
滤镜
类似于CSS3中的效果
context.filter = “一个或多个值”
- blur(值px):高斯模糊
- brightness(百分比):亮度
- contrast(百分比):对比度
- grayscale(百分比):灰度滤镜
- hue-rotate(角度deg):对图像进行色彩旋转的处理
- invert(百分比):反色(呈现出照片底片的效果)
- opacity(百分比):不透明度
- sepia(百分比):褐色处理(怀旧风格)
- drop-shadow(x, y, 模糊值, 阴影扩张/收缩, 阴影色):阴影效果
图像合成模式
context.globalCompositeOperation = “type”;
- source-over 图像叠加显示(默认)
- source-in 只显示图像重叠部分
- source-out 只显示图像不重叠的部分
- source-atop 后叠加的图像只显示与原图像重叠的部分
- destination-over 将后叠加的图像置于原图像之下
- destination-in 只显示图像重叠部分,并且只显示原图像部分
- destination-out 将原图像抠去与后图像叠加部分显示,并且后图像不显示
- destination-atop将后图像重叠部分替换为原图像并显示
- lighter两图像重叠部分进行颜色相加
- copy去除原图像,只显示新图像
- xor重叠部分透明,其他正常显示
- multiply将重叠部分的顶层像素与底层像素相乘,重叠部分显示为暗黑色
- screen将重叠部分像素倒转,相乘,再倒转,重叠部分显示为亮色
- overlaymultiply 和 screen 的结合,原本暗的地方更暗,原本亮的地方更亮
- darken保留两个图层中最暗的像素
- lighten保留两个图层中最亮的像素
- color-dodge将底层除以顶层的反置
- color-burn将反置的底层除以顶层,然后将结果反过来
- hard-light类似于叠加,上下图层互换
- soft-light用顶层减去底层或者相反来得到一个正值
- difference一个柔和版本的强光(hard-light),纯黑或纯白不会导致纯黑或纯白
- exclusion和 difference 相似,但对比度较低
- hue保留底层的亮度和色度,同时采用顶层的色调
- saturation保留底层的亮度和色调,同时采用顶层的色度
- color保留了底层的亮度,同时采用了顶层的色调和色度
- luminosity保持底层的色调和色度,同时采用顶层的亮度
状态保存与恢复
- context.save() 将当前状态推入栈中
- context.restore() 读取栈顶的状态
将保存当前的裁剪区域、虚线列表、以及各属性值压入栈中,之后可以直接依次读取栈顶存储的状态并直接绘制,会保存的属性值包括:strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, lineDashOffset, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation, font, textAlign, textBaseline, direction, imageSmoothingEnabled
像素操作-ImageData对象
ImageData对象保存了Canvas图像的底层实际像素,可以直接进行读取和写入。其中,像素被保存在Uint8ClampedArray类型的一维数组中,每个数组元素为0-255之间的数据,每4个数组元素为一组代表了一个像素点的RGBA值。如:索引为0-3的数组元素,存储了第一个像素点的红、绿、蓝、不透明度对应的0-255十进制数值,以此类推
context.getImageData(x,y,width,height)
返回ImageData对象,获取(x,y)坐标开始,width为宽,height为高的矩形区域中的像素。返回的对象中包含width、height、data数组(Uint8ClampedArray类型)三个属性
context.putImageData(imagedata对象, dx偏移, dy偏移)
getImageData()方法可以从(x,y)坐标开始获取一个矩形区域内的像素数据,将该部分数据修改后,可以通过putImageData()方法将数据绘制到canvas中。其中,dx,dy为在(x,y)的基础上进行的偏移量,之前获取到的(x,y)到(x+width,y+height)矩形区域内的图形,修改像素数据后将被绘制到(x+dx,y+dy)开始的同大小矩形区域内
context.putImageData(imagedata对象,dx偏移, dy偏移, dirtyX, dirtyY,dirtyWidth, dirtyHeight)
- dx偏移, dy偏移作用同上
- (dirtyX, dirtyY)为进行修改像素操作开始的位置坐标
- dirtyWidth, dirtyHeight为将进行修改像素操作的矩形区域的长宽
案例
使用图像合成模式的“destination-out”属性值制作刮刮卡
<div class="card"></div>//底层卡片,显示是否中奖
<canvas width="300" height="150" id="canvas1"></canvas>//刮刮乐灰色图层,监听刮开区域
<div class="tryAgain" onclick="tryAgain()">再刮一次</div>//重新开始按钮</div>
<script>
const canvas=document.getElementById("canvas1");
const context=canvas.getContext('2d');
const p=context.createPattern(cover(),"repeat");//创建模板对象,设置图像重复模式
context.fillStyle=p;
function tryAgain(){
document.querySelector(".card").innerText=Math.random()>.8?"恭喜中奖":"再刮一次";
context.clearRect(0,0,300,150);
context.globalCompositeOperation="copy";
context.fillRect(0,0,300,150);
}
tryAgain();
/*
*制作刮刮乐封面,返回canvas DOM
*/
function cover(){
const canvasCover=document.createElement('canvas');
const coverCtx=canvasCover.getContext('2d');
canvasCover.width=60;
canvasCover.height=50;
coverCtx.fillStyle="#6f6d6d"
coverCtx.fillRect(0,0,60,50)
coverCtx.rotate(45*Math.PI/180);
coverCtx.font="300 15px Serif";
coverCtx.fillStyle="#333333"
coverCtx.fillText("发大财",20,0)
return canvasCover;
}
let allowedDraw=false;
canvas.addEventListener('mousedown',function(){
allowedDraw=true;
})
canvas.addEventListener('mouseup',function(){
allowedDraw=false;
})
canvas.addEventListener('mousemove',function(event){
var event = event || window.event;
if(allowedDraw){
if (event.offsetX || event.offsetY) { //非Mozilla浏览器
var x = event.offsetX;
var y = event.offsetY;
} else if (event.layerX || event.layerY) { //兼容Mozilla浏览器
var x = event.layerX;
var y = event.layerY;
}
context.globalCompositeOperation="destination-out";
context.beginPath();
context.arc(x,y,20,0,2*Math.PI);
context.fill();
}})
//移动端
canvas.addEventListener('touchstart',function(){
let canvasRect=canvas.getBoundingClientRect();
canvas.addEventListener('touchmove',function(e){
e.preventDefault();
var x=e.targetTouches[0].pageX-canvasRect.left;
var y=e.targetTouches[0].pageY-canvasRect.top;
context.globalCompositeOperation="destination-out";
context.beginPath();
context.arc(x,y,20,0,2*Math.PI);
context.fill();
},{passive:false})
},{passive:false})
</script>