用原生js写一个"多动症"的简历
用原生js写一个"多动症"的简历
最近在知乎上看到@方应杭用vue写了一个会动的简历,觉得挺好玩的,研究一下其实现思路,决定试试用原生js来实现。
会动的简历实现思路
这张会
动
的简历,就好像一个打字员在不断地录入文字,页面呈现动态效果。又好像一个早已经录制好影片,而我们只是坐在放映机前观看。
原理分两个部分
页面能看见的不断跳动着的增加的文字,由innerHTML控制
页面的布局效果由藏在"背后的"
style
标签完成
想象一下你要往一张网页每间隔0.1秒增加一个啊
字,是不是开个定时器,间断地往body里面塞啊
,就可以啊!没错,做到这一步就完成了原理的第一部分
再想象一下,在往页面里面塞啊
的时候,我还想改变啊字的字体颜色以及网页背景颜色,那应该怎么做呢,是不是执行下面的代码就可以呢,没错,只不过更改字体和背景色不是突然改变的,而是也是开个定时器,间断地往style
标签中塞入以下代码,这样就完成了原理的第二步,是不是好简单 ???, 接下来让我们一步步完成它
.xxx{
color: blue;
background: red;
}
项目搭建
在这个项目中我们
使用webpack2来完成项目的构建
使用yarn来处理依赖包的管理
使用es6的写法
使用部分原生dom操作api
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上拉取代码,分别切换以下几个分支来查看不同的解决方案
master(使用回调函数处理)
promise(使用promise处理)
generator-thunk(使用generator + thunk函数处理)
generator-promise(使用generator + promise处理)
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好不好呀。
本文转载于:猿2048https://www.mk2048.com/blog/blog.php?id=hjiiki0hi0j
用原生js写一个"多动症"的简历的更多相关文章
- 用原生JS写一个网页版的2048小游戏(兼容移动端)
这个游戏JS部分全都是用原生JS代码写的,加有少量的CSS3动画,并简单的兼容了一下移动端. 先看一下在线的demo:https://yuan-yiming.github.io/2048-online ...
- 原生js写一个无缝轮播图插件(支持vue)
轮播图插件(Broadcast.js) 前言:写这个插件的原因 前段时间准备用vue加上网易云的nodejs接口,模拟网易云音乐移动端.因为想自己写一遍所有的代码以及加固自己的flex布局,所以没有使 ...
- 原生 js 写分页
欢迎留言或者加本人QQ172360937咨询 这段代码是用原生 js 写的一个分页的效果 <!doctype html> <html lang="en"> ...
- 用原生JS写移动动画案例及实际应用
js很强大 相信很多人都知道,那么它有哪些强大之处呢?有兴趣的人可以去查查,这里就不赘述了,因为不在本片文章讨论的范围. 我们要讲的是怎么用原生JS写移动动画?我们先举一个最简单的动画例子,很多网站的 ...
- 原生js写Ajax
//原生js写ajax就像打电话 //打电话分下面4步//1.拿出手机//2.拨号//3.说话//4.挺对方说话 //ajax也分下面4步//1.创建ajax对象//2.连接到服务器//3.发送请求( ...
- 原生JS写的ajax函数
参照JQuery中的ajax功能,用原生JS写了一个ajax,功能相对JQuery要少很多,不过基本功能都有,包括JSONP. 调用的方式分为两种: 1. ajax(url, {}); 2. ajax ...
- 前端与编译原理——用JS写一个JS解释器
说起编译原理,印象往往只停留在本科时那些枯燥的课程和晦涩的概念.作为前端开发者,编译原理似乎离我们很远,对它的理解很可能仅仅局限于"抽象语法树(AST)".但这仅仅是个开头而已.编 ...
- 使用JS写一个计算器
先上效果图: 简单的加减乘除功能还是有的,所以我们就考虑怎么来实现这个功能. 根据预期效果,可以确定页面中的布局要用到table tr td. 所以先放上页面布局,table的边框宽度border,c ...
- 原生js写的贪吃蛇网页版游戏特效
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <bo ...
随机推荐
- 爬虫之爬取B站视频及破解知乎登录方法(进阶)
今日内容概要 爬虫思路之破解知乎登录 爬虫思路之破解红薯网小说 爬取b站视频 Xpath选择器 MongoDB数据库 爬取b站视频 """ 爬取大的视频网站资源的时候,一 ...
- 【实测】Python 和 C++ 下字符串查找的速度对比
完整格式链接:https://blog.imakiseki.cf/2022/03/07/techdev/python-cpp-string-find-perf-test/ 背景 最近在备战一场算法竞赛 ...
- SQL从零到迅速精通【实用函数(3)】
1.LOWER()函数 使用LOWER函数将字符串中所有字幕字符转换为小写,输入语句如下. SELECT LOWER('BEAUTIFUL'),LOWER('Well'); 2.UPPER()函数 S ...
- 解决 Vue 项目 invalid host header 问题(两种方案)
问题出现背景 做微信H5网页时,使用花生壳内网穿透进行调试时,打开网页显示:invalid host header 分析问题 这句话的意思是:无效的Host请求头: 因为在vue在调试时相当于启动了一 ...
- Python模板引擎Jinja2使用简介
原文链接 背景 最近在项目开发中,需要针对 Jenkins 项目进行配置,Jenkins 的 job 配置采用的是 xml,在维护配置模板的过程中就遇到了问题,因为逐步发现配置灵活性超出了字符串的范畴 ...
- 笔记软件-Obsidian(相关资料分享)
Obsidian(黑曜石) 是一个功能强大的知识管理软件,是一款功能强大的带有关系图谱功能的双向链笔记,它可基于纯文本Markdown文件的本地文件夹上运行 Obsidian是一个支持markdown ...
- maven——使用阿里云镜像
1.在本地的仓库目录下找到settings.xml文件,添加 <mirrors> <mirror> <id>alimaven</id> <name ...
- Serialzers 序列化组件
Serialzers 序列化组件 前言 当做前后端分离的项目时,JSON是一个轻量级的数据交互格式.所有我们给前端数据的时候都要转成json格式,那就需要对我们从数据库拿到数据进行序列化 Django ...
- 6月5日 python复习 模块
"""1. os和sys都是干什么的?2. 你工作中都用过哪些内置模块?3. 有没有用过functools模块?"""1. os 系统相关 ...
- systemd --user进程CPU占用高问题分析
1.问题由来 近期发现堡垒机环境有如下问题,systemd占用大量cpu: 原文链接:https://www.cnblogs.com/yaohong/p/16046670.html 2.问题定位 2. ...