【分享】翻出过去的一个多彩泡泡屏保特效(JS+CSS版)

2014/12/25 15:12
阅读数 1.4K

  整理文件时翻出一个好久前做的泡泡屏保的特效,纯JS+CSS做的。回想了下,是去年年初时看见XP下那个流行的泡泡屏保,突然想移植到JS版本来。但有做着才发现有不少麻烦的问题解决不好,于是没继续。

   

  DEMO: http://www.etherdream.com/funnyscript/bubbles/   

  

  和XP系统自带的那个屏保一样,从屏幕一个角落里冒出很多泡泡,然后在屏幕里碰撞反弹。泡泡有着半透明的渐变色,并且颜色也是在不停的变换。

   

  当时这个效果分析了不少时间。如果是用flash那就再简单不过了,把泡泡蒙板的灰色通道复制到一个纯色的背景层的Alpha通道就可以了。但是网页里除非用HTML5的canvas,单凭纯粹的CSS还没那么强大的位图处理能力。在CSS里,和透明度有关的道具也只有这几个:png图片,css alpha值,rgba(),chroma滤镜,mask滤镜,AlphaImageLoader滤镜,以及CSS3的渐变。

  

  

 

  你也许会说,这不是很简单,给png图片层设置各种background-color,不就可以实现颜色的变换了吗。事实上,背景色不但混合到了半透明像素中,连泡泡外的四个边角也给填充了,这样就成了方块,而不是泡泡了,并且颜色也不正确。显然没有这么简单。

  

  因为泡泡是半透明渐变的材质,chroma和mask这些过滤单色的滤镜都派不上用场。而rgba的背景色同样也会出现多余的背景。 AlphaImageLoader滤镜经测试,实际显示出来的图片在background之上, 与<img>载入png效果一样。而CSS3的渐变和本例的蒙板配合起来比较困难,而且兼容性也有问题。

   

  本例的困难之处在于:图片本身不仅是半透明渐变的,并且这些渐变点的颜色还能通过脚本改变。考虑了很久,既然没有一个简便的方法,那不如就用复杂的吧~

 

  大家都知道,颜色都是RGB组成的,调整3种原色的比例,就可以变出各种颜色。我们不妨把灰色蒙板事先填入R,G,B三种纯色,保存为3张图片。这样就有了100%的红色泡泡,绿色泡泡,蓝色泡泡。把他们叠在同个位置,然后给3张图片设置不同的css alpha值,于是就有了各种颜色的泡泡,并且半透明的像素仍然保留!

  

  

 

   于是这个彩色的问题就解决了。不过值得注意的是,蓝色位于最顶层,而红色则是最底层。即使是红色层100%的不透明,也会被蓝色和绿色层的PNG层层剥削,很明显的减淡。所以实际显示时,还需给蓝和绿层分别加个权值,以保证红色通道不会那么的微弱,而蓝的那么的明显。不过在IE里的显示效果仍是蓝色很明显,不知道IE的透明度计算方式和标准浏览器有什么不同。。。 (2010/2/1)

 

  后续:当时对ie的mask滤镜理解不对,mask滤镜并非是单色的过滤,而是:用指定的RGB颜色替换容器内所有点的RGB,并且Alpha'=255 - Alpha。所以ie下只要把蒙板图片的Alpha通道取反,然后同时用AlphaImageLoader和Mask滤镜,即可达到效果,Mask滤镜的color参数就是泡泡的颜色。另在Webkit内核的浏览器下,可以使用-webkit-mask-image直接应用一个图片蒙板!(2011/11/10)


图片资源

html代码

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />
<title>CSS Bubbles</title>
<style>
html, body
{
	border: none;
	overflow: hidden;
	height: 100%;
}

body {background: url(BG.jpg) bottom}
</style>
</head>

<body onload="Demo()">
<script src="Bubbles.js"></script>
<script>
var MAX = 5;
var i = 0;

function Demo()
{
	CreateBubble();

	if(++i < MAX)
		setTimeout(Demo, 1000);
}
</script>
</body>
</html>

Bubbles代码

/**
 * JavaScript Bubbles
 *  By EtherDream 2010
 */
