【H5動畫】談談canvas動畫的閃爍問題

通常來講,在H5開發中,使用canvas每每只是爲了展現一些簡單的圖表或者簡單短小的動畫,不多考慮到有閃爍的問題。
最近,在手機QQ魔法表情的項目中,就遇到了奇葩的閃爍問題。
這裏說的閃爍,是指動畫剛開始播放,忽然出現瞬間空白(大概1幀到2幀的時間)。html

 

 

閃爍分析

這個魔法表情,實際是html5版本的動畫,使用Fanvas(即將騰訊開源),從swf轉化爲canvas 2d動畫。
在iOS體系下,不管哪一個機型仍是哪一個系統版本,都沒有出現問題。
可是,在部分Android機器上則出現了很奇葩的閃爍,包括小米note,小米4,三星,魅族。奇怪的是,小米同體系的紅米note則徹底正常。html5

翻閱H5 api的資料,咱們知道requestAnimationFrame在Android 4.4後才支持,而動畫的機制是,若是該接口不可用,則採用setInterval取代。
1編程

那麼貌似有點眉目了,紅米note也是4.4系統,而iOS全系都ok,也許問題就在這。
重溫一下FPS和瀏覽器重繪的知識。瀏覽器保持一個幀頻(通常60fps)刷新畫面,這就包括頁面中的canvas。而動畫的繪製過程,包括幾個步驟:
一、擦除整個canvas;
二、計算全部元件/圖元的位置顏色;
三、逐個逐個,繪製全部元件到canvas上。
這個過程,由不精準的setInterval驅動,這個時鐘沒法跟瀏覽器重繪的頻率同步。canvas

那麼,就可能出現這樣的時序狀況:
一、擦除整個canvas;
二、瀏覽器到達重繪時間點,此時canvas爲空白,瀏覽器繪製空白的canvas;
三、50ms後,這一幀動畫全部元件繪製完成(可能會由於動畫複雜, 而消耗長時間,超過16ms)
關鍵點就在這裏了。api

 

 

好招不怕舊

雙緩衝,只要對圖形圖象處理編程有稍稍一些瞭解,都應該聽過這個術語,即便不知道這玩意是什麼。這個技術很是很是古老,也很是很是簡單,但效果卻很是很是好。
來看看百度百科的說明,可能沒有wikipedia專業,但我以爲足夠解釋問題了。瀏覽器

閃爍是圖形編程的一個常見問題。須要多重複雜繪製操做的圖形操做會致使呈現的圖像閃爍或具備其餘不可接受的外觀。雙緩衝的使用解決這些問題。雙緩衝使用內存緩衝區來解決由多重繪製操做形成的閃爍問題。當啓用雙緩衝時,全部繪製操做首先呈現到內存緩衝區,而不是屏幕上的繪圖圖面。全部繪製操做完成後,內存緩衝區直接複製到與其關聯的繪圖圖面。由於在屏幕上只執行一個圖形操做,因此消除了由複雜繪製操做形成的圖像閃爍。動畫

回到咱們的動畫中,發現殊途同歸,閃爍、掉幀的問題根源就是由於部分機型下沒有自動實現cnavas的雙緩衝(通常這些都是底層實現的),而canvas每一幀動畫過程又比較漫長,擦除上一幀動畫後,要過幾十毫秒才能繪製完成下一幀,而這段時間內就會出現明顯的空白。
解決辦法就是:
建立一個臨時canvas,先把下一幀動畫繪製到臨時canvas上。在每次真正繪製的時候,擦除正式canvas後,立刻drawImage把臨時canvas的內容copy過去,而這個copy過程是很是很是高效的,因此基本能夠杜絕閃爍。this

 
 
具體代碼
    p.update = function() {
        if (!this.cacheCanvas) {
            this.cacheCanvas = document.createElement("canvas");
            this.cacheCanvas.width = this.canvas.width;
            this.cacheCanvas.height = this.canvas.height;
        }

        updateMovieClip();    //圖形變換

        var ctx = this.cacheCanvas.getContext("2d");
        ctx.setTransform(1, 0, 0, 1, 0, 0);
        ctx.save();
        ctx.clearRect(0, 0, this.canvas.width + 1, this.canvas.height + 1);     //部分Android機器很奇葩,若是局部刷新會出現空白的狀況
        drawMovieclip(ctx);    //繪製
        ctx.restore();

        //雙緩衝,先畫到臨時canvas,再轉到正式canvas
        ctx = this.canvas.getContext("2d");
        ctx.clearRect(0, 0, this.canvas.width + 1, this.canvas.height + 1);
        ctx.drawImage(this.cacheCanvas, 0, 0, this.canvas.width, this.canvas.height);

    };
相關文章
相關標籤/搜索