學習 canvas 的 globalCompositeOperation 做出的神奇效果

FEWY發表於2018-08-30

說明

最早知道 canvas 的 globalCompositeOperation 屬性,是在需要實現一個刮刮卡效果的時候,當時也就是網上找到刮刮卡的效果趕緊完成任務就完了,這次又學習一次,希望能加深理解吧。

先來看下 canvas 的 globalCompositeOperation屬性,具體是幹什麼的。

定義

globalCompositeOperation 屬性設定或返回如何將一個源(新的)影像繪製到目標(已有)的影像上。
源影像 = 您打算放置到畫布上的繪圖。
目標影像 = 您已經放置在畫布上的繪圖。

這個屬性用來設定要在繪製新形狀時應用的合成操作的型別,比如在一個藍色的矩形上畫一個紅色的圓形,是紅色在上顯示,還是藍色在上顯示,重疊的部分顯示還是不顯示,不重疊的部分又怎麼顯示,等一些情況,在面對這些情況的時候,就是 globalCompositeOperation 屬性起作用的時候了。
在取預設值的情況下,都是顯示的,新畫的圖形會覆蓋原來的圖形。

用法

預設值: source-over
語法: context.globalCompositeOperation="source-in";

表格中的藍色矩形為目標影像,紅色圓形為源影像。

屬性值 描述 效果
source-over 預設。在目標影像上顯示源影像。
學習 canvas 的 globalCompositeOperation 做出的神奇效果
source-atop 在目標影像頂部顯示源影像。源影像位於目標影像之外的部分是不可見的。
學習 canvas 的 globalCompositeOperation 做出的神奇效果
source-in 在目標影像中顯示源影像。只有目標影像內的源影像部分會顯示,目標影像是透明的。
學習 canvas 的 globalCompositeOperation 做出的神奇效果
source-out 在目標影像之外顯示源影像。只會顯示目標影像之外源影像部分,目標影像是透明的。
學習 canvas 的 globalCompositeOperation 做出的神奇效果
destination-over 在源影像上方顯示目標影像。
學習 canvas 的 globalCompositeOperation 做出的神奇效果
destination-atop 在源影像頂部顯示目標影像。源影像之外的目標影像部分不會被顯示。
學習 canvas 的 globalCompositeOperation 做出的神奇效果
destination-in 在源影像中顯示目標影像。只有源影像內的目標影像部分會被顯示,源影像是透明的。
學習 canvas 的 globalCompositeOperation 做出的神奇效果
destination-out 在源影像外顯示目標影像。只有源影像外的目標影像部分會被顯示,源影像是透明的。
學習 canvas 的 globalCompositeOperation 做出的神奇效果
lighter 顯示源影像 + 目標影像。
學習 canvas 的 globalCompositeOperation 做出的神奇效果
copy 顯示源影像。忽略目標影像。
學習 canvas 的 globalCompositeOperation 做出的神奇效果
xor 使用異或操作對源影像與目標影像進行組合。
學習 canvas 的 globalCompositeOperation 做出的神奇效果

好的,下來實現一個水滴擴散的效果
效果圖

學習 canvas 的 globalCompositeOperation 做出的神奇效果

實現思路

在一個 canvas 上先畫出黑白色的圖片,然後設定背景是一張彩色的圖片,滑鼠點選時,設定 canvas 的 globalCompositeOperation 屬性值為 destination-out,根據滑鼠在 canvas 中的 座標,用一個不規則的圖形逐漸增大,來擦除掉黑白色的圖片,就可以慢慢顯示彩色的背景了。

也就是說我們需要三張圖片

黑白的圖片

這裡寫圖片描述

彩色的圖片

這裡寫圖片描述

不規則形狀的圖片

這裡寫圖片描述

程式碼

<!doctype html>
<html>

<head>
	<meta charset="UTF-8">
	<style>
		canvas {
			/* 設定滑鼠的游標是一張圖片, 16和22 分別表示熱點的X座標和Y座標 */
			/* https://developer.mozilla.org/zh-CN/docs/Web/CSS/cursor/url */
			cursor: url('https://www.kkkk1000.com/images/mouse.png') 16 22, auto;
		}
	</style>
</head>

