canvas绘制太阳系

作者: jeffzhong | 来源:发表于2017-12-01 15:07 被阅读60次

      原文地址:canvas绘制太阳系
      学习canvas有一段时间了,顺便写个小项目练手,该项目用到的知识点包括:

    1. ES6面向对象
    2. 基本的三角函数
    3. canvas部分有:坐标变换,渐变,混合模式,线条和图形的绘制。

    实际效果: solar system(推荐在chrome或safari下运行)

    solar system

    场景

      首先建立场景类,主要用来组织管理对象,统一更新和绘制对象。这里用到了ES6的类语法,构造函数建立对象列表属性planets,绘制背景方法drawBG,使用requestAnimationFrame反复执行的动画方法animate

      绘制背景使用到了径向渐变:createRadialGradient(x1,y1,r1,x2,y2,r2); 该渐变主要用于创建两个圆相交过渡效果,如果前后两个圆心相同(x1==x2 && y1==y2),则会构造同心圆样式的渐变。 这样我们就以太阳为中心的黄色调渐变到黑色,最后用fillRect填充整个背景。

        //场景
        class Stage {
            constructor(){
                this.planets=[];
            }
            init(ctx){
                ctx.translate(W/2,H/2);//坐标重置为中间
                this.animate(ctx);
            }
            //绘制背景
            drawBG(ctx){
                ctx.save();
                ctx.globalCompositeOperation = "source-over";
                var gradient=ctx.createRadialGradient(0,0,0,0,0,600);
                gradient.addColorStop(0,'rgba(3,12,13,0.1)');
                gradient.addColorStop(1,'rgba(0,0,0,1');
                ctx.fillStyle=gradient;
                // ctx.fillStyle='rgba(0,0,0,0.9)';
                ctx.fillRect(-W/2,-H/2,W,H);
                ctx.restore();
            }
            //执行动画
            animate(ctx){
                var that=this,
                    startTime=new Date();
                (function run(){
                    that.drawBG(ctx);
                    that.planets.forEach(item=>{
                        item.update(startTime);
                        item.draw(ctx);
                    });
                    requestAnimationFrame(run);
                }());
            }
        }
    

    星球

      然后建立星球基类,除构造函数,还有更新位置角度的方法Update,对象绘制方法draw。之后所有的星球,都会初始化该类或者继承该类建立对应星球。

      行星绕太阳做圆周运动,这个可以用三角函数根据角度和半径求出x,y,但还有更加方便的方法,那就是使用canvas提供的坐标旋转方法rotate,以360度为一个周期。

        /**
         * 星球基类
         */
        class Planet{
            /**
             * @param  {Number} x         x坐标
             * @param  {Number} y         y坐标
             * @param  {Number} r         半径
             * @param  {Number} duration  周期(秒)
             * @param  {Object} fillStyle 
             * @param  {Object} blurStyle 
             */
            constructor(x,y,r,duration,fillStyle,blurStyle){
                this.x=x;
                this.y=y;
                this.r=r;
                this.duration=duration;
                this.angle=0;
                this.fillStyle=fillStyle;
                this.blurStyle=blurStyle;
            }
            update(startTime){
                this.angle=Tween.linear(new Date()-startTime,0,Math.PI*2,this.duration*1000);
            }
            draw(ctx){
                ctx.save();
                ctx.rotate(this.angle);
                // ctx.translate(this.x,this.y);
                drawCircle(this.x,this.blurStyle.color);
                ctx.beginPath();
                // ctx.globalCompositeOperation = "lighter";
                ctx.fillStyle=this.fillStyle;
                ctx.shadowColor=this.blurStyle.color;
                ctx.shadowBlur=this.blurStyle.blur;             
                // ctx.arc(0,0,this.r,Math.PI*2,false);
                ctx.arc(this.x,this.y,this.r,0,Math.PI*2,false);
                ctx.fill();
                ctx.restore();
            }
        };
    

    太阳

      开始建立第一个对象-太阳,继承上面的星球基类Planet,重写draw方法

        /**
         * 太阳
         */
        class Sun extends Planet{
            draw(ctx){
                ctx.save();
                ctx.beginPath();
                ctx.globalCompositeOperation = "source-over";
                ctx.fillStyle=this.fillStyle;
                ctx.shadowColor=this.blurStyle.color;
                ctx.shadowBlur=this.blurStyle.blur;             
                ctx.arc(this.x,this.y,this.r,0,Math.PI*2,false);
                ctx.fill();
                ctx.restore();  
            }
        }
    

    土星

      土星有美丽的土星环,所以也继承出一个单独的类,重写draw方法,其中土星环比较麻烦,建立了很多颜色节点的径向渐变。

        /**
         * 土星
         */
        class Saturn extends Planet{
            draw(ctx){
                ctx.save();
                ctx.rotate(this.angle);
                drawCircle(this.x,this.blurStyle.color);
    
                ctx.beginPath();
                ctx.fillStyle=this.fillStyle;           
                ctx.arc(this.x,this.y,this.r,Math.PI*2,false);
                ctx.fill();
    
                //土星光环
                ctx.globalCompositeOperation = "source-over";
                var gradient=ctx.createRadialGradient(this.x,this.y,0,this.x,this.y,this.r+25);
                var startStop=(this.r+3)/(this.r+24);
                gradient.addColorStop(startStop,'#282421');
                gradient.addColorStop(startStop+0.06,'#282421');
                gradient.addColorStop(startStop+0.1,'#7e7966');
                gradient.addColorStop(startStop+0.18,'#706756');
                gradient.addColorStop(startStop+0.24,'#7e7966');
                gradient.addColorStop(startStop+0.25,'#282421');
                gradient.addColorStop(startStop+0.26,'#282421');
                gradient.addColorStop(startStop+0.27,'#807766');
                gradient.addColorStop(1,'#595345');
                ctx.fillStyle=gradient;
                ctx.beginPath();
                ctx.arc(this.x,this.y,this.r+24,0,Math.PI*2,true);
                ctx.arc(this.x,this.y,this.r+3,0,Math.PI*2,false);
                ctx.fill();
                ctx.restore();  
            }
        }
    

    建立星球

      接着开始初始化星球对象,包括太阳和八大行星,然后所有的星球颜色都使用了径向渐变,这样更加的美观。这里给出太阳,水星,土星的例子,其他的行星如此类推。

        // 初始化场景类
        var stage=new Stage();
    
        // sun
        var sunStyle=ctx.createRadialGradient(0,0,0,0,0,60);
        sunStyle.addColorStop(0,'white');
        sunStyle.addColorStop(0.5,'white');
        sunStyle.addColorStop(0.8,'#ffca1e');
        sunStyle.addColorStop(1,'#b4421d');
        var sun=new Sun(0,0,60,0,sunStyle,{color:'#b4421d',blur:300});
        stage.planets.push(sun);
    
        // mercury
        var mercuryStyle=ctx.createRadialGradient(100,0,0,100,0,9);
        mercuryStyle.addColorStop(0,'#75705a');
        mercuryStyle.addColorStop(1,'#464646');
        var mercury=new Planet(100,0,9,8.77,mercuryStyle,{color:'#464646'});
        stage.planets.push(mercury);
    
        
        //saturn 
        var saturnStyle=ctx.createRadialGradient(500,0,0,500,0,26);
        saturnStyle.addColorStop(0,'#f2e558');
        saturnStyle.addColorStop(1,'#4c4a3b');
        var saturn =new Saturn(500,0,26,1075.995,saturnStyle,{color:'#4c4a3b'});
        stage.planets.push(saturn);
    

    小行星带

      当然还有火星和木星之间的小行星带,同理继承星球基类,这里用到了图像混合模式globalCompositeOperation,使用xor可以和背景对比度没那么突兀。当然还有其他属性值,比如source-over, lighter等。这里我们随机生成了300个对象,一样填充进场景类的planets属性统一更新绘制。

        /**
         * 小行星
         */
        class Asteroid extends Planet{
            draw(ctx){
                ctx.save();
                ctx.rotate(this.angle);
                ctx.beginPath();
                ctx.globalCompositeOperation = "xor";
                ctx.fillStyle=this.fillStyle;           
                ctx.arc(this.x,this.y,this.r,0,Math.PI*2,false);
                ctx.fill();
                ctx.restore();  
            }
        }
    
        function createAsteroids(){
            var asteroid=null,
                x=300,y=0, r=2,rd=300,
                angle=0, d=283, 
                color='#fff';
            for(var i=0;i<400;i++){
                rd=Random(300,320);
                angle=Random(0,Math.PI*2*1000)/1000;
                x=Math.round(Math.cos(angle)*rd);
                y=Math.round(Math.sin(angle)*rd);
                r=Random(1,3);
                d=Random(28.3,511);
                color=getAsteroidColor();
                // console.log(angle,color);
                asteroid = new Asteroid(x,y,r,d,color,{color:color,blur:1});
                stage.planets.push(asteroid);
            }
        }
    
    

    彗星

      基本快完成了,但我们除此之外,可以再添加做椭圆运动的彗星,这样更加酷。一样随机生成20个彗星填充进场景类统一更新绘制。

        /**
         * 彗星
         */
        class Comet {
            constructor(cx,cy,a,b,r,angle,color,duration){
                this.a=a;
                this.b=b;
                this.r=r;
                this.cx=cx;
                this.cy=cy;
                this.x=0;
                this.y=0;
                this.color=color;
                this.angle=angle;
                this.duration=duration;
            }
            update(startTime){
                var t=Tween.linear(new Date()-startTime,0,Math.PI*2,this.duration*1000);
                this.x=this.cx+this.a*Math.cos(this.angle+t);
                this.y=this.cy+this.b*Math.sin(this.angle+t);
            }
            draw(){
                ctx.save();
                ctx.rotate(this.angle);
                //画运动轨迹
                ctx.lineWidth=0.5;
                ctx.strokeStyle='rgba(15,69,116,0.2)';
                Shape.ellipse(ctx,this.cx,this.cy,this.a,this.b);
    
                //画球
                ctx.beginPath();
                // ctx.globalCompositeOperation = "lighter";
                ctx.globalCompositeOperation = "source-atop";
                ctx.shadowColor=this.color;
                ctx.shadowBlur=1;
                ctx.fillStyle=this.color;
                ctx.arc(this.x,this.y,this.r,0,Math.PI*2,false);
                ctx.fill();
                //画尾迹
                ctx.restore();
            }
        }
    
        function createComets(){
            var l=180,
                a=800,b=300,
                cx=a-l,cy=0,
                r=3,duration=30,angle=0,color='#fff',
                comet = null;
            for(var i=0;i<20;i++){
                l=Random(120,350)
                a=Random(600,1000);
                b=a/Random(1,3);
                cx=a-l;
                r=Random(2,4);
                angle=Random(0,Math.PI*2*1000)/1000;
                color=getCometColor();
                duration=Random(20,100);
                stage.planets.push(new Comet(cx,cy,a,b,r,angle,color,duration));
            }
        }
    

    运动轨迹

      最后的细节,就是标识出行星圆周运动的轨道,当然最简单的是按运动半径画个圆。但我们用线性渐变添加好看的尾迹,这样效果更好

        function drawCircle(r,color){
            var hsl=Color.hexToHsl(color);
            ctx.lineWidth=1;
            // ctx.strokeStyle='rgba(176,184,203,0.3)';
            // ctx.arc(0,0,this.x,Math.PI*2,false);
            // ctx.stroke();
            var gradient=ctx.createLinearGradient(-r,0,r,0);
            gradient.addColorStop(0,'hsla('+hsl[0]+','+hsl[1]+'%,0%,.3)');
            gradient.addColorStop(0.6,'hsla('+hsl[0]+','+hsl[1]+'%,50%,.9)');
            gradient.addColorStop(1,'hsla('+hsl[0]+','+hsl[1]+'%,80%,1)');
            ctx.strokeStyle=gradient;
                // ctx.shadowColor=color;
                // ctx.shadowBlur=4;    
            ctx.beginPath();
            ctx.arc(0,0,r,0,Math.PI,true);
            ctx.stroke();
        }
    

    最后

      所有的部分都已经完成,我们只需要启动场景类即可

        createAsteroids();
        createComets();
        stage.init(ctx);
    

    相关文章

      网友评论

      • 4e5f67d086be:我看到小星星撞地球了 还以为会有一些爆炸的效果 把我吓得..
      • 阡陌夕殇:感觉好酷炫,请问这些星体圆周速度是按照真实比例来缩放的吗
        阡陌夕殇:@jeffzhong :+1:
        jeffzhong:是的,是按星球周期等比例设置的

      本文标题:canvas绘制太阳系

      本文链接:https://www.haomeiwen.com/subject/pvkwbxtx.html