blog

canvasの学習メモ:小さなドロップ式、とても楽しい!

最近、Webページが欲しくて、HTML5を勉強する過程で、DEMOの一部をまとめてやってみようと思っているのですが、DEMOを全部1つ1つアレンジしてWebページを作るだけでは、見づらすぎると思います...

Jul 12, 2025 · 24 min. read
シェア

最近、HTML5を勉強する過程で作ったDEMOをWebページにまとめてみたいと思っているのですが、ただDEMOを1つ1つ並べただけのWebページを作っても見づらいと思います。せっかくcanvasを覚えたのだから、ブラウザを放り投げて、ちょっとしたオープニングアニメーションを作ってみよう、と。

私はパーティクルがより楽しいと思うので、オープニングアニメーションの効果は、しばらくの間考え、パーティクルを使用することにしました。私は***技術的なブログの記事を書いた前に覚えている、テキストイメージのパーティクルについてです: 、その後、ちょうどリニアモーションですが、ところで、少し3D効果を追加します。モーションの式は非常に簡単です。だから私は、このオープニングアニメーションの一部をよりダイナミックにしたいと思います。

DEMO first:

その効果は、直線的な動きよりもダイナミックだと思いませんか?そして、このブログのタイトルである「Little Drops of Formula, Big Drops of Fun」を忘れないでください。このエフェクトを作るには、中学生か高校生の物理学の知識だけでいいのです。あるいは高校物理の知識、加速運動、減速運動の公式、ラ。だから、それは本当に式の小さなドロップです。建物の所有者は非常にいくつかのクールなものを投げるのが好きですが、通常は使用する上で動作しない場合がありますが、楽しさは本当に非常に魅力的なああです。また、これを行うと、プログラミングの思考能力を強化することができます。

さて、本題に入りましょう。原理を簡単に説明しましょう。

パーティクル・モーションのコア・コードが少しあるだけです:

update:function(time){  
            this.x += this.vx*time;  
            this.y += this.vy*time;  
 
            if(!this.globleDown&&this.y>0){  
                var yc = this.toy - this.y;  
                var xc = this.tox - this.x;  
 
                this.jl = Math.sqrt(xc*xc+yc*yc);  
 
                var za = 20;  
 
                var ax = za*(xc/this.jl),  
                    ay = za*(yc/this.jl),  
                    vx = (this.vx+ax*time)*0.97,  
                    vy = (this.vy+ay*time)*0.97;  
 
                this.vx = vx;  
                this.vy = vy;  
 
            }else {  
                var gravity = 9.8;  
                var vy = this.vy+gravity*time;  
 
                if(this.y>canvas.height){  
                    vy = -vy*0.7;  
                }  
 
                this.vy = vy;  
            }  
        }, 

粒子には、自由落下と吸引の2つの状態があります。自由落下については触れません。吸引に関しては、まず粒子の特性を掲載します:

var Dot = function(x,y,vx,vy,tox,toy,color){  
        this.x=x;  
        this.y=y;  
        this.vx=vx;  
        this.vy=vy;  
        this.nextox = tox;  
        this.nextoy = toy;  
        this.color = color;  
        this.visible = true;  
        this.globleDown = false;  
        this.setEnd(tox , toy);  
    }  
 
setEnd:function(tox , toy){  
            this.tox = tox;  
            this.toy = toy;  
            var yc = this.toy - this.y;  
            var xc = this.tox - this.x;  
        }, 

x,yはパーティクルの位置、vxはパーティクルの水平方向の速度、vyはパーティクルの垂直方向の速度で、nexttoxを知っているかどうかは関係ありません。

まず、すべての粒子に目的地を与え、この目的地は以下のようになります。zaは粒子と目的地を結ぶ線の加速度なので、粒子の位置と目的地を通して、簡単な三角関数を通して、粒子の水平加速度と垂直加速度を出すことができます。つまり、粒子と目的地の位置を使えば、単純な三角関数によって粒子の水平加速度と垂直加速度が計算できるのです。

