gif 圖片在canvas 中的播放

一,原理

1.canvas、img、video,都是圖像對象,屬於圖像類型的節點。這種圖像對象不是Image 對象,而是對加載圖像數據流的節點的統稱。javascript

  • img 裏的圖像數據來源於提早製做好圖片文件的,是靜止的,好比.jpg,.png,.svg 等。.gif 是個特殊存在,另當別論。
  • vedio 裏的視頻數據來源於提早製做好的視頻文件,是動起來的。
  • canvas 圖像數據是動態生成的。

2.圖像對象的數據的讀寫php

  • 讀取圖像數據html

    • img 和video 沒有直接獲取ImageData 的方法,但能夠經過canvas 獲取
    • canvas 使用getImageData 方法讀取自身的圖像數據
  • 寫入圖像數據前端

    • img 和video 只能經過src ,以資源路徑的方式設置其顯示圖像(img 的src 還可使用base64)。但他們沒法直接用ImageData 設置圖像。
    • canvas 能夠用drawImage 或fillStyle 方法,以圖像對象爲參數,爲canvas 或其內部元素寫入圖像數據。canvas 也能夠經過putImageData方法,以ImageData 對象爲參數,爲其寫入圖像數據。

canvas 讀取img 圖像的案例: https://www.runoob.com/try/tr...
canvas 讀取video 圖像的案例: https://www.w3school.com.cn/t...vue

3.gif 是介於圖片和視頻之間的特殊存在。html5

  • 用讀取image 數據的原理讀取它,只能讀到第一幀gif 圖片。

直接繪製gif 確定是很差使的java

<div>
    <img id="img" src="./assets/images/girl.gif">
</div>
<canvas id="c" width="300" height="212"></canvas>
<script>
    const img=document.getElementById("img");
    const c=document.getElementById("c");
    const ctx=c.getContext('2d');
    ctx.drawImage(img,0,0);
</script>

利用requestAnimationFrame,頻繁繪製,也很差使git

const img=document.getElementById("img");
const c=document.getElementById("c");
const ctx=c.getContext('2d');
render();
function render(){
    ctx.drawImage(img,0,0);
    window.requestAnimationFrame(render);
}
  • 用讀取video 數據的原理讀取它,video 根本就不認這種格式。
<video src="./assets/images/girl.gif"></video>
  • gif 數據的獲取,只能追本溯源,用XHR 請求。

二,gif 數據讀取並顯示的流程

1.獲取gif 數據github

  • 用XHR 請求gif 圖片
  • 設置圖片的請求類型爲純文本格式:將請求頭裏的Content-Type 設置爲「text/plain,如:
let xhr=new XMLHttpRequest();
xhr.open('get','./assets/images/zao.gif');
xhr.setRequestHeader("Content-Type", "text/plain");
xhr.send();
xhr.onreadystatechange=function(){
    if(xhr.readyState===4){
        if(xhr.status===200){
            console.log('response',xhr.response);
        }
    }
}

2.從response 中提取gif 數據,並解析成ImageData 對象的集合canvas

3.用canvas 上下文的putImageData 方法,將ImageData 數據加載到canvas 中

具體實現方式我就不說了,有點複雜,不是一時片刻能說完的(真正緣由是我研究了半天libgif.js源碼,仍是隻看懂了大概流程)。先知道這個原理吧,libgif.js 庫就是實現這塊功能的。

三,libgif.js

libgif.js已經完成了對gif 的解析,並將其寫入了canvas 中。
libgif.js網址:https://github.com/buzzfeed/l...

下面是我從官網上覆制粘貼下來,用百度翻譯一下 ,而後略做調整。英語好的能夠跳過這裏,看官網。

1.寫在圖片標籤裏的屬性 Image tag attributes

  • rel:animated_src - 若是指定了此url,則將其加載到播放器而不是src中。這容許顯示預覽幀,直到動畫gif數據流入畫布
  • rel:auto_play - 若是未指定,則默認爲1。若是設置爲零,則須要調用play()方法
  • rel:rubbable - 若是未指定,則默認爲0。若是設置爲1,則gif將是一個帶有處理程序的畫布來處理摩擦。

