請戳這裏看預覽css
這裏是代碼html
原理很簡單,一個閉包,逐一截取字符串,setTimeout
渲染在頁面上便可前端
/** * @param {HTMLElement} container - 渲染字符的容器 * @param {string} text - 須要渲染的字符串 */
function loadItem(container, text) {
let num = 0
let sum = text.length
let interval = 16
const startLoad = () => {
setTimeout(() => {
num += 1
if (num <= sum) {
let str = text.substr(0, num)
container.scrollTop = 100000
container.innerHTML = str
setTimeout(() => {
startLoad()
}, interval)
}
}, interval)
}
startLoad()
}
複製代碼
html
上的 CSS
字符自動生效只要在字符串開始渲染時,在 html
中添加一個 style
標籤,將渲染的 CSS
代碼寫入到標籤中便可git
建立一個 style
標籤github
function getStyleEl() {
let newStyle = document.createElement('style')
let head = document.querySelector('head')
head.appendChild(newStyle)
let allStyle = document.querySelectorAll('style')
return allStyle[allStyle.length - 1]
}
複製代碼
將 CSS
代碼寫入面試
/** * * @param {string} style - CSS 代碼 * @param {HTMLElement} el - 建立的 style 標籤 */
function handleStyle(style, el) {
el.innerHTML = style
}
複製代碼
CSS
代碼高亮,markdown
自動轉換這裏須要藉助 prismjs
和 marked
兩個代碼處理庫(固然也能夠用其餘的)typescript
須要在上述的 loadTtem
函數中添加判斷數組
let code
switch (type) {
case 'css':
handleStyle(str, styleEl)
code = Prism.highlight(str, Prism.languages.css)
break
case 'md':
code = marked(str)
break
}
複製代碼
基本的核心功能已經準備好了markdown
下面咱們開始分析過程,開始編寫代碼閉包
需求以下:
基於上述需求,咱們須要先對接口進行定義
咱們設想函數是這樣使用的
/** * @param {HTMLElement} container - 字符渲染的容器 * @param {Object} options - 動畫參數 * * @param {string} options.content.load - 須要渲染的字符串 * @param {'css' | 'md'} options.content.type - 渲染後高亮的方式,當前僅支持 'css' | 'md' 兩個參數 * @param {string} options.content.id - 渲染容器的 id * @param {boolean} options.content.rewrite - 是否須要重寫 * * @param {Object}? options.mobileAnimate - 移動端須要特殊處理 * @param {string} options.mobileAnimate.styleID - css 加載的容器 ,id 應與 content 中 css 容器的 id 相同 * @param {string} options.mobileAnimate.string - markdown 加載的容器,id 應與 content 中 md 容器的 id 相同 */
let ar = new AnimateResume(container, {
content:[
{
load:'',
type:'css',
id:'',
rewrite:'',
},
...
],
mobileAnimate:{
styleID:'',
resumeID:''
}
})
ar.animate()
ar.skip()
複製代碼
使用前須要實例化一個並傳入參數,經過 animate
方法開始動畫,skip
方法跳過動畫
根據上述參數設想,咱們能夠寫出以下的 typescript
接口,不瞭解 typescript
的同窗能夠直接跳過,只看上面代碼的註釋便可
interface Core {
container: Element
options: CoreOptions
isSkip: boolean
animate: () => void
skip: () => void
}
interface CoreOptions {
content: Array<LoadParams>
mobileAnimate?: {
styleID: string
resumeID: string
}
}
interface LoadParams {
load: string
type: 'css' | 'md'
id: string
rewrite?: boolean
}
複製代碼
基本的架構已經分析好了,如今能夠開始實現了
首先,由於動畫是多段完成的,因此咱們經過參數 content
傳入的是一個二維數組,其中每一個 item 存放着咱們想要加載的內容和對應要求,如何讓動畫一段一段的完成呢?很天然的能想到 Promise
方法,經過 Promise.then()
來實現。
因此咱們能夠將這個需求抽象爲:一個未知長度的數組,須要逐一的在未知時間後加載下一項。
實現也很簡單,代碼以下:
function load(contents) {
if (contents.length) {
this.loadItem(contents[0])
.then(() => this.load(contents.slice(1)))
}
}
複製代碼
能夠想到,上述中的 loadItem
方法應該返回一個 new Promise
,內部當字符串加載完成後返回 resolve()
,而後繼續執行下一段 load
方法
如何才能中斷當前的動畫,直接加載完成呢?
最初我嘗試直接暴力的經過在 loadItem
時檢查加載字數和一個全局變量來判斷是否 setTimeout
, 但很明顯這麼作及其不優雅,並且有 bug(但我忘了是什麼 bug 了...)。
優雅實現:在類中聲明 this.isSkip = false
(至關於全局變量),在 skip()
方法調用時,將其改變爲 true,在 loadItem
中 setTimeout
前檢查該變量,若是爲 true 則拋出 reject()
因此上述的 load
方法須要添加變爲:
function load(contents) {
if (contents.length) {
this.loadItem(contents[0])
.then(() => this.load(contents.slice(1)))
.catch(() => this.skipAnimate())
}
}
複製代碼
skipAnimate
即爲對應的跳過動畫方法
沒有動圖…… 請點擊預覽在手機或者谷歌調試中自行查看
展現樣式咱們能夠直接在渲染的 CSS
代碼動畫中自定義,因此不過多解釋
這裏只說一下兩個頁面上下滑動的效果實現
咱們須要藉助 better-scroll
插件來幫助優化,分別設置上部分頁面上拉刷新事件和下部分頁面的下拉刷新事件,在對應事件觸發時,經過 transform:translateY(x)
來實現頁面的總體滑動,代碼以下
let styleScroll = new BScroll(styleContainer, {
pullUpLoad: {
threshold: 20
}
})
let mdScroll = new BScroll(mdContainer, {
pullDownRefresh: {
threshold: 20,
}
})
styleScroll.on('pullingUp', function () {
mdContainer.style.transform = 'translateY(calc(-100% - 4rem))'
styleContainer.style.transform = 'translateY(calc(-100% - 1rem))'
styleScroll.finishPullUp()
})
mdScroll.on('pullingDown', function () {
mdContainer.style.transform = 'translateY(0)'
styleContainer.style.transform = 'translateY(0)'
mdScroll.finishPullDown()
})
複製代碼
須要注意的是若是下方簡歷內容長度不夠,不會觸發 better-scroll
的滑動檢測,致使沒法出現預想的滑動效果。
根據傳入的字符來判斷下一個字符出現的延遲時間,即 setTimeout
方法的第二個參數。
function getInterval(str: string, interval = 16): number {
if (/\D[\,]\s$/.test(str)) return interval * 20
if (/[^\/]\n\n$/.test(str)) return interval * 40
if (/[\.\?\!]\s$/.test(str)) return interval * 60
return 0
}
複製代碼
參考自 github.com/STRML/strml… ,算是拾人牙慧了。
基本的實現思路已經說完,具體的代碼貼上來實在是篇幅太長,請查看源碼。
不瞭解 typescript
的同窗能夠看這裏,這是我年初時用 js
寫的,不過算是面向過程編寫,沒有作過多的封裝處理。
第一次見到strml.net/ 時,是在初學前端大概三四個月的樣子,當時看到這樣的展示形式着實是被驚豔到了,那時仍是個小白,連 highlight
這樣的插件都不知道,更不知道還能在style
裏自定義東西,更更更不知道網站下面就放着View Source
這麼個大字,只是一心想的要本身也寫一個,就那麼硬生生本身寫正則,經過不一樣的特殊符號加載對應的標籤處理變色,再經過 `dom.style....=...' 設置樣式,而後竟然還寫的有模有樣,第一次找工做時還竟然敢拿出來給面試官看了(笑)。
年初的時候試着重寫了這個項目,感受已經是沒有什麼難度了,不過也是面向過程,一頓操做罷了。這些天初學 typescript
想着拿個什麼東西練個手,因此又把這個項目用 ts
重構了,而且進一步的進行了封裝。感受能夠出來溜溜了,因此寫下了這篇文章。