var ax = za*(xc/this.jl),  
 ay = za*(yc/this.jl), 

水平加速度と垂直加速度では、次のステップはさらに単純で、水平速度と垂直速度の増分を計算するのは簡単です。

vx = (this.vx+ax*time)*0.97,  
vy = (this.vy+ay*time)*0.97; 

0.97を掛ける理由は、粒子が減速する前のエネルギー損失をシミュレートするためです。

速度を計算したら、パーティクルの位置を更新するだけです。

this.x += this.vx*time;  
this.y += this.vy*time; 

パーティクルと目的地を結ぶリンクの方向は飛行中に常に変化するため、パーティクルの水平加速度と垂直加速度はフレームごとに再計算する必要があります。

移動の原理は単純なものなのでしょうか?

アニメーションの初期化は、オフスクリーンキャンバスで、目的の単語やイメージを描画し、getImageDataメソッドを介してオフスクリーンキャンバスのピクセルを取得する:動きの原理は、その後、上記のアニメーションの具体的な実装をプル終了します。imageDataのデータ値はrgbaの配列なので、*** 1の値、つまり透明度が128より大きければ描画された領域と判断します。次に、あまりにも多くのパーティクルオブジェクトがページラグにつながる防止するために、領域のxy値を取得するので、パーティクルの数を減らすように、2ずつ増分ピクセルx値とy値を取り、パーティクルの数を制限します。

this.osCanvas = document.createElement("canvas");  
        var osCtx = this.osCanvas.getContext("2d");  
 
        this.osCanvas.width = 1000;  
        this.osCanvas.height = 150;  
 
        osCtx.textAlign = "center";  
        osCtx.textBaseline = "middle";  
        osCtx.font="70px 微软雅黑,黑体 bold";  
        osCtx.fillStyle = "#1D181F" 
        osCtx.fillText("WelCome" , this.osCanvas.width/2 , this.osCanvas.height/2-40);  
        osCtx.fillText("To wAxes' HOME" , this.osCanvas.width/2 , this.osCanvas.height/2+40);  
        var bigImageData = osCtx.getImageData(0,0,this.osCanvas.width,this.osCanvas.height);  
 
        dots = [];  
 
        for(var x=0;x<bigImageData.width;x+=2){  
            for(var y=0;y<bigImageData.height;y+=2){  
                var i = (y*bigImageData.width + x)*4;  
                if(bigImageData.data[i+3]>128){  
                    var dot = new Dot(  
                        Math.random()>0.5?Math.random()*20+10:Math.random()*20+canvas.width-40,  
                        -Math.random()*canvas.height*2,  
                        0,  
                        0,  
                        x+(canvas.width/2-this.osCanvas.width/2),  
                        y+(canvas.height/2-this.osCanvas.height/2),  
                        "rgba("+bigImageData.data[i]+","+bigImageData.data[i+1]+","+bigImageData.data[i+2]+",1)" 
                    );  
                    dot.setEnd(canvas.width/2,canvas.height/2)  
                    dots.push(dot);  
                }  
            }  
        } 

ループを通してパーティクルの位置xy値を取得した後、その位置がパーティクルに割り当てられ、パーティクルの移動先になります。その後、アニメーションが始まり、テキストイメージをパーティクル化する効果を作ることができます。

#p#

アニメーションを実装するためのjsコードを以下に掲載します。他のコードにも興味がある場合は、コンソールの haha を直接見ることができます。

