整理文件時翻出一個很久前作的泡泡屏保的特效,純JS+CSS作的。回想了下,是去年年初時看見XP下那個流行的泡泡屏保,忽然想移植到JS版原本。但有作着才發現有很多麻煩的問題解決很差,因而沒繼續。css
DEMO: http://www.etherdream.com/funnyscript/bubbles/ html
和XP系統自帶的那個屏保同樣,從屏幕一個角落裏冒出不少泡泡,而後在屏幕裏碰撞反彈。泡泡有着半透明的漸變色,而且顏色也是在不停的變換。web
當時這個效果分析了很多時間。若是是用flash那就再簡單不過了,把泡泡蒙板的灰色通道複製到一個純色的背景層的Alpha通道就能夠了。可是網頁裏除非用HTML5的canvas,單憑純粹的CSS還沒那麼強大的位圖處理能力。在CSS裏,和透明度有關的道具也只有這幾個:png圖片,css alpha值,rgba(),chroma濾鏡,mask濾鏡,AlphaImageLoader濾鏡,以及CSS3的漸變。canvas
你也許會說,這不是很簡單,給png圖片層設置各類background-color,不就能夠實現顏色的變換了嗎。事實上,背景色不但混合到了半透明像素中,連泡泡外的四個邊角也給填充了,這樣就成了方塊,而不是泡泡了,而且顏色也不正確。顯然沒有這麼簡單。瀏覽器
由於泡泡是半透明漸變的材質,chroma和mask這些過濾單色的濾鏡都派不上用場。而rgba的背景色一樣也會出現多餘的背景。 AlphaImageLoader濾鏡經測試,實際顯示出來的圖片在background之上, 與<img>載入png效果同樣。而CSS3的漸變和本例的蒙板配合起來比較困難,並且兼容性也有問題。app
本例的困難之處在於:圖片自己不只是半透明漸變的,而且這些漸變點的顏色還能經過腳本改變。考慮了好久,既然沒有一個簡便的方法,那不如就用複雜的吧~dom
你們都知道,顏色都是RGB組成的,調整3種原色的比例,就能夠變出各類顏色。咱們不妨把灰色蒙板事先填入R,G,B三種純色,保存爲3張圖片。這樣就有了100%的紅色泡泡,綠色泡泡,藍色泡泡。把他們疊在同個位置,而後給3張圖片設置不一樣的css alpha值,因而就有了各類顏色的泡泡,而且半透明的像素仍然保留!測試
因而這個彩色的問題就解決了。不過值得注意的是,藍色位於最頂層,而紅色則是最底層。即便是紅色層100%的不透明,也會被藍色和綠色層的PNG層層剝削,很明顯的減淡。因此實際顯示時,還需給藍和綠層分別加個權值,以保證紅色通道不會那麼的微弱,而藍的那麼的明顯。不過在IE裏的顯示效果還是藍色很明顯,不知道IE的透明度計算方式和標準瀏覽器有什麼不一樣。。。 (2010/2/1)ui
後續:當時對ie的mask濾鏡理解不對,mask濾鏡並不是是單色的過濾,而是:用指定的RGB顏色替換容器內全部點的RGB,而且Alpha'=255 - Alpha。因此ie下只要把蒙板圖片的Alpha通道取反,而後同時用AlphaImageLoader和Mask濾鏡,便可達到效果,Mask濾鏡的color參數就是泡泡的顏色。另在Webkit內核的瀏覽器下,可使用-webkit-mask-image直接應用一個圖片蒙板!(2011/11/10)this
圖片資源
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; } }; }();