手把手教你擼一個能生成抖音風格動圖的gif制作平臺
前言
又到了一周一次的周總結, 筆者基于之前的開源項目 blink , 開發了一款能在線配置故障藝術, 并一鍵生成gif動圖的平臺, 這里暫時取名為QT. 接下來筆者將復盤一下該可視化平臺的實現步驟以及功能點, 讓大家都能做自己的Gif動圖生成平臺.
在線訪問地址: 趣圖——一款輕量級生成抖音風格動效的在線工具
正文
在開始正文之前, 我們先來看看使用效果圖:
首先我們拆解一下功能:
- 圖形編輯區 —— 用來編輯動圖樣式, 問文字等
- 預覽區 —— 用來預覽用戶實時配置的動畫效果
- 結果展示區 —— 用來存放生成的gif效果
- gif文件自動下載
我們大致理清了我們需要實現的功能之后, 我們就可以一步步來實現了.
在這里我想先簡單介紹一下背景: 在筆者之前開源了生成自定義故障藝術的組件庫Blink之后, 發現如果要將故障動圖放到第三方平臺, 必須需要用第三方錄屏軟件先把動圖錄制下來, 然后保存gif之后在傳到第三方平臺, 這個操作鏈路如下:
筆者是在忍受不了那么多步驟, 一般在筆者的認知里一般實現一件簡單的事情超過3個步驟, 筆者是不能接受的,尤其是錄屏這種耗時任務. 所以再三思考還是決定自己開發一個平臺,將步驟壓縮到2步:
好了, 開始我們下面的技術探索.
1.1 開發圖形編輯區
圖形編輯區主要是表單編輯, 筆者這里使用antd來快速搭建一個簡單表單, 唯一值得注意的就是顏色組件, 這里筆者使用react-color, 因為vue3.0對jsx支持越來越好, 所以實現原理和react很像,這里筆者就直接用react來舉例子. 代碼如下:
- <div className={styles.editorArea}>
- <div className={styles.formItem}>
- <span className={styles.label}>文字: </span>
- <Input value={value['text']} placeholder="請輸入文本內容" onChange={(e) => onChange('text', e)} />
- </div>
- <div className={styles.formItem}>
- <span className={styles.label}>大小: </span>
- <InputNumber value={value['fontSize']} placeholder="請輸入文本大小" onChange={(e) => onChange('fontSize', e)} />
- </div>
- <div className={styles.formItem}>
- <span className={styles.label}>文字顏色: </span>
- <Color value={value['textColor'][0]} onChange={(e) => onChange('textColor', e, 1)} />
- <Color value={value['textColor'][1]} onChange={(e) => onChange('textColor', e, 2)} />
- </div>
- <div className={styles.formItem}>
- <span className={styles.label}>背景色: </span>
- <Color value={value['themeColor']} onChange={(e) => onChange('themeColor', e)} />
- </div>
- <div className={styles.formItem}>
- <span className={styles.label}></span>
- <Button type="primary" onClick={generateGif}>導出Gif</Button>
- {/* <Button style={{marginLeft: '20px'}} onClick={reset}>重置</Button> */}
- </div>
- </div>
大家可以不用太關注代碼細節, 你可以使用任何熟悉的方式去開發, 表單編輯器主要是實現和預覽區域的互通, 在react里我們用hooks組件的useState來和Blink組件互通, vue的話可以直接用data或者vuex解決問題, 具體如下圖實現:
只要大家能實現這種過程就可以了. 在QT項目里的效果如下:
1.2 實現預覽區
預覽區域的實現很簡單, 通過Blink暴露的屬性來動態傳遞即可, 這里我們有必要了解一下Blink的內部實現, 先上一下githugb地址: 基于react的css故障藝術庫 , 我們直接看組件的實現方式:
- import React, { useRef, useEffect } from 'react'
- import './index.less'
- export default function Blink(props) {
- const {
- text = '趣談前端',
- fontSize = '48px',
- themeColor = '#000',
- textColor = ['#74fcfd', '#ea3448'],
- onRef
- } = props
- const ref = useRef(null)
- useEffect(() => {
- onRef && onRef(ref)
- }, [])
- return (
- <div className='blink' style={{backgroundColor: themeColor}} ref={ref}>
- <div className="blink-item" data-text={text} style={{fontSize: fontSize}}>
- <div className="text text-front" style={{color: textColor[0]}}>{text}</div>
- <div className="text text-back" style={{color: textColor[1]}}>{text}</div>
- </div>
- </div>
- )
- }
至于樣式問題, 筆者在github里有詳細的介紹, 這里就不詳細說明了. 所以說我們在項目中實現預覽也很簡單, 直接引入組件即可:
- <Blink {...value} onRef={(ref) => { blinkRef.current = ref.current}} />
value就是form表單的配置產物.
1.3 實現預覽gif動圖
實現預覽gif動圖是文章的重點, 我們要考慮如何將dom轉化為圖片, 然后再將圖片轉化為gif. 這塊筆者思考了一會, 想出了一個解決方案, 思路如下: 先用canvas庫定時截取預覽區域的動畫效果, 生成n張關鍵幀圖片, 然后利用canvas將多張關鍵幀組裝成gif動圖. 筆者的思路主要采用的flash軟件的關鍵幀動畫的實現, 具體流程如下:
圖中我們涉及到了幾個有意思的插件, 筆者在H5-Dooring項目中也用到過,分別為:
- dom-to-image —— 轉門將dom轉化為圖片的庫
- gif.js —— 將多張圖片轉化為gif動圖
實現過程中由于dom-to-image產生圖片的過程是異步的, 但是我們要將所以圖片生成完之后才能傳給gif.js, 這里筆者用了promise.all;來實現(注意, 考點). 我們先將第一步驟二次封裝成新的promise對象, 代碼如下:
- const generateImg = (node, imgId, time) => {
- return new Promise((resolve, reject) => {
- setTimeout(() => {
- domtoimage.toPng(node)
- .then(function (dataUrl) {
- let img = new Image();
- img.src = dataUrl;
- img.id = imgId;
- img.className = 'imgPiece';
- document.getElementById('imgBox').appendChild(img);
- resolve(document.getElementById(imgId));
- })
- .catch(function (error) {
- reject(error);
- });
- }, time)
- })
- }
其次我們使用promise.all來將圖片統一收集傳給gif.js對象:
- const generateGif = () => {
- document.getElementById('imgBox').innerHTML = '';
- let blink = blinkRef.current;
- let promiseArr = [];
- for(let i=0, len=24; i < len; i++) {
- promiseArr.push(generateImg(blink, `img${i+1}`, 16 * i));
- }
- Promise.all(promiseArr).then(res => {
- if(res) {
- let w = res[0].width;
- let h = res[0].height;
- // res即為所有的img集合, 可以直接傳給gif.js ...
- });
- }
- })
- }
關于gif.js的使用方法, 官網里也有詳細的介紹, 這里筆者不一一舉例了, 感興趣的朋友可以研究一下.
1.4 一鍵下載gif動圖
實現現在gif文件我們采用file-saver模塊來實現, 實現思路也很簡單, 如下:
- saveAs(image, `${uuid(6, 10)}.gif`);
image即是gif.js或者其他動圖插件生成的gif圖片, uuid主要是給圖片命名. 我們可以看看幾個下載好的gif例子:
最后
在H5編輯器H5-Dooring中,后期也會實現類似的功能,大家感興趣的可以了解一下。
github:H5編輯器H5-Dooring
github:Blink
本文轉載自微信公眾號「趣談前端」,可以通過以下二維碼關注。轉載本文請聯系趣談前端公眾號。