MENU

JavaScriptCanvas画像処理DOM

【JavaScript】 Canvasでレイヤーを表現する

更新日:2020/05/31

 

Canvasでアニメーションをおこなう場合、全体をクリアしてから再描画する必要があります。

 

しかし描画内容が複雑だと処理が重くなり、滑らかな表現が難しくなります。

 

改善策として、動かない図形とアニメーションをさせる図形のレイヤーを分けることで、処理を軽くする方法をお伝えします。

 

 

参考記事:【JavaScript】 Canvasの使い方まとめ

Canvasを重ねることでレイヤーを表現する

 

Canvasにはレイヤーという機能がありません。

 

しかしCanvasを重ねることで、疑似的にレイヤーを表現できます。

 

Canvasを重ねるには、スタイルの設定が重要となります。

 

スタイルの定義

 

次のようなHTMLがあるとします。

 

HTML

<div class="canvas-wrap">
    <canvas class="canvas" width="600" height="300"></canvas>
    <canvas class="canvas" width="600" height="300"></canvas>
</div>

 

このとき、次のようにスタイルを設定することで、ぴったりとキャンバスを重ね合わせることができます。

 

CSS

.canvas-wrap{
            width: 600px;
            max-width: 100%;
            position: relative;
            padding: 0;
            box-sizing: content-box;
}
.canvas_warp:before{
            content:"";
            display: block;
            padding-top: 50%;
}
.canvas{
            position: absolute;
            left:0;
            top:0;
            border: 0;
            max-width:100%;
            box-sizing: content-box;
            padding: 0;
            margin: 0;
}

 

親要素divには、positionにrelativeを。
子要素のcanvasには、positionにabsolute、topとleftに0をセットします。

 

こうすることで、親要素divの座標( 0 , 0 )に子要素のcanvas全てがセットされます。
このとき親要素divの高さを指定しないと高さがゼロになり、次に続く要素がcanvasと重なってしまいます。

 

canvas 高さ 次要素

 

heightを指定すれば問題ないのですがレスポンシブに対応させる場合、縦横の比率を保つことができません。

 

そこで疑似要素beforeに、padding-topを設定します。
padding-topにパーセント指定すると、親要素の幅に対する比率がセットされます。

 

padding-top 疑似要素

 

これで、幅と高さの比率を維持できます。

 

キャンバスの幅と高さ

 

上のスタイル定義では、キャンバスのwidthとheightを設定していません。

 

タグ上サイズが描画サイズ。
スタイル上のサイズ指定が表示サイズというように、特別な意味があるためです。

 

詳しくは次の記事をご覧ください。
参考記事:【JavaScript】 Canvasのサイズが難解で困る

 

キャンバスの階層

 

キャンバスの階層(重なり順)は、html上で後にくるものが上になります。

 

ただしスタイルシート上で、z-indexを設定することで、順番を変更できます。

 

レイヤーデモ

 

 

JavaScript

(()=>{
/****************************
* 画像の読み込み
【JavaScript】 画像を動的に読み込んでhtml(DOM)やCanvasで使用するより
*****************************/
        const onloadImage = ( imageUrls , callBack = ()=>null , errorCallBack = ()=>null ) => {
            const stat = { wait:"wait", ok : "ok" , error : "error" };

 

            const im = imageUrls.map( e => ( { image : new Image() , stat : stat.wait } ) );

 

            im.forEach( ( e , i ) => {
                e.image.onload = () => {
                    e.image.onload = e.image.onerror = null;
                    if( e.stat === stat.wait) { e.stat = stat.ok; callBack( i , e.image );}
                };
                e.image.onerror = function(){ e.stat = stat.error;errorCallBack( i  );};
                e.image.src = imageUrls[i]
            } );

 

            return im;
        };
/****************************
* 背景キャンバスの描画
*****************************/
        const drawBackground = ( cvs , image ) =>{
            const [ w , h ] = [ image.naturalWidth , image.naturalHeight ];
            cvs.getContext("2d").drawImage( image , 0 , 0 , w , h , 0 , 0 , cvs.width , cvs.height);
        };
        const drawBackgroundError = ( cvs  ) =>{
            const ctx = cvs.getContext("2d");
            ctx.fillStyle = "orange";
            ctx.fillText("背景画像を読み込めませんでした",10,130);
            ctx.strokeRect( 0 , 0 , cvs.width , cvs.height);
        };
/****************************
* アニメキャンバスの描画
*****************************/
        const anime = ball =>{
            const ctx = ball.ctx;
            ctx.clearRect( 0 , 0 , ball.width , ball.height );
            ctx.beginPath();
            ctx.fillStyle = ball.color;
            ctx.arc( ball.x , ball.y , ball.r , 0 , 2 * Math.PI);
            ctx.fill();

 

            ball.x += ( ball.step * ball.xDir );
            if( ball.x < 0 || ball.x > ball.width ) {
                ball.xDir *= -1; ball.x += ( ball.step * ball.xDir*2 );
            }
            ball.y += ( ball.step * ball.yDir );
            if( ball.y < 0 || ball.y > ball.height ) {
                ball.yDir *= -1; ball.y += ( ball.step * ball.yDir*2 );
            }
        };
/****************************
* DOMが構築されるまで待つ
*****************************/
        window.addEventListener( "DOMContentLoaded" , ()=> {
            const cvs = document.getElementsByClassName("canvas");
            const backgroundCanvas = cvs[0];
            const animeCanvas = cvs[1];
            const ball = { x : 100 , y : 100 , xDir : 0.3 , yDir : 1 ,
                r : 10 , step: 10 , color : "orange",
                ctx : animeCanvas.getContext("2d") ,
                width : animeCanvas.width , height : animeCanvas.height};

 

            onloadImage( ["https://xxxx.com/xxxx.jpg"] ,
                (index,img)  =>  drawBackground( backgroundCanvas , img ) ,
                index => drawBackgroundError(backgroundCanvas) );

 

            setInterval(
                ()=>anime(ball)
            ,50);
        });
})();

