1. ホーム
  2. Web制作
  3. html5

キャンバス画像分割効果の実装

2022-01-31 17:22:10

cssdesignawardsを見ていたら、画像の中身を分割するエフェクト(URL https://weareludwig.com をクリックすると、その中を見ることができ、とてもクールな感じがするので、自分でも実装してみましたが、なかなかうまくいきました。エフェクトを見る https://codepen.io/geeknoble/pen/OQaOVG

分析

まず、画像の内容が小さな矩形に分割され、それぞれの矩形がランダムにパンされていることがわかります。CanvasのdrawImage関数は画像の内容を切り抜いてCanvasのキャンバスに描くことができるので、この効果の大原則はdrawImageを使うことです。画像の内容を中断して復元する効果と、次の画像と切り替える効果の2つがあり、どちらもdrawImageが使えるが移動距離が違うということです。大まかな考え方はあるので、先に実装してしまうのも手です。

初期作業

まず、画像の幅や高さ、矩形の数、クリッピングの大きさなど、いくつかの変数を初期化します。次に、各矩形の座標を計算し、双方向ループを使用して矩形の座標をデータに保存します。各矩形はランダムな変位を持ちますが,これも保存してランダムで保存する必要があります.ここで、x,y はキャンバスの座標、x1,y1 は画像の切り出しの座標です。

init: function (context, width, height, area, img) {
            this.context = context;
            this.img = img;
            this.imgWidth = img[0].width; //image width and height
            this.imgHeight = img[0].height;
            this.index = 0; //the current image serial number
            this.width = width; //Canvas width and height
            this.height = height;
            this.area = height/12; //Small rectangle length
            this.countX = width / this.area; //number of small rectangles horizontally and vertically
            this.countY = height / this.area;
            this.wx = this.imgWidth / this.countX; // the width and height of the image in the small rectangle
            this.wy = this.imgHeight / this.countY;
            this.state = true; //State of the image, true means not split
            this.dataFlag = true; //state of the small rectangle, true means no random value added
            this.duration = 1000; //animation time
            this.duration2 = 1500;
            this.startTime = 0;
            this.data = []; //the coordinates of the small rectangle
            this.randoms = []; //position random values
            //initialize the rectangle coordinates
            var x1 = 0, y1 = 0, x = 0, y = 0;
            for (var i = 0; i < this.countY; i++) {
                for (var j = 0; j < this.countX; j++) {
                    context.drawImage(this.img[this.index], x1, y1, this.wx, this.wy, x, y, this.area, this.area);
                    //store the rectangle coordinates
                    this.data.push({
                        x1: x1,
                        y1: y1,
                        x: x,
                        y: y
                    });
                    //add random values
                    this.randoms.push(random(-this.area, this.area));
                    x1 += this.wx;
                    x += this.area;

                }
                x1 = 0;
                y1 += this.wy;
                x = 0;
                y += this.area;
            }
            this.checkMargin();
        }

エッジの検出

矩形に変位を加える前に、変位後の座標が画像の境界を超えるかどうかを判断する必要があります。たとえば、上部の矩形は y 軸方向にずらした場合のみ上方に移動でき、その条件は、現在の座標に変位値を加えたものが画像の幅と高さを 0 よりも小さいか大きいかどうかになります。更新された座標が0より小さい場合は、乱数値は負でなければならないので正の数に変更する必要があり、画像の高さより大きい場合は負の数に変更すればよい。各矩形は一方向に移動するので、ここではX軸を偶数位置、Y軸を奇数位置に移動するように書いています。

//Detect edges
        checkMargin: function () {
            var self = this;
            this.data.forEach(function (item, index) {
                if (index % 2 == 0) { // move the x-axis if the subscript is a multiple of 2, otherwise move the y-axis
                    if ( item.x1 + self.randoms[index] < 0)
                        // change to a positive number
                        self.randoms[index] = -self.randoms[index];
                    if (item.x1 + self.wx + self.randoms[index] > self.imgWidth )
                        // change to a negative number
                        self.randoms[index] = -Math.abs(self.randoms[index])
                } else {
                    if (item.y1 + self.randoms[index] < 0)
                        self.randoms[index] = -self.randoms[index];
                    if (item.y1 + self.randoms[index] + self.wy > self.imgHeight)
                        self.randoms[index] = -Math.abs(self.randoms[index])
                }
            })
        }

分離・復元

アニメーションの内容の分離と復元は、矩形座標の値を更新し、データ中の座標にランダムな値を加えて内容を乱し、ランダムな値を減らして復元することである。

//Detect edges
        checkMargin: function () {
            var self = this;
            this.data.forEach(function (item, index) {
                if (index % 2 == 0) { // move the x-axis if the subscript is a multiple of 2, otherwise move the y-axis
                    if ( item.x1 + self.randoms[index] < 0)
                        // change to a positive number
                        self.randoms[index] = -self.randoms[index];
                    if (item.x1 + self.wx + self.randoms[index] > self.imgWidth )
                        // change to a negative number
                        self.randoms[index] = -Math.abs(self.randoms[index])
                } else {
                    if (item.y1 + self.randoms[index] < 0)
                        self.randoms[index] = -self.randoms[index];
                    if (item.y1 + self.randoms[index] + self.wy > self.imgHeight)
                        self.randoms[index] = -Math.abs(self.randoms[index])
                }
            })
        }

座標を格納した後、あなたはパンアニメーションを実装するために行くことができます、移動のプロセスは、滑らかな遷移を持って、我々はTween.jsジョギングアルゴリズムを使用することができます、アルゴリズムは4つのパラメータが現在の時刻、初期位置、終了位置、アニメーション時間です。詳細については、Xinxu Zhang氏のこの記事を参照してください。 https://www.zhangxinxu.com/wordpress/2016/12/how-use-tween-js-animation-easing/ . Tween.jsを使えば、フレームごとに移動距離を把握し、requestAnimationFrameで座標を更新することができます。

blockAnimation: function () {
            var flag = 1;
            if (this.state) { // Determine if the image is broken or restored
                this.update(true)
            } else {
                flag = -1;
                this.update(false);
            }
            var self = this;
            this.startTime = +new Date(); // get the current time

            this.state = !this.state;
            (function animation() {
                var t = +new Date();
                if (t >= self.startTime + self.duration) { // end of animation condition
                    return false;
                }
                self.data.forEach(function (item, index) {
                    if (index % 2 == 0) {
                        var pos = Math.tween.Expo.easeInOut(t - self.startTime, 0, self.randoms[index] * flag, self.duration); // Calculate the distance moved per frame
                        self.context.drawImage(self.img[self.index], item.x1 + pos, item.y1, self.wx, self.wy, item.x, item.y, self.area, self.area);
                    } else {
                        var pos = Math.tween.Expo.easeInOut(t - self.startTime, 0, self.randoms[index] * flag, self.duration);  
                        self.context.drawImage(self.img[self.index], item.x1, item.y1 + pos, self.wx, self.wy, item.x, item.y, self.area, self.area);
                    }
                });
                requestAnimationFrame(animation);
            })();
        }

この時点で、分離・回収のアニメーションが実装されています

画像トグル

次に画像切り替えの部分ですが、回転する画像のアニメーションは、各画像の位置を可視ウィンドウの幅方向に距離を移動させますが、ここも同じです。回転画像と違って、ここではcanvasタグしかないので、切り替え時に現在の画像と次の画像の座標を変更するだけで、現在の画像はy1 + posの距離、次の画像はy1 + pos - imgHeightの距離に移動します(なぜimgHeightを引く必要があるのかは説明不要です)。

// Vertical slide animation
        verticalAnimation: function (val) {
            if (!this.time2) {
                return false;
            }
            this.checkTime(2);

            var self = this;
            val ? val = 1 : val = -1; // determine whether to slide up or down
            if ((this.index + val) < 0 || (this.index + val) >= (this.img.length)) { //judge if the image number is at the bottom
                return false;
            }

            this.state ? this.update(true) : this.update(false);
            this.startTime = +new Date();

            (function animation() {
                var t = +new Date();
                if (t >= self.startTime + self.duration2) {
                    val === 1 ? self.index++ : self.index--; //adjust the order of the images
                    self.index < 0 ? self.index = self.img.length - 1 : self.index;
                    self.index >= self.img.length ? self.index = 0 : self.index;
                    return false;
                }
                self.data.forEach(function (item) {
                    var pos = Math.tween.Cubic.easeInOut(t - self.startTime, 0, (self.imgHeight) * val, self.duration2);
                    // Update the current image coordinates
                    self.context.drawImage(self.img[self.index], item.x1, item.y1 + pos, self.wx, self.wy, item.x, item.y, self.area, self.area);
                    // Update the next image coordinates
                    self.context.drawImage(self.img[self.index + val], item.x1, item.y1 + pos - self.imgHeight * val, self.wx, self.wy, item.x, item.y, self.area, self.area);

                });
                requestAnimationFrame(animation);
            })()
        }

X軸のトグルも同様です。これですべての機能がほぼ完成し、全コードはコードペンで見ることができます。

以上が今回の記事の内容ですが、皆様の学習の一助となり、スクリプトハウスをより一層応援していただければ幸いです。