2.構造函數 Constructor options

  • gif - 必填。img標籤的DOM元素。
  • loop_mode - 可選。將此設置爲false將強制禁用gif的循環。
  • auto_play - 可選。與上面的rel:auto_play屬性相同,此arg會覆蓋img標記信息。
  • max_width - 可選。將圖像從max_width縮放到max_width。有助於移動。
  • rubbable - 可選。讓它能夠擦掉。
  • on_end - 可選。添加一個回調,用於當gif到達單個循環結束時(一次迭代)。傳遞的第一個參數將是gif HTMLElement。
  • loop_delay - 可選。每次循環(迭代)後暫停的時間(以毫秒爲單位)。
  • progressbar_height - 可選。進度條的高度。
  • progressbar_background_color - 可選。進度條的背景顏色。
  • progressbar_foreground_color - 可選。進度條的前景色。

3.loading 事件

  • load(callback) - 將src指定的gif或img標記的rel:animated_src sttributie加載到canvas元素中,而後調用callback(若是有的話)
  • load_url(src,callback) - 將src參數中指定的gif文件加載到canvas元素中,若是傳遞了一個,則調用callback

4.播放控制器 play controls

  • play - 開始玩GIF
  • pause - 中止播放gif
  • move_to(i) - 移動到gif的第i幀
  • move_relative(i) - 向前移動i幀(若是i <0則向後移動)

5.數據獲取 getters

  • get_canvas - gif正在播放的canvas元素。方便分配事件處理程序。
  • get_playing - gif當前是否正在播放
  • get_loading - gif是否已完成加載/解析
  • get_auto_play - 是否將gif設置爲自動播放
  • get_length - gif中的幀數
  • get_current_frame - 當前顯示的gif幀的索引

6.官方案例

<div>
    <img id="img1"
         src="./assets/images/zao.gif"
         rel:auto_play="1"
         rel:rubbable="1"
    >
</div>
<div>
    <img id="img2"
         src="./assets/images/zao.jpg"
         rel:animated_src="./assets/images/zao.gif"
         rel:auto_play="0"
    />
</div>
<div>
    <a href="javascript:;" onmousedown="sup2.pause(); return false;">Pause</a> |
    <a href="javascript:;" onmousedown="sup2.play(); return false;">Play</a> |
    <a href="javascript:;" onmousedown="sup2.move_to(0); return false;">Restart</a> |
    <a href="javascript:;" onmousedown="sup2.move_relative(1); return false;">Step forward</a> |
    <a href="javascript:;" onmousedown="sup2.move_relative(-1); return false;">Step back</a>
</div>

<canvas id="c" width="300" height="212"></canvas>
<script src="./lib/libgif.js"></script>
<script src="./lib/rubbable.js"></script>
<script>
    let img1=document.getElementById("img1");
    let img2=document.getElementById("img2");
    let sup1 = new RubbableGif({ gif: img1 } );
    sup1.load();
    let sup2 = new SuperGif({ gif: img2 } );
    sup2.load();
</script>

注:

  • RubbableGif 對象繼承自 SuperGif 對象,SuperGif是解析gif 的核心。
  • 前端創建的<img> 節點,只是一個向SuperGif 對象傳遞數據的工具,等SuperGif 解析完了gif,就會用canvas 將img 替換掉。

四,實際應用

實際項目中,咱們可能只是想在咱們的canvas 項目裏添加動起來表情包,而不是額外的創建一個真實的img 節點,更不是再生成一個真實的canvas 將其替換。因此,我作了一下調整:

1.將rubbable.js 中替換img 節點的方法過濾掉。爲何叫過濾呢,由於這樣不會影響原始功能,尊重做者。

if(parent){
    parent.insertBefore(div, gif);
    parent.removeChild(gif);
}

