用原生js寫一個"多動症"的簡歷

用原生js寫一個"多動症"的簡歷

預覽地址
源碼地址javascript

最近在知乎上看到@方應杭用vue寫了一個會動的簡歷,以爲挺好玩的,研究一下其實現思路,決定試試用原生js來實現。css

http://odssgnnpf.bkt.clouddn.com/return.gif

會動的簡歷實現思路

這張會的簡歷,就好像一個打字員在不斷地錄入文字,頁面呈現動態效果。又好像一個早已經錄製好影片,而咱們只是坐在放映機前觀看。html

原理分兩個部分vue

  1. 頁面能看見的不斷跳動着的增長的文字,由innerHTML控制java

  2. 頁面的佈局效果由藏在"背後的"style標籤完成webpack

想象一下你要往一張網頁每間隔0.1秒增長一個字,是否是開個定時器,間斷地往body裏面塞,就能夠啊!沒錯,作到這一步就完成了原理的第一部分git

再想象一下,在往頁面裏面塞的時候,我還想改變啊字的字體顏色以及網頁背景顏色,那應該怎麼作呢,是否是執行下面的代碼就能夠呢,沒錯,只不過更改字體和背景色不是忽然改變的,而是也是開個定時器,間斷地往style標籤中塞入如下代碼,這樣就完成了原理的第二步,是否是好簡單 ???, 接下來讓咱們一步步完成它程序員

.xxx{
  color: blue;
  background: red; 
}

項目搭建

在這個項目中咱們es6

  1. 使用webpack2來完成項目的構建github

  2. 使用yarn來處理依賴包的管理

  3. 使用es6的寫法

  4. 使用部分原生dom操做api

  5. standard.js(代碼風格約束利器)

目錄結構以下

目錄結構

最重要的幾個模塊分別是resumeEditor(簡歷編輯模塊)stylesEditor(簡歷樣式編輯模塊)以及vQuery(封裝的dom操做模塊)
最後app.js(入口模塊)再將幾個模塊的功能結合起來完成整個項目。

vQuery(封裝的dom操做模塊)

由於後面的幾個模塊都要依賴這個小模塊,因此咱們先簡單的看下。

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上拉取代碼,分別切換如下幾個分支來查看不一樣的解決方案

  1. master(使用回調函數處理)

  2. promise(使用promise處理)

  3. generator-thunk(使用generator + thunk函數處理)

  4. generator-promise(使用generator + promise處理)

  5. 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好很差呀。

源碼地址

相關文章
相關標籤/搜索