学习 canvas 的 globalCompositeOperation 刮刮卡

定义

globalCompositeOperation 属性设置或返回如何将一个源(新的)图像绘制到目标(已有)的图像上。 源图像 = 您打算放置到画布上的绘图。 目标图像 = 您已经放置在画布上的绘图。

这个属性用来设置要在绘制新形状时应用的合成操作的类型,比如在一个蓝色的矩形上画一个红色的圆形,是红色在上显示,还是蓝色在上显示,重叠的部分显示还是不显示,不重叠的部分又怎么显示,等一些情况,在面对这些情况的时候,就是 globalCompositeOperation 属性起作用的时候了。 在取默认值的情况下,都是显示的,新画的图形会覆盖原来的图形。

用法

默认值: source-over 语法: context.globalCompositeOperation="source-in";

表格中的蓝色矩形为目标图像,红色圆形为源图像。

属性值

描述

效果

source-over

默认。在目标图像上显示源图像。

source-atop

在目标图像顶部显示源图像。源图像位于目标图像之外的部分是不可见的。

source-in

在目标图像中显示源图像。只有目标图像内的源图像部分会显示,目标图像是透明的。

source-out

在目标图像之外显示源图像。只会显示目标图像之外源图像部分,目标图像是透明的。

destination-over

在源图像上方显示目标图像。

destination-atop

在源图像顶部显示目标图像。源图像之外的目标图像部分不会被显示。

destination-in

在源图像中显示目标图像。只有源图像内的目标图像部分会被显示,源图像是透明的。

destination-out

在源图像外显示目标图像。只有源图像外的目标图像部分会被显示,源图像是透明的。

lighter

显示源图像 + 目标图像。

copy

显示源图像。忽略目标图像。

xor

使用异或操作对源图像与目标图像进行组合。

好的,下来实现一个水滴扩散的效果 效果图 

 

实现思路

在一个 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://www.kkkk1000.com/images/bg2.png", "https://www.kkkk1000.com/images/clear.png"];
		// 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://www.kkkk1000.com/images/bg.jpg)';
			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,当变量的值超过某一个值时,就清除全部灰色。

代码在这里-- 见代码1icon-default.png?t=N7T8https://cloud.tencent.com/developer/tools/blog-entry?target=https%3A%2F%2Fcodepen.io%2FFEWY%2Fpen%2FBOjmyg&source=article&objectId=1436244

第二种 就直接看移动了多少,鼠标移动时,会有一个变量进行自增运算,当这个变量,超过一定值时,就擦除全部灰色。

代码在这里-- 见代码2icon-default.png?t=N7T8https://cloud.tencent.com/developer/tools/blog-entry?target=https%3A%2F%2Fcodepen.io%2FFEWY%2Fpen%2FeLJeNv&source=article&objectId=1436244

注意: 第一种方式使用 getImageData 存在跨域问题,不过因为这个效果中,没有在canvas上画图片,而是设置canvas的 background 为一张图片,所以这个还没有影响,但是如果canvas上画了其他图片,就可能需要处理跨域的问题了。 使用 getImageData 能获取到 canvas 上的像素信息,就可以根据刮刮卡上灰色的面积,决定擦除全部灰色的时机,更加灵活。

第二种方式,虽然不存在跨域的问题,但是,不能很好的根据刮刮卡上灰色的面积,控制最后擦除全部灰色的时机。

代码1

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



	var canvas = document.getElementById("canvas");
		var context = canvas.getContext("2d");
		// canvas的宽度
		var width = 400;
		// canvas的高度
		var height = 250;

		init()
		function init() { 
			// 先在canvas上画一个灰色的矩形
			context.fillStyle = '#ddd';
			context.fillRect(0, 0, width, height); 
			// 设置canvas的背景为一张图片
			canvas.style.background = 'url("https://www.kkkk1000.com/images/globalCompositeOperation/bg3.jpg") no-repeat center';
			canvas.style.backgroundSize = "100% 100%";
 
			// 设置画的线的宽度			
			context.lineWidth = 35;
			// 设置线交汇时,是圆角的
			context.lineJoin = "round";
		}

 

		canvas.addEventListener('mousedown', mouseDown, false);
		canvas.addEventListener('mousemove', mouseMove, false);
		canvas.addEventListener('mouseup', mouseUp, false);
		
		/* 如果需要移动端也可以生效,
		需要绑定touchstart、touchmove、touchend 事件,并且获取触摸点的坐标
		*/
		//canvas.addEventListener('touchstart', mouseDown, false);
		//canvas.addEventListener('touchmove', mouseMove, false);
		//canvas.addEventListener('touchend', mouseUp, false);
		

		// 判断是否可以画线
		var isDrawing; 
		// 保存开始画线时,线的起点的X坐标
		var startX=0;
		// 保存开始画线时,线的起点的Y坐标
		var startY=0; 

		// 按下鼠标按钮时,调用mouseDown
		function mouseDown(e){
			 isDrawing = true;
			 // 保存鼠标点击时 X坐标为,画线时,线的起点的X坐标
			 startX = e.layerX;
			 // 保存鼠标点击时 Y坐标为,画线时,线的起点的Y坐标
			 startY = e.layerY;

			 /* 移动端使用下面的方法 获取 startX 和 startY
			 startX = e.touches[0].clientX;
			 startY = e.touches[0].clientY;
			 */
		}

		// 鼠标移动时,调用mouseDown
		function mouseMove(e){
			if(isDrawing){
			// 获取鼠标相对于 canvas 的坐标
			var x = e.layerX;
			var y = e.layerY;

			/* 移动端使用下面的方法 获取 x 和 y
			var x = e.touches[0].clientX;
			var y = e.touches[0].clientY;
			*/ 
			context.globalCompositeOperation = "destination-out"; 

			// 开始画线
			context.beginPath();
			// 起点坐标为 startX 和 startY
			context.moveTo(startX, startY);
			// 结束的坐标为这次移动时的坐标
			context.lineTo(x, y);
			context.closePath(); 
			context.stroke();

			// 设置这次移动结束时的坐标,为下次开始画线时的坐标
			startX = x;
			startY = y; 
			} 
		}

		// 松开鼠标按钮时,调用的事件
		function mouseUp(e){
			// isDrawing 为false时,不可以画线
			isDrawing = false;
			// 获取图片像素信息
			var data = context.getImageData(0, 0, width, height).data;
			console.log("图片像素信息",data); 
			var length = data.length;
			var k = 0;

			// 如果一个像素是透明的(值都是0),k就+1
			for (var i = 0; i < length - 3; i += 4) {
				if (data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 0 && data[i + 3] == 0) {
					k++;
				}
			}

			// 当k > width*height*0.2 时,
			// 也就是说有20%的面积是透明的时,就把整个canvas的背景显示出来
			if(k>width*height*0.2){
				 context.fillStyle = "blue";
				 context.fillRect(0, 0, width, height);
			}
		}

