【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

posted @ 2017-04-21 20:21  逆火狂飙  阅读(382)  评论(0编辑  收藏  举报
生当作人杰 死亦为鬼雄 至今思项羽 不肯过江东