2.引用Image 對象代替真實的<img> 節點,這樣就不須要再讓瀏覽器渲染img 了,節省資源,真正只將其當成一個傳遞數據的工具。

const gif=new Image(w,h);
gif.setAttribute('data-animated_src',src);

3.爲SuperGif 添加一個change 屬性的方法,讓其在圖片渲染時執行。這樣就能夠在外部,實時獲取SuperGif 裏的canvas。注意,這種寫法不會影響對象的繼承,由於SuperGif 還有各個叫RubbableGif 的子級,SuperGif 實例對其原型屬性修改後,RubbableGif 的實例也會繼承這種修改。這個問題也能夠從繼承上解決,我本着尊重做者的目的,就不改了(其實是看得費勁,不想深究了)。

(function (root, factory) {
    //...
}(this, function () {
    //渲染監聽方法
    var change=function(){}
    var SuperGif = function ( opts ) {
        //...
        var player = (function () {
            //...
            var putFrame = function () {
                //...
                if(options.change){options.change()}
            }
        }
    }
    
})

4.還有的小問題,libgif.js 對標籤屬性的定義不規範。rel:auto_play="0" ,這樣的方式定義在模板文件裏還能夠,好比vue的v-on:click="play()"。在真正的HTML 要這樣的寫:data-auto_play="0"

六,canvas 顯示圖片

1.canvas顯示圖片的方式有兩種:

  • canvas 繪圖:

    • drawImage 方法將將圖像對象繪製到canvas 中。
    • putImageData 方法將ImageData 數據寫入canvas 中,此方法能夠對圖形的rgba 後處理。
  • canvas 元素圖像填充:ctx.createPattern(圖像對象,重複方式) 創建pattern 對象圖像,而後將其賦予ctx.fillStyle

參考地址: https://www.runoob.com/try/tr...

2.上代碼:

項目地址:https://github.com/buglas/fun...,中的gif4.html 文件

  • putImageData 方法代碼
<div>
    <canvas id="canvas" width="300" height="212"></canvas>
</div>
<script src="./lib/libgif.js"></script>
<script src="./lib/rubbable.js"></script>
<script>
    const canvas=document.getElementById('canvas');
    const ctx=canvas.getContext('2d');
    const src='./assets/images/girl.gif';
    const [w,h]=[150,133];
    const GifTool={
        drawGif:({src,w,h,draw})=>{
            const gif=new Image(w,h);
            gif.setAttribute('data-animated_src',src);
            const sup = new SuperGif({
                gif,
                change:()=>{
                    const canvas=sup.get_canvas();
                    const curCtx=canvas.getContext("2d");
                    const imgData=curCtx.getImageData(0,0,w,h);
                    draw({imgData,canvas});
                }
            });
            sup.load();
        }
    }
    GifTool.drawGif({
        src,w,h,
        draw:({imgData})=>{
            ctx.putImageData(imgData,0,0);
        }
    });
</script>
  • fillStyle 方法代碼
GifTool.drawGif({
    src,w,h,
    draw:({canvas})=>{
        const pat=ctx.createPattern(canvas,"repeat");
        ctx.rect(0,0,w,h);
        ctx.fillStyle=pat;
        ctx.fill();
    }
});
  • 還能夠繪製多個
GifTool.drawGif({
    src,w,h,
    draw:({canvas})=>{
        const pat=ctx.createPattern(canvas,"repeat");
        ctx.save();
        ctx.translate(10,0);
        ctx.beginPath();
        ctx.arc(60,80,60,0,Math.PI*2);
        ctx.fillStyle=pat;
        ctx.fill();
        ctx.restore();
        
        ctx.save();
        ctx.translate(150,40);
        ctx.beginPath();
        ctx.arc(60,80,60,0,Math.PI*2);
        ctx.fillStyle=pat;
        ctx.fill();
        ctx.restore();
    }
});
相關文章
相關標籤/搜索