代码2

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




	var canvas = document.getElementById("canvas");
		var context = canvas.getContext("2d");
		// canvas的宽度
		var width = 400;
		// canvas的高度
		var height = 250;

		init()
		function init() { 
			// 先在canvas上画一个灰色的矩形
			context.fillStyle = '#ddd';
			context.fillRect(0, 0, width, height); 
			// 设置canvas的背景为一张图片
			// 因为 getImageData 方法需要跨域,所以这里用 base64来保存图片
			canvas.style.background = 'url("https://www.kkkk1000.com/images/globalCompositeOperation/bg3.jpg") no-repeat center';
			canvas.style.backgroundSize = "100% 100%";
 
			// 设置画的线的宽度			
			context.lineWidth = 35;
			// 设置线交汇时,是圆角的
			context.lineJoin = "round";
		}

 

		canvas.addEventListener('mousedown', mouseDown, false);
		canvas.addEventListener('mousemove', mouseMove, false);
		canvas.addEventListener('mouseup', mouseUp, false);
		
		/* 如果需要移动端也可以生效,
		需要绑定touchstart、touchmove、touchend 事件,并且获取触摸点的坐标
		*/
		//canvas.addEventListener('touchstart', mouseDown, false);
		//canvas.addEventListener('touchmove', mouseMove, false);
		//canvas.addEventListener('touchend', mouseUp, false);
		

		// 判断是否可以画线
		var isDrawing; 
		// 保存开始画线时,线的起点的X坐标
		var startX=0;
		// 保存开始画线时,线的起点的Y坐标
		var startY=0; 
		// removeGrey 用来判断是否擦除全部灰色
		var removeGrey=0;
		// 按下鼠标按钮时,调用mouseDown
		function mouseDown(e){
			 isDrawing = true;
			 // 保存鼠标点击时 X坐标为,画线时,线的起点的X坐标
			 startX = e.layerX;
			 // 保存鼠标点击时 Y坐标为,画线时,线的起点的Y坐标
			 startY = e.layerY;

			 /* 移动端使用下面的方法 获取 startX 和 startY
			 startX = e.touches[0].clientX;
			 startY = e.touches[0].clientY;
			 */
		}

		// 鼠标移动时,调用mouseDown
		function mouseMove(e){
			if(isDrawing){
			removeGrey++;
			// 获取鼠标相对于 canvas 的坐标
			var x = e.layerX;
			var y = e.layerY;

			/* 移动端使用下面的方法 获取 x 和 y
			var x = e.touches[0].clientX;
			var y = e.touches[0].clientY;
			*/ 
			context.globalCompositeOperation = "destination-out"; 

			// 开始画线
			context.beginPath();
			// 起点坐标为 startX 和 startY
			context.moveTo(startX, startY);
			// 结束的坐标为这次移动时的坐标
			context.lineTo(x, y);
			context.closePath(); 
			context.stroke();

			// 设置这次移动结束时的坐标,为下次开始画线时的坐标
			startX = x;
			startY = y; 
			} 
		}

		// 松开鼠标按钮时,调用的事件
		function mouseUp(e){
			// isDrawing 为false时,不可以画线
			isDrawing = false;
			console.log(removeGrey);
  
			// 当removeGrey > 100时,清除全部灰色
			if(removeGrey>100){
				 context.fillStyle = "blue";
				 context.fillRect(0, 0, width, height);
			}
		}

refs
globalCompositeOperation
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值