<body>
	<canvas id="canvas" width="400px" height="250px"></canvas>

	<script type="text/javascript"> 
		var canvas = document.getElementById("canvas");
		var context = canvas.getContext("2d");

		// 儲存圖片路徑的陣列
		var urlArr = ["https://user-gold-cdn.xitu.io/2018/8/30/1658abd14d14b88a?w=640&h=360&f=png&s=221487", "https://user-gold-cdn.xitu.io/2018/8/30/1658abd15d587ca5?w=100&h=100&f=png&s=2721"];
		// imgArr 儲存載入後的圖片的陣列,imgArr中儲存的是真實的圖片
		// loadImg 函式用來載入 urlArr 中所有的圖片
		// 並返回一個儲存所有圖片的陣列
		var imgArr = loadImg(urlArr);
		// flag 用來限制 點選事件,一張圖片只會產生一次效果
		var flag = false;
 

		function loadImg(urlArr) {
			var index = 0;
			var res = [];
			// 每次給 load 函式傳入一個圖片路徑,來載入圖片
			load(urlArr[index]);
			function load(url) {
				// 如果 index 等於 urlArr.length,
				// 表示載入完 全部圖片了,就結束 load函式
				if (index == urlArr.length) {
					// 載入完全部圖片,呼叫 init 函式
					init();
					return;
				}

				var img = new Image();
				img.src = url;
				// 不管當前圖片是否載入成功,都要載入下一張圖片
				img.onload = next;
				img.onerror = function () {
					console.log(res[index] + "載入失敗");
					next();
				}
				// next 用來載入下一張圖片
				function next() {
					// 把載入後的圖片,儲存到 res 中
					res[index] = img;
					load(urlArr[++index])
				}
			}
			// 最後返回儲存所有真實圖片的陣列
			return res;
		}

		function init() {
			// 先在canvas上畫黑白的圖片,然後再設定背景是彩色的圖片
			// 避免先顯示出彩色圖片,再顯示出黑白的圖片
			context.globalCompositeOperation = "source-over";
			context.drawImage(imgArr[0], 0, 0, 400, 250);
			canvas.style.background = 'url(https://user-gold-cdn.xitu.io/2018/8/30/1658abd157ad9e8b?w=640&h=360&f=jpeg&s=44022)';
			canvas.style.backgroundSize = "100% 100%";

			// flag 是 true 時,滑鼠點選才有水滴擴散的效果
			flag = true;
			// canvas 繫結點選事件,點選時產生水滴擴散效果
			canvas.onclick =  diffusion;
		}

		// width 表示 不規則形狀的圖片的尺寸
		var width = 0;
		// speed 表示擴散效果的速度
		var speed = 8;
		// diffusion 函式根據滑鼠座標,產生效果
		function  diffusion (e) {
			if (flag) {
				flag = false;
				context.globalCompositeOperation = "destination-out";
				window.requestAnimationFrame(draw);
				// 根據滑鼠座標,畫擴散效果
				function draw() {
					// 這裡不一定需要是 1800 ,但必須是一個足夠大的數,可以擴散出整張背景圖
					if (width > 1800) {
						flag = true;
						return;
					}
					width += speed;
					// 獲取滑鼠相對於 canvas 的座標
					var x = e.layerX;
					var y = e.layerY;

					// 畫不規則形狀的圖片,逐漸增大圖片尺寸
					context.drawImage(imgArr[1], x - (width / 2), y - (width / 2), width, width);
					window.requestAnimationFrame(draw);
				}
			}
		}
	</script>
</body>

</html>
複製程式碼

我們繼續來實現一個刮刮卡的效果

效果圖

這裡寫圖片描述

刮刮卡效果實現的思路:

一個 canvas 上先畫一層灰色,然後設定canvas的背景圖,設定 canvas 的 globalCompositeOperation屬性值為 destination-out,點選並移動時,根據移動點的座標,擦除掉灰色,當擦掉一部分時,再自動擦除掉全部灰色,顯示出背景來。

刮刮卡的效果和水滴擴散的效果,在開始的時候幾乎是一樣的,不過水滴擴散效果,用的是一張不規則形狀的圖片來清除黑白圖片,而刮刮卡效果,是通過畫線的方式,線比較粗而已,來清除上面的灰色。 主要的不同是,刮刮卡效果最後需要自動擦除掉全部灰色,這裡有兩種方式。

第一種
使用 canvas 的 getImageData 方法,來獲取 canvas 上的畫素資訊,這個方法返回的物件的 data 屬性是一個一維陣列,包含以 RGBA 順序的資料,資料使用 0 至 255(包含)的整數表示,詳細的可以看看 canvas 的畫素操作
用這個方法來判斷有多少已經擦除掉了,也就是通過一個變數來記錄有多少畫素的RGBA的值是0,當變數的值超過某一個值時,就清除全部灰色。

程式碼在這裡

第二種
就直接看移動了多少,滑鼠移動時,會有一個變數進行自增運算,當這個變數,超過一定值時,就擦除全部灰色。

程式碼在這裡

注意:
第一種方式使用 getImageData 存在跨域問題,不過因為這個效果中,沒有在canvas上畫圖片,而是設定canvas的 background 為一張圖片,所以這個還沒有影響,但是如果canvas上畫了其他圖片,就可能需要處理跨域的問題了。
使用 getImageData 能獲取到 canvas 上的畫素資訊,就可以根據刮刮卡上灰色的面積,決定擦除全部灰色的時機,更加靈活。

第二種方式,雖然不存在跨域的問題,但是,不能很好的根據刮刮卡上灰色的面積,控制最後擦除全部灰色的時機。

總結

文章中的效果主要是使用 globalCompositeOperation屬性取值為 destination-out ,而取值為其他值的時候,同樣也是可以製作出各種效果的,大家也可以發揮自己的想象力,去試試其它值,也許有新發現呢。

這裡寫圖片描述

相關文章