var part_1 = (function(w){  
    var dots = [],DOT_SIZE = 2,cube=null;  
 
    var Dot = function(x,y,vx,vy,tox,toy,color){  
        this.x=x;  
        this.y=y;  
        this.vx=vx;  
        this.vy=vy;  
        this.nextox = tox;  
        this.nextoy = toy;  
        this.color = color;  
        this.visible = true;  
        this.globleDown = false;  
        this.setEnd(tox , toy);  
    }  
 
    Dot.prototype = {  
        paint:function(){  
            ctx.fillStyle=this.color;  
            ctx.fillRect(this.x-DOT_SIZE/2 , this.y-DOT_SIZE/2 , DOT_SIZE , DOT_SIZE);  
        },  
 
        setEnd:function(tox , toy){  
            this.tox = tox;  
            this.toy = toy;  
            var yc = this.toy - this.y;  
            var xc = this.tox - this.x;  
            // this.initjl = Math.sqrt(xc*xc+yc*yc);  
        },  
 
        update:function(time){  
            this.x += this.vx*time;  
            this.y += this.vy*time;  
 
            if(!this.globleDown&&this.y>0){  
                var yc = this.toy - this.y;  
                var xc = this.tox - this.x;  
 
                this.jl = Math.sqrt(xc*xc+yc*yc);  
 
                var za = 20;  
 
                var ax = za*(xc/this.jl),  
                    ay = za*(yc/this.jl),  
                    vx = (this.vx+ax*time)*0.97,  
                    vy = (this.vy+ay*time)*0.97;  
 
                this.vx = vx;  
                this.vy = vy;  
 
                // if(Math.abs(this.vx)<1&&Math.abs(this.vy)<1){  
                //     this.y = this.toy  
                //     this.x = this.tox  
                // }  
            }else {  
                var gravity = 9.8;  
                var vy = this.vy+gravity*time;  
 
                if(this.y>canvas.height){  
                    vy = -vy*0.7;  
                }  
 
                this.vy = vy;  
            }  
        },  
 
        loop:function(time){  
            this.update(time);  
            this.paint();  
        }  
    }  
 
      
 
    var animate = function(){  
        this.state = "before" 
    }  
 
    var ap = animate.prototype;  
 
    ap.init = function(){  
        this.osCanvas = document.createElement("canvas");  
        var osCtx = this.osCanvas.getContext("2d");  
 
        this.osCanvas.width = 1000;  
        this.osCanvas.height = 150;  
 
        osCtx.textAlign = "center";  
        osCtx.textBaseline = "middle";  
        osCtx.font="70px 微软雅黑,黑体 bold";  
        osCtx.fillStyle = "#1D181F" 
        osCtx.fillText("WelCome" , this.osCanvas.width/2 , this.osCanvas.height/2-40);  
        osCtx.fillText("To wAxes' HOME" , this.osCanvas.width/2 , this.osCanvas.height/2+40);  
        var bigImageData = osCtx.getImageData(0,0,this.osCanvas.width,this.osCanvas.height);  
 
        dots = [];  
 
        for(var x=0;x<bigImageData.width;x+=2){  
            for(var y=0;y<bigImageData.height;y+=2){  
                var i = (y*bigImageData.width + x)*4;  
                if(bigImageData.data[i+3]>128){  
                    var dot = new Dot(  
                        Math.random()>0.5?Math.random()*20+10:Math.random()*20+canvas.width-40,  
                        -Math.random()*canvas.height*2,  
                        0,  
                        0,  
                        x+(canvas.width/2-this.osCanvas.width/2),  
                        y+(canvas.height/2-this.osCanvas.height/2),  
                        "rgba("+bigImageData.data[i]+","+bigImageData.data[i+1]+","+bigImageData.data[i+2]+",1)" 
                    );  
                    dot.setEnd(canvas.width/2,canvas.height/2)  
                    dots.push(dot);  
                }  
            }  
        }  
        console.log(dots.length)  
    }  
 
    ap.changeState = function(){  
        var osCtx = this.osCanvas.getContext("2d");  
        osCtx.clearRect(0,0,this.osCanvas.width,this.osCanvas.height);  
        this.osCanvas.width = 460;  
        this.osCanvas.height = 100;  
 
        osCtx.fillStyle="#5C5656" 
        osCtx.fillRect(20,20,60,60)  
 
        drawLogo(this.osCanvas , osCtx);  
 
        var bigImageData = osCtx.getImageData(0,0,this.osCanvas.width,this.osCanvas.height);  
 
        var index=0;  
        dots.sort(function(a , b){  
            return Math.random()-Math.random();  
        })  
        for(var x=0;x<bigImageData.width;x+=2){  
            for(var y=0;y<bigImageData.height;y+=2){  
                var i = (y*bigImageData.width + x)*4;  
                if(bigImageData.data[i+3]>128){  
                        var d = dots[index];  
                        if(d){  
                            d.setEnd(x+(canvas.width/2-300) , y+50)  
                            d.color = "rgba("+bigImageData.data[i]+","+bigImageData.data[i+1]+","+bigImageData.data[i+2]+",1)";  
                            index++  
                        }  
                }  
            }  
        }  
 
        setTimeout(function(){  
            var endindex = index;  
            for(var i=0;i<dots.length-endindex;i++){  
                if(dots[index]){  
                    var d = dots[index];  
                      
                    d.globleDown = true;  
                    d.vx = Math.random()*100-50;  
                }  
                index++;  
            }  
        } , 2000)  
    }  
 
 
    function endState(){  
        canvas.width = 600;  
        canvas.height = 100;  
        canvas.style.display="block";  
        canvas.style.top = "50px";  
        canvas.style.left = (window.innerWidth-canvas.width)/2+"px";  
        cube = new Cube(50);  
        cube._initVector(50,50);  
    }  
 
    function drawLogo(canvas , ctx){  
        ctx.textAlign = "center";  
        ctx.textBaseline = "middle";  
        ctx.font="65px 微软雅黑,黑体 bold" 
        ctx.fillStyle="#E06D2F" 
        ctx.fillText("DEMO" , 300 , canvas.height/2)  
 
        ctx.font="40px 微软雅黑,黑体 bold" 
        ctx.fillStyle="#405159" 
        ctx.fillText("acmeの" , 160 , canvas.height/2)  
        ctx.fillText("ネスト" , 420 , canvas.height/2)  
    }  
 
    var num = 0;  
    ap.update = function(time){  
        time = time/100;  
        if(this.state==="first"||this.state==="before"){  
            var completeNum = 0;  
            dots.forEach(function(dot){  
                if(dot.visible) dot.loop(time);  
                if(dot.jl<5){  
                    completeNum++  
                }  
            });  
            if(completeNum>=5*dots.length/6){  
                  
                if(this.state==="before"){  
                    this.state = "first";  
                    dots.forEach(function(dot){  
                        dot.setEnd(dot.nextox , dot.nextoy);  
                    });  
                }else {  
                    this.state = "second";  
                    this.changeState();  
                }  
            }  
        }else if(this.state==="second"){  
            var completeNum = 0,  
                allnum = 0;  
            dots.forEach(function(dot){  
                if(dot.visible) dot.loop(time);  
                if(dot.globleDown){  
                    allnum++;  
                    if(Math.abs(dot.y-canvas.height)<2){  
                        completeNum++  
                    }  
                }  
            });  
 
            if(completeNum===allnum&&allnum!==0){  
                this.state = "third";  
                part_2.animate();  
                endState();  
            }  
        }else if(this.state==="third"){  
            cube.update();  
            drawLogo(canvas , ctx);  
        }  
    }  
 
    return new animate();  
})(window) 
Read next

たくさんの靴下があるが、最も高速で効率的なアルゴリズムで靴下を合わせるにはどうすればいいだろうか?

問題の説明\n\nコンピュータ科学者として、私は何をすべきか悩んでいました。すぐに思いついたのは、複雑さがOになるように大きさの色でソートすることでした。\nハッシュや他の「その場でない」方法は、靴下を複製することができないので、ここでは好ましくありません。\nですから

Jul 12, 2025 · 3 min read