+function()
{
	//
	// 浏览器辅助
	//
	var _VER_ = navigator.userAgent;
	var _IE6_ = /IE 6/.test(_VER_);

	var STD = !!window.addEventListener;
	var de = document.documentElement;

	_IE6_ && document.execCommand("BackgroundImageCache", false, true);


	//
	// 常量
	//
	var D = 222;				//泡泡直径
	var K = 0.999;

	var POW_RATE = 0.0001;		//补偿概率
	var POW_RANGE = 0.8;		//补偿范围(基于诞生速度)

	function SPEED_X(){return 8 + RND() * 4}
	function SPEED_Y(){return 6 + RND() * 2}


	var arrBubs = [];
	var iBottom;
	var iRight;


	var SQRT = Math.sqrt;
	var ATAN2 = Math.atan2;
	var SIN = Math.sin;
	var COS = Math.cos;
	var ABS = Math.abs;
	var RND = Math.random;
	var ROUND = Math.round;


	function Timer(call, time)
	{
		var last = +new Date;
		var delay = 0;

		return setInterval(function()
		{
			// 时间差累计
			var cur = +new Date;
			delay += (cur - last);
			last = cur;

			// 计算帧数
			if(delay >= time)
			{
				call();
				delay %= time;
			}
		}, 1);
	}

	Timer(update, 17);

	CreateBubble = function()
	{
		var bub = new Bubble();

		bub.setX(0);
		bub.setY(0);
		bub.vx = SPEED_X();
		bub.vy = SPEED_Y();

		arrBubs.push(bub);
	};



	function update()
	{
		var n = arrBubs.length;
		var bub, bub2;
		var i, j;


		updateWall();

		for(i=0; i<n; i++)
		{
			bub = arrBubs[i];

			bub.paint();

			bub.vx *= K;
			bub.vy *= K;

			if(RND() < POW_RATE)
			{
				bub.vx = SPEED_X() * (1 + RND() * POW_RANGE);
				bub.vy = SPEED_Y() * (1 + RND() * POW_RANGE);
			}

			bub.setX(bub.x + bub.vx);
			bub.setY(bub.y + bub.vy);
			checkWalls(bub);
		}

		for(i=0; i<n-1; i++)
		{
			bub = arrBubs[i];

			for(j=i+1; j<n; j++)
			{
				bub2 = arrBubs[j];
				checkCollision(bub, bub2);
			}
		}
	}

	function updateWall()
	{
		iRight = de.clientWidth - D;
		iBottom = de.clientHeight - D;
	}

	function checkWalls(bub)
	{
		if(bub.x < 0)
		{
			bub.setX(0);
			bub.vx *= -1;
		}
		else if(bub.x > iRight)
		{
			bub.setX(iRight);
			bub.vx *= -1;
		}

		if(bub.y < 0)
		{
			bub.setY(0);
			bub.vy *= -1;
		}
		else if(bub.y > iBottom)
		{
			bub.setY(iBottom);
			bub.vy *= -1;
		}
	}

	function rotate(x, y, sin, cos, reverse)
	{
		if(reverse)
			return {x: x * cos + y * sin, y: y * cos - x * sin};
		else
			return {x: x * cos - y * sin, y: y * cos + x * sin};
	}

	function checkCollision(bub0, bub1)
	{
		var dx = bub1.x - bub0.x;
		var dy = bub1.y - bub0.y;
		var dist = SQRT(dx*dx + dy*dy);
	
		if(dist < D)
		{
			// 计算角度和正余弦值
			var angle = ATAN2(dy,dx);
			var sin = SIN(angle);
			var cos = COS(angle);

			// 旋转 bub0 的位置
			var pos0 = {x:0, y:0};

			// 旋转 bub1 的速度
			var pos1 = rotate(dx, dy, sin, cos, true);

			// 旋转 bub0 的速度
			var vel0 = rotate(bub0.vx, bub0.vy, sin, cos, true);

			// 旋转 bub1 的速度
			var vel1 = rotate(bub1.vx, bub1.vy, sin, cos, true);

			// 碰撞的作用力
			var vxTotal = vel0.x - vel1.x;
			vel0.x = vel1.x;
			vel1.x = vxTotal + vel0.x;

			// 更新位置
			var absV = ABS(vel0.x) + ABS(vel1.x);
			var overlap = D - ABS(pos0.x - pos1.x);

			pos0.x += vel0.x / absV * overlap;
			pos1.x += vel1.x / absV * overlap;

			// 将位置旋转回来
			var pos0F = rotate(pos0.x, pos0.y, sin, cos, false);
			var pos1F = rotate(pos1.x, pos1.y, sin, cos, false);

			// 将位置调整为屏幕的实际位置
			bub1.setX(bub0.x + pos1F.x);
			bub1.setY(bub0.y + pos1F.y);
			bub0.setX(bub0.x + pos0F.x);
			bub0.setY(bub0.y + pos0F.y);

			// 将速度旋转回来
			var vel0F = rotate(vel0.x, vel0.y, sin, cos, false);
			var vel1F = rotate(vel1.x, vel1.y, sin, cos, false);

			bub0.vx = vel0F.x;
			bub0.vy = vel0F.y;
			bub1.vx = vel1F.x;
			bub1.vy = vel1F.y;
		}
	}



	var APLHA = 0.8;
	var POW = [1, APLHA, APLHA*APLHA];

	/******************************
	 * Class Bubble
	 ******************************/
	function Bubble()
	{
		var kOpa = [], kStp = [];
		var arrFlt = [];
		var oBox = document.body.appendChild(document.createElement("div"));


		styBox = oBox.style;
		styBox.position = "absolute";
		styBox.width = D + "px";
		styBox.height = D + "px";

		for(var i=0; i<4; i++)
		{
			var div = document.createElement("div");
			var sty = div.style;

			sty.position = "absolute";
			sty.width = "222px";
			sty.height = "222px";

			oBox.appendChild(div);

			// 泡泡顶层
			if(i == 3)
			{
				if(_IE6_)
					sty.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src=heart.png)";
				else
					sty.backgroundImage = "url(heart.png)";
				break;
			}

			kOpa[i] = 3 * RND();
			kStp[i] = 0.02 * RND();

			if(STD)
			{
				sty.backgroundImage = "url(ch" + i + ".png)";
				arrFlt[i] = sty;
			}
			else
			{
				sty.filter = "alpha progid:DXImageTransform.Microsoft.AlphaImageLoader(src=ch" + i + ".png)";
				arrFlt[i] = div.filters.alpha;
			}
		}

		this.styBox = styBox;
		this.kOpa = kOpa;
		this.kStp = kStp;
		this.arrFlt = arrFlt;
	}

	Bubble.prototype.setX = function(x)
	{
		this.x = x;
		this.styBox.left = ROUND(x) + "px";
	};

	Bubble.prototype.setY = function(y)
	{
		this.y = y;
		this.styBox.top = ROUND(y) + "px";
	};

	Bubble.prototype.paint = function()
	{
		var i, v;

		for(i=0; i<3; i++)
		{
			v = ABS(SIN(this.kOpa[i] += this.kStp[i] * RND()));
			v *= POW[i];

			v = ((v * 1e4) >> 0) / 1e4;
			this.arrFlt[i].opacity = STD? v : v*100;
		}
	};

}();


展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部