#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閱讀 230,563評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容

  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,179評論 4 61
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,767評論 25 708
  • 55歲之前想做的事,給自己定位,事業和生活. 事業上,先擁有屬于自己的茶行(雅玩茶舍).集成銷售各類茶品,紫砂壺及...
    雅玩閱讀 224評論 0 0
  • 今天是我實習的最后一天,早就想離開這里的我不知怎么心里卻有了一絲難受,難道是對這里的不舍嗎?說實話這兩天真的有點傷...
    花仙子豬豬閱讀 257評論 0 1
  • 記得剛結婚那會,我和肥佬的做飯水平相當,他的青菜炒得好,我煎魚還可以吧。后來慢慢地,他學會了很多,做飯頗具...
    語歌晨唱閱讀 1,198評論 0 2