前言
笔者之前利于业余时间开发了一个gif动图生成平台, 具体开发背景我也在上一篇文章手把手教你撸一个能生成抖音风格动图的gif制作平台中介绍过了, 我们今天继续来实现该平台, gif动图平台的实现方式比较将完全用前端的手段来实现, 所以大家在接下来的内容中会发现很多有意思的前端插件. 接下来我们看看主要要实现的功能点:
- 纯前端实现图片上传和预览
- 基于react-beautiful-dnd实现元素自由拖动排序
- 使用javascript实现生成uuid的函数
- 使用file-saver实现前端下载文件
- 使用gif.js实现基于图片生成gif动图
- 控制gif动图播放速度的方法
正文
还是按照笔者一贯的风格, 在实现功能之前我们先看看实现后的预览效果:
由效果图可以看出我们只需要上传图片序列, 就可以动态生成gif动图, 并且可以右键保存gif文件. 我们还可以控制动图的播放速度和排列顺序, 以便更灵活的配置动图. 如果大家想亲自体验,可以用以下方式直达:
1. 基本页面搭建
在开始之前我们先要理清基本的页面结构, 笔者划分为如下结构:
由上图可知一共划分为3个区, 我们可以使用任何我们熟悉的第三方组件去实现, 笔者这里采用antd来开发. 技术方案图如下:
以上就是笔者分析的大致实现目标, 也是笔者常用的开发导图, 目标导向对开发效率的提升还是非常有帮助的, 笔者建议大家也可以在实现较复杂的业务需求之前先思考方案,再动手敲代码.
2. 实现图片上传预览功能
对于第一步骤的界面我想大家都能实现, 我们现在具体一步步落实到功能实现. 我们先来实现一下图片的上传预览. 由于我们这里没有借助服务器, 完全由前端的方式实现图片预览, 所以我们需要用到FileReader对象.
FileReader 对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 File 或 Blob 对象指定要读取的文件或数据。
其中File对象可以是来自用户在一个input元素上选择文件后返回的FileList对象,也可以来自拖放操作生成的DataTransfer对象,还可以是来自在一个HTMLCanvasElement上执行mozGetAsFile()方法后返回结果。 接下来我们看看如何利用antd的Upload组件和FileReader实现图片预览, 具体代码如下:
constuploadprops=useMemo(() => ({ name: 'file', multiple: true, listType:"picture-card", onPreview: () => {}, showUploadList: false, beforeUpload(file, fileList) { // 解析并提取excel数据letreader=newFileReader(); reader.onload=function(e) { leturl=e.target.result; setImageList(prev=> [prev, {id: uuid(6, 16), url}]) }; reader.readAsDataURL(file); } }), []);
uploadprops即为Upload组件需要的属性配置, 通过代码我们可以知道, 我们在beforeUpload阶段拦截了图片, 通过FileReader对象的readAsDataURL API将读取到的文件转化为base64的地址, 这样我们就能通过如下方式展示图片了:
<imgsrc={item.url}id={item.id}alt=""/>
在代码中我们将图片数据存储到一个对象里, 对象包括由uuid函数生成的唯一id和url组成, 至于为何生成唯一id, 这里笔者将在下文中介绍.
3. 使用react-beautiful-dnd实现元素自由拖动排序
大家在研究过H5-Dooring | 一款强大的开源H5编辑器 后就会发现react-dnd这个模块很熟悉, 因为该开源编辑器就使用了react-dnd实现的组件拖拽和组件数据传递的. 笔者在社区又发现了一个比较有意思的拖拽库react-beautiful-dnd, 同样可以实现优雅平滑的智能拖拽效果, 基本案例如下:
在深入研究该库之后笔者发现可以直接用来实现图片组件的拖拽并排序的功能, 所以笔者直接使用该库来封装我们的图片组件. 由于该库的使用有详细的案例代码,这里笔者就不做详细介绍了, 只需要提一点就是为了实现排序, 我们需求确定每一个元素的唯一标识, 所以这里笔者想到了uuid, 所以渲染图片的数组数据结构可以长这样:
constimgList= [ { id: uuid(6, 10), url: '图片的base64位地址' } ]
为了限制图片上传过大, 我们还可以在Upload组件的beforeUpload阶段限制图片上传大小, 这个根据使用而定. uuid的实现方式很多, 笔者这里送上一个实现方式:
// 生成uuidfunctionuuid(len: number, radix: number) { letchars='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''); letuuid= [], i; radix=radix||chars.length; if (len) { for (i=0; i<len; i++) uuid[i] =chars[0| (Math.random() *radix)]; } else { letr; uuid[8] =uuid[13] =uuid[18] =uuid[23] ='-'; uuid[14] ='4'; for (i=0; i<36; i++) { if (!uuid[i]) { r=0| (Math.random() *16); uuid[i] =chars[i===19? (r&0x3) |0x8 : r]; } } } returnuuid.join(''); }
实现后的效果如下图:
至于删除图片的方法也很简单, 通过图片的唯一id去数组里使用数组的fiter方法删除即可.
4. 控制gif动图播放速度的方法
控制gif的播放速度方法我们可以用slider组件实现, gif.js也提供速度的接口, 我们只需要实现速度值的计算即可. 我们都知道滑块滑动越长, 数值越大, 与之对应的 速度越大, 时间间隔越小, 所以我们在前端层设计的展示效果如下:
滑块最大值笔者设置为20, 最小值为1, 对应的当滑块设置为最大值时, gif的播放速度最大, 每张图片停留间隔为0.1s, 滑块为最小值1时, gif播放速度最小, 每张图片停留2s, 根据这个规律我们得到了如下规律:
具体代码如下:
consthandleSpeed= (v) => { letrealSpeed= (20-v+1) /10; setSpeed(realSpeed) }
当然大家可以用更简单的方式来实现, slider组件也提供反向取值的操作.
5. 基于图片序列生成gif动图
基于图片序列生成gif的方式也很简单, 我们通过批量获取图片拖动区的图片, 组装成数组传给gif.js即可. 我们直接看效果:
6. 使用file-saver实现前端下载文件
我们只需要把生成的gif图片, 传递给file-saver模块中, 使用其提供的API即可下载文件, 这里在之前文章笔者也介绍过了, 这里直接上代码:
import { saveAs } from'file-saver'; // resultImage为gif生成的gif图片对象saveAs(resultImage, `${uuid(6, 10)}.gif`);