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

则其效果为:

探照灯效果和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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,066评论 4 62
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,832评论 25 707
  • 55岁之前想做的事,给自己定位,事业和生活. 事业上,先拥有属于自己的茶行(雅玩茶舍).集成销售各类茶品,紫砂壶及...
    雅玩阅读 211评论 0 0
  • 今天是我实习的最后一天,早就想离开这里的我不知怎么心里却有了一丝难受,难道是对这里的不舍吗?说实话这两天真的有点伤...
    花仙子猪猪阅读 239评论 0 1
  • 记得刚结婚那会,我和肥佬的做饭水平相当,他的青菜炒得好,我煎鱼还可以吧。后来慢慢地,他学会了很多,做饭颇具...
    语歌晨唱阅读 1,164评论 0 2