摘要:用原生寫一個多動癥的簡歷預覽地址源碼地址最近在知乎上看到方應杭用寫了一個會動的簡歷,覺得挺好玩的,研究一下其實現思路,決定試試用原生來實現。
用原生js寫一個"多動癥"的簡歷
預覽地址
源碼地址
會動的簡歷實現思路最近在知乎上看到@方應杭用vue寫了一個會動的簡歷,覺得挺好玩的,研究一下其實現思路,決定試試用原生js來實現。
這張會動的簡歷,就好像一個打字員在不斷地錄入文字,頁面呈現動態效果。又好像一個早已經錄制好影片,而我們只是坐在放映機前觀看。
原理分兩個部分
頁面能看見的不斷跳動著的增加的文字,由innerHTML控制
頁面的布局效果由藏在"背后的"style標簽完成
想象一下你要往一張網頁每間隔0.1秒增加一個啊字,是不是開個定時器,間斷地往body里面塞啊,就可以啊!沒錯,做到這一步就完成了原理的第一部分
再想象一下,在往頁面里面塞啊的時候,我還想改變啊字的字體顏色以及網頁背景顏色,那應該怎么做呢,是不是執行下面的代碼就可以呢,沒錯,只不過更改字體和背景色不是突然改變的,而是也是開個定時器,間斷地往style標簽中塞入以下代碼,這樣就完成了原理的第二步,是不是好簡單 ???, 接下來讓我們一步步完成它
.xxx{ color: blue; background: red; }項目搭建
在這個項目中我們
使用webpack2來完成項目的構建
使用yarn來處理依賴包的管理
使用es6的寫法
使用部分原生dom操作api
standard.js(代碼風格約束利器)
目錄結構如下
最重要的幾個模塊分別是resumeEditor(簡歷編輯模塊) 、 stylesEditor(簡歷樣式編輯模塊) 、 以及vQuery(封裝的dom操作模塊)
最后app.js(入口模塊)再將幾個模塊的功能結合起來完成整個項目。
因為后面的幾個模塊都要依賴這個小模塊,所以我們先簡單的看下。
class Vquery { constructor (selector, context) { this.elements = getEles(selector, context) } optimizeCb (callback) { ... } get (index) { ... } html (sHtml) { ... } addClass (iClass) { ... } css (styles) { ... } height (h) { ... } scrollTop (top) { ... } } export default (selector, context) => { return new Vquery(selector, context) }
可以看出它做的事就是封裝一個構造函數Vquery,它的實例會有一些簡單的dom操作方法,最后為了能夠像jQuery那樣使用$().funcName的形式去使用,我們導出了一個匿名函數,在匿名函數中去new Vquery
stylesEditor(簡歷樣式編輯模塊)簡歷所展現的布局效果都是由這個模塊完成的,核心方法是showStyles。
const showStyles = (num, callback) => { let style = styles[num] let length let prevLength if (!style) { return } length = styles.filter((item, i) => { // 計算數組styles前n個元素的長度 return i <= num }).reduce((result, item) => { result += item.length return result }, 0) prevLength = length - style.length clearInterval(timer) timer = setInterval(() => { let start = currentStyle.length - prevLength let char = style.substring(start, start + 1) || "" currentStyle += char if (currentStyle.length === length) { // 數組styles前n個元素已經全部塞入,則關閉定時器,并且執行外面傳進來的回調,進而執行下一步操作 clearInterval(timer) callback && callback() } else { let top = $stylePre.height() - MAX_HEIGHT if (top > 0) { // 當塞入的內容已經超過了容器的高度,我們需要設置一下滾動距離才方便演示接下來的內容 goBottom(top) } $style.html(currentStyle) $stylePre.html(Prism.highlight(currentStyle, Prism.languages.css)) } }, delay) }stylesEditor(簡歷樣式編輯模塊)
簡歷編輯模塊用來展示簡歷內容,主要會經歷由markdown格式往html頁面形式的轉換。
const markdownToHtml = (callback) => { $resumeMarkdown.css({ display: "none" }) $resumeWrap.addClass(iClass) $resumetag.html(marked(resumeMarkdown)) // 借助marked工具將markdown轉化為html callback && callback() // 執行后續的回調 } const showResume = (callback) => { // 原理基本上同stylesEditor, 不斷地往簡歷編輯的容器中塞入事先準備好的簡歷內容,當全部塞入的時候再關閉定時器,并執行后續的回調操作 clearInterval(timer) timer = setInterval(() => { currentMarkdown += resumeMarkdown.substring(start, start + 1) if (currentMarkdown.length === length) { clearInterval(timer) callback && callback() } else { $resumeMarkdown.html(currentMarkdown) start++ } }, delay) }app(入口模塊)
最后由app入口模塊將以上幾個模塊整合完成項目的功能,我們找出其中的核心代碼來, ?,你沒看錯,傳說中的回調地獄,亮瞎了我的狗眼啊。想必大家和我一樣都是不愿意看到這坨惡心的代碼的,但對于處理異步問題,回調又的確是一直以來的解決方案之一。
因為定時器的操作是異步行為,而我們的簡歷生成過程會涉及到多個異步操作,所以為了看到如首頁預覽鏈接的效果,必須等前一個步驟完成之后,才能執行下一步步驟,這里首先使用的回調函數的解決方案,大家可以從github上拉取代碼,分別切換以下幾個分支來查看不同的解決方案
master(使用回調函數處理)
promise(使用promise處理)
generator-thunk(使用generator + thunk函數處理)
generator-promise(使用generator + promise處理)
async(使用async處理)
showStyles(0, () => { showResume(() => { showStyles(1, () => { markdownToHtml(() => { showStyles(2) }) }) }) })解決回調地獄之promise
回調方式能夠解決異步操作問題,但是代碼寫起來非常的不美觀,可讀性差,代碼呈橫向發展趨勢...偉大的程序員們開疆擴土發明了promise的解決方案。我們來看一下promise分支中app模塊最終的寫法
showStylesWrap(0) .then(showResumeWrap) .then(showStylesWrap.bind(null, 1)) .then(markdownToHtmlWrap) .then(showStylesWrap.bind(null, 2))
可以看到,代碼清爽了很多,縱向發展,應用第一步第二步第三步...一眼就能夠看出來,當然實現的邏輯是將原來的相關的模塊用Promise包裝起來,并且在原來回調函數執行的地方resolve即可,詳細實現,歡迎查看項目源碼
解決回調地獄之generator-thunk,generator-promise兩種方式比較類似,都要用到es6中的generator。關于什么是generator,thunk函數,可以查看軟大神關于ECMAScript 6 入門,這里簡要地講述一下,其如何處理異步操作問題使得可以將異步行為寫起來如同步般爽。
function timeOut1 () { setTimeout(() => { console.log(1111) }, 1000) } function timeOut2 () { setTimeout(() => { console.log(2222) }, 200) } function * gen () { yield timeOut1() yield timeOut2() } let g = gen() g.next() g.next()
上面的代碼在過了200毫秒會log出2222,過了1秒鐘之后log出1111
這,要?了,你不是說generator寫起來同步可以解決異步問題嗎,為毛這里timeOut2沒有在timeOut1之后執行呢,畢竟gen函數中看起來是希望這樣的嘛。
其實不然,timeOut2啥時候執行取決于
g.next() g.next()
試想兩個函數幾乎同時執行,那在定時器中當然是200毫秒后的timeOut2先打印出2222來,但是有沒有辦法,讓timeOut2在timeOut1后執行呢?答案是有的
function timeOut1 () { setTimeout(() => { console.log(1111) g.next() }, 1000) } function timeOut2 () { setTimeout(() => { console.log(2222) }, 200) } function * gen () { yield timeOut1() yield timeOut2() } let g = gen() g.next()
可以看到我們在timeOut1執行完成之后,再將指針指向下一個位置,即timeOut2再去執行,這樣的結果就和gen函數中兩個yield的寫起來同步感覺一樣了。但是含有一個問題,如果涉及到很多個異步操作,我們是很難通過上面的方式將異步流程管理起來的。于是我們需要做下面一件事
function co (fn) { var gen = fn(); function next(err, data) { var result = gen.next(data); if (result.done) return; result.value(next); // thunk和promise不同地方之一在這里, promise是result.value.then(next) } next(); }
內部的next函數就是 thunk 的回調函數。next函數先將指針移到 generator 函數的下一步(gen.next方法),然后判斷 generator 函數是否結束(result.done屬性),如果沒結束,就將next函數再傳入 thunk 函數(result.value屬性),否則就直接退出。
最后我們在看一下通過co函數的寫法完成上面的例子
function timeOut1() { return (callback) => { setTimeout(() => { console.log(1111) callback() }, 1000) } } function timeOut2() { return (callback) => { setTimeout(() => { console.log(2222) callback() }, 200) } } function co(fn) { var gen = fn(); function next(err, data) { var result = gen.next(data); if (result.done) return; result.value(next); // thunk和promise不同地方之一在這里, promise是result.value.then(next) } next(); } co(function * () { yield timeOut1() yield timeOut2() })解決回調地獄之async
尾述async其實就是generator函數的語法糖。大家如果把generator弄明白了,使用它一定不再話下,關于這個項目的用法,歡迎查看async分支源代碼,這里不再贅述。
本文中可能存在闡述不當的地方,歡迎大家指正。???,最后點個贊,點個star好不好呀。
源碼地址
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/82816.html
摘要:函數式編程前端掘金引言面向對象編程一直以來都是中的主導范式。函數式編程是一種強調減少對程序外部狀態產生改變的方式。 JavaScript 函數式編程 - 前端 - 掘金引言 面向對象編程一直以來都是JavaScript中的主導范式。JavaScript作為一門多范式編程語言,然而,近幾年,函數式編程越來越多得受到開發者的青睞。函數式編程是一種強調減少對程序外部狀態產生改變的方式。因此,...
摘要:雖然有了十全的計劃,但如何高效率去記住上面那么多東西是一個大問題,看看我是怎么做的。 前言 前一篇文章講述了我在三月份毫無準備就去面試的后果,一開始心態真的爆炸,但是又不服氣,一想到每次回來后家人朋友問我面試結果的期待臉,越覺得必須付出的行動來證明自己了。 面經傳送門:一個1年工作經驗的PHP程序員是如何被面試官虐的? 下面是我花費兩個星期做的準備,主要分三部分: 有計劃——計劃好...
摘要:拿到秋招的同學,如確定入職需與用人單位簽署三方協議,以保證雙方的利益不受損失。當然每個崗位所要求的側重點不同,但卻百變不離其宗。方法論要想達成某個目標都有其特定的方法論,學習技術也不例外,掌握適當的學習方法才能事半功倍。 寫在前面的話 筆者從17年的2月份開始準備春招,其中遇到不少坑,也意識到自己走過的彎路。故寫了這篇文章總結一番,本文適合主動學習的,對自己要學的課程不明確的,對面試有...
摘要:拿到秋招的同學,如確定入職需與用人單位簽署三方協議,以保證雙方的利益不受損失。當然每個崗位所要求的側重點不同,但卻百變不離其宗。方法論要想達成某個目標都有其特定的方法論,學習技術也不例外,掌握適當的學習方法才能事半功倍。 寫在前面的話 筆者從17年的2月份開始準備春招,其中遇到不少坑,也意識到自己走過的彎路。故寫了這篇文章總結一番,本文適合主動學習的,對自己要學的課程不明確的,對面試有...
閱讀 2695·2021-10-12 10:12
閱讀 2338·2021-09-02 15:41
閱讀 2567·2019-08-30 15:55
閱讀 1402·2019-08-30 13:05
閱讀 2436·2019-08-29 11:21
閱讀 3539·2019-08-28 17:53
閱讀 3032·2019-08-26 13:39
閱讀 804·2019-08-26 11:50