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