【Canvas与艺术】绘制夜幕星空中的繁星与流星
【关键点】
用alpha值控制星星的闪烁,用线性半透明渐变色绘制流星。
【成图】
【代码】
<!DOCTYPE html> <html lang="utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <head> <title>用Html5/Canvas绘制夜幕星空中的繁星与流星</title> <style type="text/css"> .centerlize{ margin:0 auto; width:1200px; } </style> </head> <body onload="init();"> <div class="centerlize"> <canvas id="myCanvas" width="12px" height="12px" style="border:1px dotted black;"> 如果看到这段文字说您的浏览器尚不支持HTML5 Canvas,请更换浏览器再试. </canvas> </div> </body> </html> <script type="text/javascript"> <!-- /***************************************************************** * 将全体代码(从<!DOCTYPE到script>)拷贝下来,粘贴到文本编辑器中, * 另存为.html文件,再用chrome浏览器打开,就能看到实现效果。 ******************************************************************/ // canvas的绘图环境 var ctx; // 高宽 const WIDTH=1024; const HEIGHT=512; // 舞台对象 var stage; //------------------------------- // 初始化 //------------------------------- function init(){ // 获得canvas对象 var canvas=document.getElementById('myCanvas'); canvas.width=WIDTH; canvas.height=HEIGHT; // 初始化canvas的绘图环境 ctx=canvas.getContext('2d'); ctx.translate(WIDTH/2,HEIGHT/2);// 原点平移 // 准备 stage=new Stage(); stage.init(); // 开幕 animate(); } // 播放动画 function animate(){ stage.update(); stage.paintBg(ctx); stage.paintFg(ctx); // 循环 if(true){ //sleep(100); window.requestAnimationFrame(animate); } } // 舞台类 function Stage(){ // 群山 var peaks=[]; // 群星 var stars=[]; // 流星数组 var shoots=[]; // 初始化 this.init=function(){ // 创建远山开始----> peaks=new Array(); // 左倾山峰 for(var i=0;i<22;i++){ var x=i*50-WIDTH/2; var y=Math.sin(x)+Math.random()*20+HEIGHT/2-30; var p1=createPt(x,y);// 山尖 var h=HEIGHT/2-y; var x1=x+h+Math.random()*8; var p2=createPt(x1,HEIGHT/2);// 左脚 var x2=x-h-Math.random()*32; var p3=createPt(x2,HEIGHT/2);// 右脚 peaks.push({"p1":p1,"p2":p2,"p3":p3}) } // 右倾山峰 for(var i=0;i<11;i++){ var x=i*100-WIDTH/2+Math.random()*25; var y=Math.cos(x)+Math.random()*10+HEIGHT/2-20; var p1=createPt(x,y);// 山尖 var h=HEIGHT/2-y; var x1=x+h+Math.random()*27; var p2=createPt(x1,HEIGHT/2);// 左脚 var x2=x-h-Math.random()*8; var p3=createPt(x2,HEIGHT/2);// 右脚 peaks.push({"p1":p1,"p2":p2,"p3":p3}) } // 创建远山结束<---- // 创建群星 stars=new Array(); for(var i=0;i<100;i++){ var star=this.createStar(); stars.push(star); } } this.createStar=function(){ var star={}; star.x=getRndBetween(-WIDTH/2,WIDTH/2); star.y=getRndBetween(-HEIGHT/2,HEIGHT/2-50); star.rgb=getRgb(); // 闪烁星相关的属性 var seed=Math.random(); star.blink=(seed<0.1); // 与流星相关的属性 star.length=100;// 首尾长度 star.theta=Math.PI/6;// 倾角 star.vx=0; star.vy=0; return star; } // 更新 this.update=function(){ // 让一成群星进行闪烁 for(var i=0;i<stars.length;i++){ var star=stars[i]; if(star.blink){ star.color="rgba("+star.rgb+","+Math.random()+")"; }else{ star.color="rgb("+star.rgb+")"; } } // 更新流星的位置 for(var i=0;i<shoots.length;i++){ var shoot=shoots[i]; shoot.x+=shoot.vx; shoot.y+=shoot.vy; if(shoot.x<-WIDTH/2-100 || shoot.y>HEIGHT/2+100 ){ shoots=[]; // 飞出一颗,增补一颗 var star=this.createStar(); stars.push(star); } } // 当流星数组没有元素时 if(shoots.length<1){ // 从群星选出一颗星变成流星 var idx=getRndFromZeroToN(stars.length-1); var shoot=stars[idx]; shoot.isShoot=true; // 设置速度 var speed=6*Math.random()+0.5; shoot.vx=-speed*Math.cos(shoot.theta); shoot.vy=speed*Math.sin(shoot.theta); // 塞入流星数组 shoots.push(shoot); } } // 画背景 this.paintBg=function(ctx){ ctx.clearRect(-WIDTH/2,-HEIGHT/2,WIDTH,HEIGHT);// 清屏 // 渐变色天空背景 var lgrd = ctx.createLinearGradient(0,-HEIGHT/2,0,HEIGHT/2); lgrd.addColorStop(0.1, "rgb(0,0,2)"); lgrd.addColorStop(0.9, "rgb(0,0,40)"); ctx.fillStyle=lgrd; ctx.fillRect(-WIDTH/2,-HEIGHT/2,WIDTH,HEIGHT); } // 画前景 this.paintFg=function(ctx){ // 绘制群星 for(var i=0;i<stars.length;i++){ var star=stars[i]; ctx.beginPath(); ctx.arc(star.x,star.y,1,0,Math.PI*2,false); ctx.closePath(); ctx.fillStyle=star.color; ctx.fill(); } // 绘制流星 for(var i=0;i<shoots.length;i++){ var s=shoots[i]; var start=createPt(s.x, s.y); var end=createPt(s.x+s.length*Math.cos(s.theta),s.y-s.length*Math.sin(s.theta)); // 绘制尾部,尾部是从起点到终点的直线,在渐变色的作用下直线会变成拖曳长尾的流星形状 ctx.beginPath(); ctx.moveTo(start.x,start.y); ctx.lineTo(end.x,end.y); ctx.lineWidth=2; ctx.lineCap="round"; var lgrd = ctx.createLinearGradient(start.x,start.y,end.x,end.y);// 设置从起点到终点的线性渐变色 lgrd.addColorStop(0, star.color?star.color:"white"); lgrd.addColorStop(0.2, "gray"); lgrd.addColorStop(0.6, "black"); ctx.strokeStyle=lgrd; ctx.stroke(); // 绘制头部 ctx.beginPath(); ctx.arc(start.x,start.y,3,0,Math.PI*2,false); ctx.closePath(); var rgnt=ctx.createRadialGradient(0,0,0,0,0,WIDTH/2);// 绘制从头部扩散开来的径向渐变色 rgnt.addColorStop(0,"rgba("+s.rgb+",0.1)"); rgnt.addColorStop(1,"rgba(1,1,1,0.88)"); ctx.fillStyle=rgnt; ctx.fill(); } // 最后绘制山峰,以图让流星落在山后 for(var i=0;i<peaks.length;i++){ var peak=peaks[i]; ctx.beginPath(); ctx.moveTo(peak.p1.x,peak.p1.y); ctx.lineTo(peak.p2.x,peak.p2.y); ctx.lineTo(peak.p3.x,peak.p3.y); ctx.closePath(); ctx.fillStyle="black" ctx.fill(); } writeText(ctx,WIDTH/2-30,HEIGHT/2-5,"逆火原创","8px consolas","white");// 版权 } } // 取得一种颜色的RGB值 function getRgb(){ var arr=["255,255,255",// 白 "255,255,255",// 白 "255,255,255",// 白 "255,255,255",// 白 "255,255,255",// 白 "255,255,255",// 白 "255,255,255",// 白 "255,255,255",// 白 "255,255,255",// 白 "255,255,255",// 白 "196,196,0",// 黄 "196,196,0",// 黄 "196,196,0",// 黄 "255,128,64",// 橙 "255,128,64",// 橙 "196,136,136",// 红 ]; // 打乱数组 var n = arr.length; while(n--) { var index = Math.floor(Math.random() * n); var temp = arr[index]; arr[index] = arr[n]; arr[n] = temp; } return arr[getRndFromZeroToN(arr.length-1)]; } //------------------------------------------------------------ // 得到0到N(包括0和N)的随机整数 // 如果要得到数组里面的一个要把数组长度减一 //------------------------------------------------------------ function getRndFromZeroToN(n){ return Math.floor(Math.random()*(n+1)); } //------------------------------------------------------------ // 得到lowerLimit到upperlimit(包括端点值)的随机整数 //------------------------------------------------------------ function getRndBetween(lowerLimit,upperLimit){ return Math.floor(Math.random()*(upperLimit-lowerLimit+1))+lowerLimit; } /*---------------------------------------------------------- 函数:创建一个二维坐标点 x:横坐标 y:纵坐标 Pt即Point ----------------------------------------------------------*/ function createPt(x,y){ var retval={}; retval.x=x; retval.y=y; return retval; } /*---------------------------------------------------------- 函数:延时若干毫秒 milliseconds:毫秒数 ----------------------------------------------------------*/ function sleep(milliSeconds) { const date = Date.now(); let currDate = null; while (currDate - date < milliSeconds) { currDate = Date.now(); } } /*---------------------------------------------------------- 函数:书写文字 ctx:绘图上下文 x:横坐标 y:纵坐标 text:文字 font:字体 color:颜色 ----------------------------------------------------------*/ function writeText(ctx,x,y,text,font,color){ ctx.save(); ctx.textBaseline="bottom"; ctx.textAlign="center"; ctx.font = font; ctx.fillStyle=color; ctx.fillText(text,x,y); ctx.restore(); } /*------------------------------------------------------------- 在闻名世界的威斯特敏斯特大教堂地下室的墓碑林中,有一块名扬世界的墓碑。其实这只是一块很普通的墓碑,粗糙的花岗石质地,造型也很一般,同周围那些质地上乘、做工优良的亨利三世到乔治二世等二十多位英国前国王墓碑,以及牛顿、达尔文、狄更斯等名人的墓碑比较起来,它显得微不足道,不值一提。并且它没有姓名,没有生卒年月,甚至上面连墓主的介绍文字也没有。 但是,就是这样一块无名氏墓碑,却成为名扬全球的着名墓碑。每一个到过威斯特敏斯特大教堂的人,他们可以不去拜谒那些曾经显赫一世的英国前国王们,可以不去拜谒那诸如狄更斯、达尔文等世界名人们,但他们却没有人不来拜谒这一块普通的墓碑,他们都被这块墓碑深深的震撼着,准确地说,他们被这块墓碑上的碑文深深地震撼着。在这块墓碑上,刻着这样的一段话: 当我年轻的时候,我的想象力从没有受到过限制,我梦想改变这个世界; 当我成熟以后,我发现我不能改变这个世界,我将目光缩短了些,决定只改变我的国家; 当我进入暮年后,我发现我不能改变我的国家,我的最后愿望仅仅是改变一下我的家庭。但是,这也不可能。 当我躺在床上,行将就木时,我突然意识到:如果一开始我仅仅去改变我自己,然后作为一个榜样,我可能改变我的家庭; 在家人的帮助和鼓励下,我可能会为国家做一些事情。然后谁知道呢?我甚至可能改变这个世界。 据说,许多世界政要和名人看到这块碑文时都感慨不已。有人说这是一篇人生的教义,有人说这是灵魂的一种自省。当年轻的曼德拉看到这篇碑文时,顿然有醒醐灌顶之感,声称自己从中找到了改变南非甚至整个世界的金钥匙。回到南非后,这个志向远大、原本赞同以爆治爆垫平种族歧视鸿沟的黑人青年,一下子改变的自己的思想和处世风格,他从改变自己、改变自己的家庭和亲朋好友着手,经历了几十年,终于改变了他的国家。 真的,要想撬起世界,它的最佳支点不是地球,不是国家、民族,也不是别人,而是自己的心灵。 与其怨天尤人,不如全力以赴;若想改变世界,你必须先从改变自己开始! --------------------------------------------------------------*/ //--> </script>
END