一,原理
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();
}
});
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。