動的にレイヤーキャンバスを追加

 

次は、キャンバスを動的に追加してみます。

 

最低限必要な処理

 

次のコードは、最低限必要な処理です。

 

const wrap = document.getElementById( "canvas-wrap" );
const cvs = document.createElement("canvas");
cvs.classList.add("canvas");
cvs.setAttribute("width",width);
cvs.setAttribute("height",width);
cvs.style.zIndex = 1;
wrap.appendChild( cvs );

 

  1. 親要素を取得する
  2. キャンバス要素を作成する
  3. キャンバス要素にクラスをセット(任意)
  4. キャンバス要素に描画サイズをセットする
  5. キャンバス要素にz-indexをセットする(任意)
  6. 親要素にキャンバス要素を追加する

 

キャンバスのサイズは作成時に幅300px、高さ150pxに設定されます。
そのため、コード上で正しい値をセットする必要があります。

 

 

動的追加デモ

 

追加ボタンを押すたびに、キャンバスが追加されます。

 

 

HTML

<button id="btn">追加</button>
<div id="canvas-wrap" class="canvas-wrap" ></div>

 

JavaScript

(()=>{
    const layerCanvas = function ( id , width , height ) {
        const wrap = document.getElementById( id );
        let count = 0;

 

        return {
            addCanvas:() => {
                const cvs = document.createElement("canvas");
                cvs.classList.add("canvas");
                cvs.setAttribute("width",width);
                cvs.setAttribute("height",height);
                cvs.style.zIndex = count;
                wrap.appendChild( cvs );
                count ++;
                return cvs;
            }
        };
    };
    window.addEventListener( "DOMContentLoaded" , ()=> {
        const lcv = layerCanvas("canvas-wrap" , 600 , 300);
        let count=0;

 

        document.getElementById("btn").addEventListener( "click" ,
            () => {
                const newCanvas = lcv.addCanvas();
                count ++;
                const ctx = newCanvas.getContext("2d");
                ctx.fillText( "layer" + count , count * 20 , count * 20);
            });
    });
})();

けーちゃんおススメJavaScript入門書

  • スラスラ読める JavaScript ふりがなプログラミング
  • プログラム未経験者がJavaScript始めるならコレ!
    コードを掲載して自分で理解しろという投げっぱなしな入門書とは異なり、コードに一つ一つどんなことをやっているかをふりがなという形式で解説しています。
    それでいてJavaScriptの基礎と応用を学べる良書です。
  • これからWebをはじめる人のHTML&CSS、JavaScriptのきほんのきほん
  • JavaScriptの機能を実践で活かすにはHTMLやCSSの知識が不可欠です。
    しかしそれらの知識があることが前提として書かれている書籍が多い中、この本は総合的な知識を身に着けることができます。
    HTMLやCSSの知識も不安な方には、ぴったりの一冊です
  •  

    入門書の役割は、自分のやりたいことをネットで調べることができるようになるための、基礎的な知識の獲得です。
    まずはこれらの本でしっかりと基礎知識を身につけましょう。
    そしてもっと高度なことや専門的なことはネットで調べ、情報が足りないと感じたら書籍を購入してください。


    記事の内容について

     

    こんにちはけーちゃんです。
    説明するのって難しいですね。


    「なんか言ってることおかしくない?」
    たぶん、こんなご意見あると思います。

    裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。

    そんなときは、ご意見もらえたら嬉しいです。

    ご意見はこちら。
    https://note.affi-sapo-sv.com/info.php

    【お願い】

    お願い

    ■このページのURL


    ■このページのタイトル


    ■リンクタグ


    ※リンクして頂いた方でご希望者には貴サイトの紹介記事を作成してリンクを設置します。
    サイト上部の問い合わせよりご連絡ください。
    ただしサイトのジャンルによっては、お断りさせていただくことがあります。