場景轉場動畫的Canvas實現

廢話不說,先上圖


圖1:轉場前


圖2:轉場中(動畫效果此處請看末尾鏈接)


圖3:轉場後



看完效果,我們來講講原理。

首先我們需要準備一張轉場用的圖。理論上所有圖片都可以作爲過渡圖使用。

我使用的是一張帶有天使翅膀圖案的圖片作爲過渡圖。


首先,將過渡圖黑白化。

var pixs = context.getImageData(0, 0, context.canvas.width, context.canvas.height);
var data = pixs.data;
var len = data.length;
while(len -= 4){
    var r = (data[len] + data[len + 1] + data[len + 2]) / 3;
    data[len] = data[len + 1] = data[len + 2] = r;
}
context.putImageData(pixs, 0, 0);

(以上代碼純手寫未測試,請注意)


在進行過渡之前,我們必須往屏幕上繪製一些內容,隨便畫什麼都可以。

然後我們開始準備過渡了。


在過渡之前,我們需要定義一些變量。

// 用於保存舊的場景最後一幀
var oldSceneCanvas = document.createElement("canvas");

// 用於指示當前最多可用的不透明度值
var currentOffset = 255;

// 將過渡圖繪製在後臺canvas上,由於之前我們將它黑白化過,因此這裏假定已經在canvas上
var transitionCanvas = ...;

完成上面的操作後,我們將當前可見畫布中的內容繪製在 oldSceneCanvas 上。


然後假裝正常的把新內容繪製在可見畫布上。


之後我們將 oldSceneCanvas 的每個像素和 currentOffset進行比較,如果alpha通道大於currentOffset,則讓其等於 currentOffset的值。


將oldSceneCanvas 最後繪製在可見畫布上。


開啓一個定時器,定時將currentOffset的值減小。動畫效果就這麼出現了……


最後放上我的實現代碼,其中使用了幾個其他的東東,你可以根據運行報錯進行修改……


(function (window, document, Jyo, undefined) {

    Jyo.Transition = function (renderer) {
        /// <summary>轉場動畫</summary>
        /// <param name="renderer" type="Jyo.Renderer">渲染器對象</param>

        Jyo.Object.call(this);

        this.renderer = renderer;

        /*
            設置過渡用畫布
        */
        var osCvs = document.createElement("canvas");
        var tsCvs = document.createElement("canvas");

        // 當前偏移量
        this.currentOffset = 255;

        // 是否正在執行
        this.isRun = false;

        // 是否允許更新
        this.canUpdate = false;

        /*
            設置舊場景圖畫布
        */
        this.oldScreenCanvas = new Jyo.Xnb();
        this.oldScreenCanvas.object = osCvs;
        this.oldScreenContext = osCvs.getContext("2d");
        this.oldPixelData = null;

        /*
            設置過渡圖畫布
        */
        this.transitionCanvas = tsCvs;
        this.transitionContext = tsCvs.getContext("2d");

        // 過渡Alpha數組
        this.transitionAlphaArray = null;
    };

    Jyo.Transition.prototype = new Jyo.Object({
        begin: function (img, time, delay) {
            /// <summary>開始轉場動畫</summary>
            /// <param name="img" type="Jyo.Xnb">圖像Xnb對象</param>
            /// <param name="time" type="Number">總時長(毫秒)</param>
            /// <param name="delay" type="Number" optional="true">延遲時長(毫秒)</param>

            var _this = this;
            var renderer = this.renderer;
            var osCvs = this.oldScreenCanvas.object;
            var tsCvx = this.transitionCanvas;

            this.oldPixelData = null;
            this.transitionAlphaArray = null;

            this.canUpdate = false;
            this.currentOffset = 255;
            this.isRun = true;
            this.time = time;

            // 設置畫布大小
            osCvs.width = tsCvx.width = renderer.width;
            osCvs.height = tsCvx.height = renderer.height;

            // 繪製預設圖
            this.oldScreenContext.drawImage(renderer.canvas, 0, 0);
            this.transitionContext.drawImage(img, 0, 0, renderer.width, renderer.height);

            /* 預先對過渡圖進行灰度計算 */
            var pix = this.transitionContext.getImageData(0, 0, renderer.width, renderer.height);
            var iD = pix.data;
            var i = iD.length;
            while ((i -= 4) > 0) {
                iD[i + 3] = iD[i] * 0.3 + iD[i + 1] * 0.59 + iD[i + 2] * 0.11;
            }
            _this.transitionAlphaArray = iD;

            if (typeof delay != "undefined") {
                setTimeout(function () {
                    _this.canUpdate = true;
                }, delay);
            } else {
                _this.canUpdate = true;
            }

            setTimeout(function () {
                _this.fireEvent("begin");
            }, 0);
        },
        draw: function () {
            /// <summary>繪製過渡動畫</summary>

            if (!this.isRun) return;

            var renderer = this.renderer;

            if (this.transitionAlphaArray) {
                var osCtx = this.oldScreenContext;
                var tArr = this.transitionAlphaArray;
                var pix = this.oldPixelData;

                if (!pix) {
                    this.oldPixelData = pix = osCtx.getImageData(0, 0, renderer.width, renderer.height);
                }
                var iD = pix.data;

                var i = iD.length;
                while ((i -= 4) > 0) {
                    if (tArr[i + 3] > this.currentOffset) {
                        iD[i + 3] = this.currentOffset;
                    }
                }
                osCtx.putImageData(pix, 0, 0);
            }

            renderer.drawImage(this.oldScreenCanvas, 0, 0);
        },
        update: function (currentTime) {
            /// <summary>更新過渡動畫</summary>
            /// <param name="currentTime" type="Number">當前時間</param>

            if (!this.isRun || !this.canUpdate) return;
            if (!this.beginTime) {
                this.beginTime = currentTime;
                return;
            }

            var timeSpan = currentTime - this.beginTime;
            if (timeSpan < this.time) {
                this.currentOffset = 255 - (timeSpan / this.time * 255) | 0;
            } else {
                delete this.beginTime;
                this.canUpdate = false;
                this.isRun = false;

                this.fireEvent("end");
            }
        }
    });

})(window, document, Jyo);


演示地址:https://math3d.xyz/Avg/

發佈了22 篇原創文章 · 獲贊 71 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章