3

一,原理

1.canvas、img、video,都是图像对象,属于图像类型的节点。这种图像对象不是Image 对象,而是对加载图像数据流的节点的统称。

  • img 里的图像数据来源于提前制作好图片文件的,是静止的,比如.jpg,.png,.svg 等。.gif 是个特殊存在,另当别论。
  • vedio 里的视频数据来源于提前制作好的视频文件,是动起来的。
  • canvas 图像数据是动态生成的。

2.图像对象的数据的读写

  • 读取图像数据

    • 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...

3.gif 是介于图片和视频之间的特殊存在。

  • 用读取image 数据的原理读取它,只能读到第一帧gif 图片。

直接绘制gif 肯定是不好使的

<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,频繁绘制,也不好使

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 数据

  • 用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 对象的集合

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();
    }
});

已注销
148 声望9 粉丝