手把手教你擼一個能生成抖音風格動圖的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}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章