WEB 版 GIF 编辑器
之 从无到有&从外到内
需求背景
技术调研
- GIF素材收集
- 编译处理+技术选型
- GIF文件存储
技术调研
资源收集:
书签栏注入JS工具,截获截图,作为帧素材备用。
- 1
(function(){
if(!window.$ || !$.getUrlParam) return alert('环境不对头,不能使用。');
var qihoo_playerEl = $("#qihoo_player")[0];
if(!qihoo_playerEl) return alert('已经废弃了~');
var ipcData = (window.playInfo && window.playInfo.origin) || { sn:$.getUrlParam('sn') };
var sn = ipcData.sn;
if(!sn) return alert('没有取到SN号,无法继续。');
//设定关键帧采集间隔时长数
var delayTime = prompt('请设定关键帧采集间隔时长数(秒,10-N)',30);
if(isNaN(delayTime) || delayTime<1){
return alert('无效帧间隔时长'+(delayTime<1?'(这是生成定格动画的,不是录像,所以时间不能太短)':'')+',还想玩从新来过吧!');
}
delayTime *= 1000;
//设定帧数
var frameCount = prompt('请设定要采集的关键帧总数(10-50)',10);
if(isNaN(frameCount) || frameCount<1){
return alert('无效帧总数,还想玩从新来过吧!');
}
///if(frameCount>100)return alert('你要疯啊? 帧数太多会搞死的...');
var pr = '';
//监控被移动到后台则提示
document.addEventListener("visibilitychange", function(e){
if(document.visibilityState=='hidden'){
document.title='[页面已切换到后台,可能无法正常截图]';
}else{
document.title=pr;
}
});
var _fCount = 0, _date = new Date(), tit = ipcData.titlePub || $(".title").text()||sn;
/* 截屏上传图片 */
function startUpload(){
qihoo_playerEl.uploadScreenshotImg({
url: 'http://'+(window._s_h_1?'1.':'')+'huzj.sinaapp.com/gif/upload.php',
from: tit,
sn: sn
});
_fCount ++;
pr = document.title='截图进度:'+(Math.round(_fCount/frameCount*100))+'% ('+(_fCount+'/'+frameCount)+')';
/* 输出进度日志 */
console.log("计划获取总帧数:"+frameCount, '当前截取第'+_fCount+'帧', ' 已耗时(秒):'+ Math.round( (new Date() - _date)/1000 ),' 效果预览:','http://huzj.sinaapp.com/gif/index.html?sn='+sn+'&step=1&acount='+_fCount );
if(_fCount<frameCount){
setTimeout(startUpload, delayTime);
}else{
alert('已完成摄像机['+tit+']的截图');
}
}
startUpload();
})();
技术调研
加工处理:
基于一个用 CoffeeScript 编写的gif处理库 gif.js,
合成为BLOB形式的GIF文件。
- 2
/*** gif.js使用方法 ***/
// 实例化一个GIF对象
var gif = new GIF({
workers: 2,
quality: 10
});
// 通过img元素对象来创建帧
gif.addFrame(imageElement);
// 通过canvas元素对象创建帧 示例:http://jnordberg.github.io/gif.js/tests/canvas.html
gif.addFrame(canvasElement, {delay: 200});
// 通过canvas上下文对象创建帧
gif.addFrame(ctx, {copy: true});
// 通过video当前画面创建帧 示例:http://jnordberg.github.io/gif.js/tests/video.html
gif.addFrame(videoElement, {copy: true});
// GIF生成完毕后通过异步事件监听做相应处理
gif.on('finished', function(blob) {
window.open(URL.createObjectURL(blob));
});
// 渲染GIF图
gif.render();
技术调研
文件保存:
URL.createObjectURL( BLOB ) 生成GIF URL,输出到页面;
并使用A标签的download属性,自定义GIF文件名称。
实现工具雏形。
- 3
/*** gif 存储 ***/
// GIF生成完毕后通过异步事件监听做相应处理
gif.on('finished', function(blob) {
var blobUrl = URL.createObjectURL(blob);
retBoxEl.innerHTML = '<a href="'+blobUrl+'" download="水滴直播['+sn+']动态图.gif">'+
'<img src="'+blobUrl+'">'+
'</a>';
});
gif.js内部逻辑流程
图片资源加载成功
drawImage 绘制
图片到canvas上
getImageData 从canvas取得帧图像的像素矩阵数据
new Worker
启动子进程
addFrame
逐帧将imageData
post给子进程 render
子进程完成24位色到
8位色的计算转换
将ArrayBuffer
回传给主进程
使用Blob API 创建
image/gif 图片文件
postMessage
postMessage
服务端数据存取
- SaeStorage 进行图片和JSON数据存取
- 通过 rewrite 规则实现伪静态图片URL
确定需求&产品设计
- UI样式
- 色调
- 布局
- 细节
- 交互功能
- 核心功能
- 附加功能
- 锦上添花
产品设计 - UI
- 色调 - “继承、发展”了各现成UI效果
- 布局 - boxFlex 基于可操控性考虑
- 收缩比率 flex-shrink 默认值不统一,
不参与弹性计算的子容器需reset为0 - 小点心 width: calc(100% - 100px );
- 小点心 CSS3 counter
- 收缩比率 flex-shrink 默认值不统一,
- 重置默认组件样式,
pointer-events及各种伪类的使用。
图形化界面降低使用成本 UI 雏形
产品设计 - 核心功能
- 导入素材 - 按水滴设备、从本地
- 逐帧或批量编辑
元件、尺寸、坐标、样式、序列的编辑 - 导出便于传播的GIF图
产品设计 - 附加功能
- 存档
- 存档的导入导出
- 本地文件导入
- 新建
- 添加图片元件
- 添加手绘元件
产品设计 - 锦上添花的功能
- 侧边栏展开收起
- 网格参考线
- 标尺工具
- 放大缩小
- 宽高比例锁
- 拖拽控制编辑器区域大小
- 帧元素的事件编程
-- 逻辑控制公共元素显隐 - 帧延迟时长的动态编程
- 接入多说...
技术实施
- 基于QClient设计思路
- 围绕数据模型的事件消息机制
gifEditor.data._base_data - 使用 $.tmpl 缓存+动态渲染
- MV* ?双向绑定?
UI和具体需求有了,接下来考虑功能的实现
先看下代码,再继续往下看PPT总结
数据交互流程
- UI:提供可视化数据操作界面+渲染预览效果
- Logic:业务层面复杂场景下数据API的整合处理
- Data:数据模型及相应读写操作API
功能划分&技术选型
- jQuery
- gif.js 图片编译
- gif_editor.js 业务逻辑
- common.js 公用方法
- tmpl.js 模板解析
- h5_upload H5上传组件
- rulers.js 标尺组件
待探索的方向们
- ‘泛摄像头’||视频转GIF - 播放器或服务端方向
- 优化或换掉底层GIF.JS - 开销、效率
- JS浮点计算溢出导致不准确 - 服务端?
- 关注其他动图格式进展 - 性能、质量
- 趣味性挖掘 - 定格1、定格们
- GIF资源站还蛮热门的 - 工具、资源、社交
回顾
- 探寻需求背景
- 可行性技术调研
- UE与产品设计
- 技术设计与实施
- 未来方向
Q & A
gif编辑器
By 胡尊杰