HTML/CSS/JSでまだら模様の波を実装したい


### 質問の背景

HTML/CSS/JSを使用した静的コーディングでWebサイトを制作しています。  
その過程で、canvasで単色の波のアニメーションを使用しているのですが、  
まだら模様で波を実装しなければならない箇所があり、お知恵を借りられれば幸いです。

### 実現したいこと

[波打つ（1 つ 重なりなし） | 動くWebデザインアイディア帳](https://coco-
factory.jp/ugokuweb/move02/5-10/)

添付画像のようなまだら模様を、上記と同じ動きの波で実装したいです。

[![添付画像のようなまだら模様を、1と同じ動きの波で実装したいです。](https://i.stack.imgur.com/2glQ5.png)](https://i.stack.imgur.com/2glQ5.png)

皆さまが上記を実装するとなった場合、どういった方法で実装するか  
教えていただけますと幸いです。

よろしくお願いいたします。




`context.fill();`を使って単色で塗りつぶしている処理を`context.drawImage(overImage, 0,
0);`で画像を描画する処理に書き換えることで実現できます。

末尾のサンプルコードはリンク先のJavaScriptを書き直した全文です。  
変更点をピックアップします。

  * 最初に画像をロードします。

[code]

    let overImage = new Image();
    overImage.src = "colorful-abstract-art-background.jpg";
    let backImage = new Image();
    backImage.src = "copy-space-wall-texture-for-backgrounds.jpg";
    
[/code]

  * 背景画像を使用する場合は`draw`関数の`clearRect`を`drawImage`に変更します。

[code]

        // キャンバスの描画をクリア
        //context.clearRect(0, 0, canvas.width, canvas.height);
        context.drawImage(backImage, 0, 0);
    
[/code]

  * `drawWave`関数で`beginPath`する前に`save`しておき、`closePath`した後に画像を描画します。

[code]

        context.save();
        context.beginPath(); //パスの開始
        // (中略)
        context.closePath() //パスを閉じる
        //context.fill(); //波を塗りつぶす
        context.clip();
        context.drawImage(overImage, 0, 0);
        context.restore();
    
[/code]

**実行結果の例**

[![まだら模様](https://i.stack.imgur.com/eUJzz.jpg)](https://i.stack.imgur.com/eUJzz.jpg)

下記クレジットの画像を使用しました。

[著作者：rawpixel.com](https://jp.freepik.com/free-photo/colorful-abstract-art-
background_4102535.htm#query=%E8%83%8C%E6%99%AF%E7%B4%A0%E6%9D%90&position=0&from_view=keyword&track=sph)／出典：Freepik  
著作者：[Freepik](https://jp.freepik.com/free-photo/copy-space-wall-texture-for-
backgrounds_10980237.htm#query=%E8%83%8C%E6%99%AF%E7%B4%A0%E6%9D%90&position=7&from_view=keyword&track=sph)

**サンプルコード**

あらかじめ画像をダウンロードして適切なフォルダに配置しておいてください。

[code]

    var unit = 100,
        canvasList, // キャンバスの配列
        info = {}, // 全キャンバス共通の描画情報
        colorList; // 各キャンバスの色情報
    
    let overImage = new Image();
    overImage.src = "colorful-abstract-art-background.jpg";
    let backImage = new Image();
    backImage.src = "copy-space-wall-texture-for-backgrounds.jpg";
    
    /**
     * Init function.
     * 
     * Initialize variables and begin the animation.
     */
    function init() {
        info.seconds = 0;
        info.t = 0;
        canvasList = [];
        colorList = [];
        // canvas1個めの色指定
        canvasList.push(document.getElementById("waveCanvas"));
        colorList.push(['#43c0e4']);
      // 各キャンバスの初期化
        for(var canvasIndex in canvasList) {
            var canvas = canvasList[canvasIndex];
            canvas.width = document.documentElement.clientWidth; //Canvasのwidthをウィンドウの幅に合わせる
            canvas.height = 200;//波の高さ
            canvas.contextCache = canvas.getContext("2d");
        }
        // 共通の更新処理呼び出し
        update();
    }
    
    function update() {
        for(var canvasIndex in canvasList) {
            var canvas = canvasList[canvasIndex];
            // 各キャンバスの描画
            draw(canvas, colorList[canvasIndex]);
        }
        // 共通の描画情報の更新
        info.seconds = info.seconds + .014;
        info.t = info.seconds*Math.PI;
        // 自身の再起呼び出し
        setTimeout(update, 35);
    }
    
    /**
     * Draw animation function.
     * 
     * This function draws one frame of the animation, waits 20ms, and then calls
     * itself again.
     */
    function draw(canvas, color) {
        // 対象のcanvasのコンテキストを取得
        var context = canvas.contextCache;
        // キャンバスの描画をクリア
        //context.clearRect(0, 0, canvas.width, canvas.height);
        context.drawImage(backImage, 0, 0);
    
        //波を描画 drawWave(canvas, color[数字（波の数を0から数えて指定）], 透過, 波の幅のzoom,波の開始位置の遅れ )
        drawWave(canvas, color[0], 1, 3, 0);//drawWave(canvas, color[0],0.5, 3, 0);とすると透過50%の波が出来る
    }
    
    /**
    * 波を描画
    * drawWave(色, 不透明度, 波の幅のzoom, 波の開始位置の遅れ)
    */
    function drawWave(canvas, color, alpha, zoom, delay) {
        var context = canvas.contextCache;
        context.fillStyle = color;//塗りの色
        context.globalAlpha = alpha;
        context.save();
        context.beginPath(); //パスの開始
        drawSine(canvas, info.t / 0.5, zoom, delay);
        context.lineTo(canvas.width + 10, canvas.height); //パスをCanvasの右下へ
        context.lineTo(0, canvas.height); //パスをCanvasの左下へ
        context.closePath() //パスを閉じる
        //context.fill(); //波を塗りつぶす
        context.clip();
        context.drawImage(overImage, 0, 0);
        context.restore();
    }
    
    /**
     * Function to draw sine
     * 
     * The sine curve is drawn in 10px segments starting at the origin. 
     * drawSine(時間, 波の幅のzoom, 波の開始位置の遅れ)
     */
    function drawSine(canvas, t, zoom, delay) {
        var xAxis = Math.floor(canvas.height/2);
        var yAxis = 0;
        var context = canvas.contextCache;
        // Set the initial x and y, starting at 0,0 and translating to the origin on
        // the canvas.
        var x = t; //時間を横の位置とする
        var y = Math.sin(x)/zoom;
        context.moveTo(yAxis, unit*y+xAxis); //スタート位置にパスを置く
    
        // Loop to draw segments (横幅の分、波を描画)
        for (i = yAxis; i <= canvas.width + 10; i += 10) {
            x = t+(-yAxis+i)/unit/zoom;
            y = Math.sin(x - delay)/3;
            context.lineTo(i, unit*y+xAxis);
        }
    }
    
    init();
    
[/code]

