#7 高级canvas内容:阴影, clip()等

本章主要介绍以下几个方面:

  • 阴影效果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)

则其效果为:

#7 高级canvas内容:阴影, clip()等_第1张图片

探照灯效果和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,所以不进行填充

#7 高级canvas内容:阴影, clip()等_第2张图片

可以根据这个原则来绘制出剪纸效果

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()

最终效果:

#7 高级canvas内容:阴影, clip()等_第3张图片

五. 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

你可能感兴趣的:(#7 高级canvas内容:阴影, clip()等)