本章主要介绍以下几个方面:
- 阴影效果shadowColor, shadowOffsetX...
- 全局透明度globalAlpha设置,图像叠加时的效果globalCompositeOperation
- clip() 设置绘制区域,探照灯效果和基本canvas的动画模型
- 零和原则制作剪纸效果
- isPointInPath()判断点的位置,clearRect()清空矩形画布
一.阴影效果
这个效果和css中的 'box-shadow' 类似。canvas中它有几个属性:
- shadowColor: 阴影的颜色
- shadowOffsetX: x轴的偏移
- shadowOffsetY: Y轴的偏移
- shadowBlur: 设置模糊值
示例:
ctx.shadowColor = 'rgba(0,0,0,0.5)'
ctx.shadowOffsetX = -2
ctx.shadowOffsetY = -1
ctx.shadowBlur = 2
ctx.font = 'bolder 50px Ubuntu'
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillStyle = 'orangered'
ctx.fillText('James Sawyer', 200, 200, 400) // 400为最大宽度
二.globalAlpha && globalCompositeOperation
globalAlpha
这个属性相对来说比较简单,主要是设置一个全局的alpha值。这个值和 rgba(0,0,0,alpha) 中的alpha的效果一致,只不过这个是设置全局的透明度
ctx.globalAlpha = 0.7
globalCompositeOperation
这个值的属性比较多,主要用于设置图形相互叠加时显示的效果,其显示效果可以分为3组
1.后绘制的图形B在上面,前面绘制的图形A被遮盖
- source-over: 默认值,后面的图形B在A的上面
- source-atop: 后面的图形B只显示与前面图形A的交叉部分,A则全部显示 (A + A∩B)
- source-out: 只显示B图形未与图形A未交叉部分,A不显示 (A∪B - A - A∩B)
- source-in: 只显示图形交叉部分(A∩B)
2.后绘制的图形B在前面绘制的图形A的下面
这种情况和上面的情况就是图形的z-index改变了,其余的一致。也有3种属性
- destination-over: A在B上面
- destination-atop: 后面的图形A只显示与前面图形B的交叉部分,B则全部显示 (B + A∩B)
- destination-out: 只显示A图形未与图形B未交叉部分,B不显示 (A∪B - B - A∩B)
- destination-in: 只显示图形交叉部分(A∩B)
3.其他情形
- lighter: 交叉部分的颜色变浅
- copy: 只复制最后绘制的图形B
- xor: 交叉部分被去掉即(A∪B - A∩B)
除了上面的11种,还有更多的选项:
globalCompositeOperation 具体文档
三.clip() 将画布设置成当前的
创建剪辑区域
表示使用设置的路径区域作为绘制的范围环境
例如:
ctx.beginPath()
ctx.arc(400, 400, 150, 0, Math.PI * 2)
ctx.stroke() // 用于做辅助线,可以不加这行
# clip()表示使用上面的圆围成的区域作为绘制环境
ctx.clip()
ctx.font = 'bold 150px Ubuntu'
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillStyle = 'black'
ctx.fillText('CANVAS', canvas.width/2, canvas.height/2)
则其效果为:
探照灯效果和canvas基本动画
探照灯利用clip() 函数将可视区域随动画的改变而改变
动画效果
动画效果利用 setInveral()函数来不停的更新画布,到达动画的效果
window.onload = function() {
var canvas = document.querySelector('#canvas');
canvas.width = 800;
canvas.height = 800;
# 设置探照灯对象模型
/*
* @param (x, y): 表示圆心坐标
* @param radius: 圆的半径
* @param vx, vy: 水平和垂直方向的速度,通过他们控制速度大小
*/
var searchLight = {
x: 400,
y: 400,
radius: 150,
vx: Math.random() * 5 + 10,
vy: Math.random() * 5 + 15
};
# 通过setInterval来更新模型的位置
# 每40ms更新一次
setInterval(() => {
draw(ctx);
update(canvas.width, canvas.height);
}, 40);
}
function draw(ctx) {
# 绘制之前先清空画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
# 讲画图绘制为黑色
ctx.beginPath();
ctx.fillStyle = 'black';
ctx.fill()
# 绘制圆形区域
ctx.save()
ctx.beginPath()
ctx.arc(
searchLight.x, searchLight.y,
searchLight.r,
0, Math.PI * 2
);
ctx.fill();
# 将上面的区域作为剪辑区域
ctx.clip();
ctx.font = 'bold 150px Ubuntu';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = '#058';
ctx.fillText('CANVAS', canvas.width/2, canvas.height/2)
ctx.restore();
}
# 小球运动模型,很基本的逻辑判断
function update(canvasWidth, canvasHeight) {
searchLight.x += searchLight.vx;
searchLight.y += searchLight.vy;
# 如果小球超出了左边的边界,则速度反向,x点变为圆的半径
if (searchLight.x - searchLight.radius <= 0) {
searchLight.vx = -searchLight.vx;
searchLight.x = searchLight.radius;
}
# 如果小球超出了右边的边界,
# 则速度反向,x点变为 画布宽度 - 圆的半径
if (searchLight.x + searchLight.radius >= canvasWidth) {
searchLight.vx = -searchLight.vx;
searchLight.x = canvasWidth - searchLight.radius;
}
# y轴方向 基本同上
if (searchLight.y - searchLight.radius <= 0) {
searchLight.vy = -searchLight.vy;
searchLight.y = searchLight.radius;
}
if (searchLight.y + searchLight.radius >= canvasHeight) {
searchLight.vy = -searchLight.vy;
searchLight.y = canvasHeight - searchLight.radius;
}
}
探照灯效果
四.路径方向和零和原则
零和原则: 封闭图形内部是否填充颜色和路径的方向有关,从封闭图形的内部引出一条射线,指定一个方向为正方向,与正方向相交则+1,与反方向相交-1,最后总和不为0,则该区域为填充区域;总和为0则该区域不进行填充
如图,浅蓝色部分相交之和为0,所以不进行填充
可以根据这个原则来绘制出剪纸效果
ctx.beginPath()
// 顺时针绘制一个圆
ctx.arc(400, 400, 300, 0, Math.PI * 2, false)
// 逆时针绘制一个圆
ctx.arc(400, 400, 300, 0, Math.PI * 2, true)
#中心部分因为零和原则,中间部分将不填充
ctx.closePath()
// 添加阴影效果
ctx.shadowColor = 'black'
ctx.shadowOffsetX = 2
ctx.shadowOffsetY = 2
ctx.shadowBlur = 4
ctx.fillStyle = '#058'
ctx.fill()
最终效果:
五. clearRect, isPointInPath##
clearRect(x, y, width, height)
清除指定宽高范围内的画布,多用于动画重新绘制新的图案
ctx.clearRect(200, 200, canvas.weight, canvas.heigth)
isPointInPath(x, y)
判断一个点是否在某个区域内
一般获取鼠标在canvas中的位置使用:
var x = event.clientX - canvas.getBoundingClientRect().left;
var y = event.clientY - canvas.getBoundingClientRect().top;
// (x, y)即为鼠标所在canvas的坐标
然后通过 isPointInPath(x, y)来判断是否在所选区域内
示例:
当鼠标在区域内时,点击小球改变颜色:
var canvas = document.getElementById('#canvas')
canvas.height = 800
canvas.width = 800
var ctx = canvas.getContext('2d')
// 创建一个容器,用来放置所有小球的信息
var balls = []
window.onload = function() {
// 产生10个随机球
for (var i = 0; i < 10; i++) {
var iBall = {
x: Math.random() * canvas.width,
y: Math.random() * canvas.heigth,
r: Math.random() * 20 + 20
}
balls[i] = iBall
}
draw()
canvas.addEventListener('click', detect)
}
// 获取随机颜色值
function getRandomColor() {
return '#' + ('00000' +Math.random() * 0x1000000<<2).toString(16).slice(-6)
}
function draw() {
for (var i = 0; i < balls.length; i++) {
ctx.beginPath();
ctx.arc(balls[i].x, balls[i].y, balls[i].r, 0, Math.PI * 2);
ctx.fillStyle = '#058';
ctx.fill();
}
}
function detect(e) {
var x = event.clientX - canvas.getBoundingClientRect().left;
var y = event.clientY - canvas.getBoundingClientRect().top;
ctx.beginPath();
ctx.arc(balls[i].x, balls[i].y, balls[i].r, 0, Math.PI * 2);
# 判断鼠标位置,是否在圆内
ctx.fillStyle = getRandomColor() || 'red';
ctx.fill();
}
具体demo