手把手教你撸一个能生成抖音风格动图的gif制作平台

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/70/70d3857840eab26d7644528ca453ae31.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"前言"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"又到了一周一次的周总结, 笔者基于之前的开源项目 "},{"type":"link","attrs":{"href":"https://github.com/MrXujiang/blink","title":null},"content":[{"type":"text","text":"blink"}]},{"type":"text","text":" , 开发了一款能在线配置故障艺术, 并一键生成gif动图的平台, 这里暂时取名为"},{"type":"text","marks":[{"type":"strong"}],"text":"QT"},{"type":"text","text":". 接下来笔者将覆盘一下该可视化平台的实现步骤以及功能点, 让大家都能做自己的Gif动图生成平台."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在线访问地址: "},{"type":"link","attrs":{"href":"http://io.nainor.com/qt","title":null},"content":[{"type":"text","text":"趣图——一款轻量级生成抖音风格动效的在线工具"}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"正文"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在开始正文之前, 我们先来看看使用效果图:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/22/22fc56f681b2da34037f9e73ad6b0abb.gif","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先我们拆解一下功能:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"图形编辑区 —— 用来编辑动图样式, 问文字等"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"预览区 —— 用来预览用户实时配置的动画效果"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"结果展示区 —— 用来存放生成的gif效果"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"gif文件自动下载"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们大致理清了我们需要实现的功能之后, 我们就可以一步步来实现了."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在这里我想先简单介绍一下背景: 在笔者之前开源了生成自定义故障艺术的组件库Blink之后, 发现如果要将故障动图放到第三方平台, 必须需要用第三方录屏软件先把动图录制下来, 然后保存gif之后在传到第三方平台, 这个操作链路如下:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/9d/9d0989b949295f3c7e5253610a3fdc57.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"笔者是在忍受不了那么多步骤, 一般在笔者的认知里一般实现一件简单的事情超过3个步骤, 笔者是不能接受的,尤其是录屏这种耗时任务. 所以再三思考还是决定自己开发一个平台,将步骤压缩到2步:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/4c/4c1e58f1a014488e175615d0729c8c1d.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"好了, 开始我们下面的技术探索."}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.1 开发图形编辑区"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"图形编辑区主要是表单编辑, 笔者这里使用antd来快速搭建一个简单表单, 唯一值得注意的就是颜色组件, 这里笔者使用react-color, 因为vue3.0对jsx支持越来越好, 所以实现原理和react很像,这里笔者就直接用react来举例子. 代码如下:"}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"
\n
\n 文字: \n onChange('text', e)} />\n
\n
\n 大小: \n onChange('fontSize', e)} />\n
\n
\n 文字颜色: \n onChange('textColor', e, 1)} />\n onChange('textColor', e, 2)} />\n
\n
\n 背景色: \n onChange('themeColor', e)} />\n
\n
\n \n \n {/* */}\n
\n
\n复制代码"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"大家可以不用太关注代码细节, 你可以使用任何熟悉的方式去开发, 表单编辑器主要是实现和预览区域的互通, 在react里我们用hooks组件的useState来和Blink组件互通, vue的话可以直接用data或者vuex解决问题, 具体如下图实现:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/51/51dff2103a347662ede260cc75d46924.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"只要大家能实现这种过程就可以了. 在QT项目里的效果如下:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e4/e43e952487c1709a29fb027ea545cb89.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.2 实现预览区"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"预览区域的实现很简单, 通过Blink暴露的属性来动态传递即可, 这里我们有必要了解一下Blink的内部实现, 先上一下githugb地址: "},{"type":"link","attrs":{"href":"https://github.com/MrXujiang/blink","title":null},"content":[{"type":"text","text":"基于react的css故障艺术库"}]},{"type":"text","text":" , 我们直接看组件的实现方式:"}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"import React, { useRef, useEffect } from 'react'\nimport './index.less'\n\nexport default function Blink(props) {\n const { \n text = '趣谈前端',\n fontSize = '48px',\n themeColor = '#000',\n textColor = ['#74fcfd', '#ea3448'],\n onRef\n } = props\n\n const ref = useRef(null)\n\n useEffect(() => {\n onRef && onRef(ref)\n }, [])\n\n return (\n
\n
\n
{text}
\n
{text}
\n
\n
\n )\n}\n复制代码"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至于样式问题, 笔者在github里有详细的介绍, 这里就不详细说明了. 所以说我们在项目中实现预览也很简单, 直接引入组件即可:"}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" { blinkRef.current = ref.current}} />\n复制代码"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"value就是form表单的配置产物."}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.3 实现预览gif动图"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"实现预览gif动图是文章的重点, 我们要考虑如何将dom转化为图片, 然后再将图片转化为gif. 这块笔者思考了一会, 想出了一个解决方案, 思路如下: "},{"type":"text","marks":[{"type":"strong"}],"text":"先用canvas库定时截取预览区域的动画效果, 生成n张关键帧图片, 然后利用canvas将多张关键帧组装成gif动图"},{"type":"text","text":". 笔者的思路主要采用的flash软件的关键帧动画的实现, 具体流程如下:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b2/b2dee56bcbbcb199b4caa46dc241507a.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"图中我们涉及到了几个有意思的插件, 笔者在H5-Dooring项目中也用到过,分别为:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"dom-to-image"},{"type":"text","text":" —— 转门将dom转化为图片的库"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"gif.js"},{"type":"text","text":" —— 将多张图片转化为gif动图"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"实现过程中由于"},{"type":"text","marks":[{"type":"strong"}],"text":"dom-to-image"},{"type":"text","text":"产生图片的过程是异步的, 但是我们要将所以图片生成完之后才能传给"},{"type":"text","marks":[{"type":"strong"}],"text":"gif.js"},{"type":"text","text":", 这里笔者用了"},{"type":"text","marks":[{"type":"strong"}],"text":"promise.all"},{"type":"text","text":";来实现(注意, 考点). 我们先将第一步骤二次封装成新的"},{"type":"text","marks":[{"type":"strong"}],"text":"promise"},{"type":"text","text":"对象, 代码如下:"}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"const generateImg = (node, imgId, time) => {\n return new Promise((resolve, reject) => {\n setTimeout(() => {\n domtoimage.toPng(node)\n .then(function (dataUrl) {\n let img = new Image();\n img.src = dataUrl;\n img.id = imgId;\n img.className = 'imgPiece';\n document.getElementById('imgBox').appendChild(img);\n resolve(document.getElementById(imgId));\n })\n .catch(function (error) {\n reject(error);\n });\n }, time)\n })\n }\n复制代码"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其次我们使用promise.all来将图片统一收集传给gif.js对象:"}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"const generateGif = () => {\n document.getElementById('imgBox').innerHTML = '';\n let blink = blinkRef.current;\n let promiseArr = [];\n\n for(let i=0, len=24; i < len; i++) {\n promiseArr.push(generateImg(blink, `img${i+1}`, 16 * i));\n }\n\n Promise.all(promiseArr).then(res => {\n if(res) {\n let w = res[0].width;\n let h = res[0].height;\n // res即为所有的img集合, 可以直接传给gif.js ...\n });\n }\n })\n }\n复制代码"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"关于gif.js的使用方法, 官网里也有详细的介绍, 这里笔者不一一举例了, 感兴趣的朋友可以研究一下."}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.4 一键下载gif动图"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"实现现在gif文件我们采用"},{"type":"text","marks":[{"type":"strong"}],"text":"file-saver"},{"type":"text","text":"模块来实现, 实现思路也很简单, 如下:"}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"saveAs(image, `${uuid(6, 10)}.gif`);\n复制代码"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"image即是gif.js或者其他动图插件生成的gif图片, uuid主要是给图片命名. 我们可以看看几个下载好的gif例子:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/fa/faa575682d6b380765f53c3760fdfc34.gif","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"更多自定义配置大家可以去官网配置: "},{"type":"link","attrs":{"href":"http://io.nainor.com/qt","title":""},"content":[{"type":"text","text":"http://io.nainor.com/qt"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"最后"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在H5编辑器"},{"type":"link","attrs":{"href":"https://github.com/MrXujiang/h5-Dooring","title":null},"content":[{"type":"text","text":"H5-Dooring"}]},{"type":"text","text":"中,后期也会实现类似的功能,大家感兴趣的可以了解一下。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"github地址:"},{"type":"link","attrs":{"href":"https://github.com/MrXujiang/h5-Dooring","title":null},"content":[{"type":"text","text":"H5编辑器H5-Dooring"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果想学习更多H5游戏, "},{"type":"text","marks":[{"type":"strong"}],"text":"webpack"},{"type":"text","text":","},{"type":"text","marks":[{"type":"strong"}],"text":"node"},{"type":"text","text":","},{"type":"text","marks":[{"type":"strong"}],"text":"gulp"},{"type":"text","text":","},{"type":"text","marks":[{"type":"strong"}],"text":"css3"},{"type":"text","text":","},{"type":"text","marks":[{"type":"strong"}],"text":"javascript"},{"type":"text","text":","},{"type":"text","marks":[{"type":"strong"}],"text":"nodeJS"},{"type":"text","text":","},{"type":"text","marks":[{"type":"strong"}],"text":"canvas"},{"type":"text","text":"数据可视化等前端知识和实战,欢迎在《趣谈前端》一起学习讨论,共同探索前端的边界。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章