编写会动的简历
今天跟着方老师做了一个会动的简历,思路就是通过 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 方面耗费了点时间...
行文不畅,这个..这个..这个我也在慢慢改进,我之前还在想着,是把做这个东西的整个流程都记录下来,还是只挑其中比较重要的知识点,写着写着,还是写下了含糊不清的这篇东西...不过也算是有所收获,多总结总结还是不亏的~