編寫會動的簡歷
今天跟着方老師做了一個會動的簡歷,思路就是通過 JavaScript 代碼,利用定時器每次同時在 HTML 和 CSS 中輸入固定的字符達到實時代碼預覽的效果,其中用到了 prism.js 庫給代碼添加高亮,用了 marked.js 庫把 markdown 轉換成 HTML ,並在頁面中展示出來。
效果預覽:Git Pages
代碼鏈接:GitHub
是不是看上去覺得效果挺炫的~下面我就來整理一下這個項目的流程,思路以及踩過的坑。感覺好長好多涉及的點要記...應該分幾篇寫還是合成一篇?...算了先寫着先吧。
分別設置 HTML 和 CSS
先寫個 demo,可以讓代碼出現在頁面上:
可以看到,我們指定的字符串已經出現在頁面上了,但是,出現了兩個問題:
1.頁面背景色並沒有發生變化
2.輸出字符串中的空格被壓縮了
解決方法:
1.當然沒有啊,我們只是把字符串寫進 HTML 中,CSS 中還是沒有這些設置
2.我們使用 pre 包裹住字符串,讓他的空格得以保留
修改一下:把字符串寫入 HTML 中的同時,把字符串也寫進 CSS 中;使用 pre 標簽。
使用 setInterval 和 substring 進行逐個字符輸出
思路:使用 substring 獲取字符串的某個部分,使用 setInterval 每次輸入固定字符,並做判斷,當獲取的字符串下標超過字符串長度時,setInterval 停止。
使用 prism.js 設置 CSS 高亮
由於 JSbin 不能上傳文件,所以這里往下就不用 JSbin 演示了。
使用 prism.js 庫給字體做高亮(沒錯,還是使用 CRM 大法~):
1.下載 prism.js 的 CSS 和 JS 文件
2.引入文件到項目中
3.copy.run.modify
var result = `
body{
background-color: red;
}
`
var n = 0
var timer = setInterval(()=>{
n+=1
code.innerHTML = Prism.highlight(result.substring(0,n), Prism.languages.css)
styleTag.innerHTML = result.substring(0,n)
if(n>=result.length){
window.clearInterval(timer)
}
},100)
more style
給代碼設置更多的樣式,包括通過使用 animation 營造呼吸效果,調整代碼框大小等等:
var result = `
/*
* 面試官你好,我是XXX
* 只用文字作做我介紹太單調了
* 我就用代碼來介紹吧
* 首先准備一些樣式
*
*/
* {
transition: all 1s;
}
html {
font-size: 16px;
}
.code-wrapper {
width: 50%;
left: 0;
position: fixed;
height: 100%;
}
/* 調整一下代碼框大小 */
#code {
border:1px solid transparent;
padding: 16px;
overflow: hidden;
}
#code {
left: 0;
width: 100%;
height:100%;
}
/* 讓代碼呼吸起來 */
#code{
animation: breathe 1s infinite alternate-reverse;
}
/* 給代碼加上一點點高亮 */
.token.comment {
color: slategray;
}
.token.property {
color: #f92672;
}
.token.selector {
color: #a6e22e;
}
`
另外,設置代碼高亮有一個小竅門,即我們先設置一個 default.css,把高亮的 CSS 隱藏起來(放到 prism.css 后面),然后再通過 setInterval 把高亮代碼設置回來,起到更好的視覺效果。
創建白板
左邊是負責代碼輸出展示,右邊是我們最后要寫入 Markdown 的地方,所以我們要創建一個函數,創建一塊白板出來:
function createPaper(fn){
var paper = document.createElement('div')
paper.id = "paper"
var content = document.createElement('pre')
content.className = "content"
paper.appendChild(content)
document.body.appendChild(paper)
fn.call()
}
回調初現
在創建白板的時候,輸入字符串的任務要停下來,之后再繼續輸入字符串的任務。
現在封裝一下之前的函數:
function writeCode(prefix,code){
let domCode = document.querySelector('#code')
domCode.innerHTML = prefix || ''
let n = 0
let timer = setInterval(()=>{
n = n+1
domCode.innerHTML = Prism.highlight(prefix + code.substring(0,n), Prism.languages.css)
//這句話的作用是:當代碼在被輸入到HTML中時,代碼框的滾動條能一直保持在最下方
domCode.scrollTop = domCode.scrollHeight
styleTag.innerHTML = prefix + code.substring(0,n)
if(n >= code.length){
window.clearInterval(timer)
}
},20)
}
prefix 和 code 分別對應第一次需要輸入的字符串和本次需要輸入的字符串,如果沒有 prefix,則之前的 innerHTML 則會被新的字符串取代,而不是連起來。
所以我們這里的流程應該是:writeCode('',result) -> 添加白板 createPaper() -> 設置白板樣式 -> 在白板添加 Markdown
那我們這樣寫行不行呢:
writeCode('',result)
createPaper()
writeMarkdown()
很遺憾,這樣是不行的,因為 writeCode() 中有 setInterval 計時器,所以他是一個異步函數,實際上流程就變成了這樣:
添加白板 createPaper() ? writeCode('',result) ? 設置白板樣式 ? 在白板添加 Markdown
下面先插播一下我對於 異步與回調 的理解。
異步與回調
- 異步就是不等待結果的函數/事件
如:
function setTime(fn){
setTimeout(()=>{
console.log(2)
fn.call()
},1000)
}
function cb(){
console.log(3)
}
setTime(cb)
console.log(1)
按照代碼順序本來是先執行 setTime(cb),再執行 console.log(1),但由於 setTime(cb) 是異步事件,不用等待他執行完成之后才執行后續代碼,所以 console.log(1) 就先執行了,1S 后才執行setTime(cb) 的內容。
- 回調是拿到異步結果的一種方式。
function setTime(fn){
setTimeout(()=>{
console.log(2)
fn.call()
},1000)
}
function cb(){
console.log(‘異步任務執行完成,回調結束’)
}
setTime(cb)
如這段代碼中,函數 cb() 就被當做 回調函數 傳入 setTime() 中,當 setTime() 中的內容執行完之后,就執行函數 cb() 。
注意
同步事件/函數 也可以使用回調
回到正題
所以我們應該使用回調函數,在writeCode() 結束的時候執行 createPaper()
function writeCode(prefix,code,fn){
let domCode = document.querySelector('#code')
domCode.innerHTML = prefix || ''
let n = 0
let timer = setInterval(()=>{
n = n+1
domCode.innerHTML = Prism.highlight(prefix + code.substring(0,n), Prism.languages.css)
domCode.scrollTop = domCode.scrollHeight
styleTag.innerHTML = prefix + code.substring(0,n)
if(n >= code.length){
window.clearInterval(timer)
fn.call()
}
},20)
}
writeCode('',result,()=>{
createPaper()
})
輸入 Markdown
因為所要輸入的地方不同,所以另寫一個函數,輸入 markdown:
function writeMarkdown(markdown,fn){
let domMarkdown = document.querySelector('#paper .content')
let n = 0
let timer = setInterval(()=>{
n = n+1
domMarkdown.innerHTML = markdown.substring(0,n)
domMarkdown.scrollTop = domMarkdown.scrollHeight
if(n >= markdown.length){
window.clearInterval(timer)
fn.call()
}
},20)
}
var result4 = `
/* 還差一點點 */
.markdown-body {
padding: 16px;
background-color: white;
overflow: auto;
}
/* Done~ 簡歷完成啦~ */
`
var md = `
# 簡歷
個人簡歷
...
`
// 實際上就變成了:
writeCode('',result,()=>{
createPaper(()=>{
writeMarkdown(md)
})
})
最后把 Markdown 轉換成 HTML
我們選用了比較討巧的方法:新建一個 div,然后把 div 的樣式設置好(主要用了github-markdown-css),div 內容設置成經過 marked.js 庫(怎么使用在此不贅述了)處理后的 HTML,最后用這個 div 替換掉之前的白板。
function convertMarkdownToHtml(fn){
var div = document.createElement('div')
div.className = 'html markdown-body'
div.innerHTML = marked(md)
let markdownContainer = document.querySelector('#paper > .content')
markdownContainer.style = 'background-color:white'
markdownContainer.replaceWith(div)
fn && fn.call()
}
// 實際上最后的流程就變成了:
writeCode('',result,()=>{
createPaper(()=>{
writeCode(result,result2,()=>{
writeMarkdown(md,()=>{
writeCode(result + result2,result3,()=>{
convertMarkdownToHtml(()=>{
writeCode(result + result2 + result3,result4,()=>{
console.log('Done')
})
})
})
})
})
})
})
// 哈哈,恐怖吧?傳說中的回調地獄
總結
縱觀整個項目下來,感覺異步和回調不難,反而在 CSS 方面耗費了點時間...
行文不暢,這個..這個..這個我也在慢慢改進,我之前還在想着,是把做這個東西的整個流程都記錄下來,還是只挑其中比較重要的知識點,寫着寫着,還是寫下了含糊不清的這篇東西...不過也算是有所收獲,多總結總結還是不虧的~