vue.js实现会动的简历(包含底部导航功能,编辑功能)


Posted in Javascript onApril 08, 2019

在网上看到一个这样的网站,STRML 它的效果看着十分有趣,如下图所示:

vue.js实现会动的简历(包含底部导航功能,编辑功能)

这个网站是用 react.js 来写的,于是,我就想着用 vue.js 也来写一版,开始撸代码。

首先要分析打字的原理实现,假设我们定义一个字符串 str ,它等于一长串注释加 CSS 代码,并且我们看到,当 css 代码写完一个分号的时候,它写的样式就会生效。我们知道要想让一段 CSS 代码在页面生效,只需要将其放在一对 <style> 标签对中即可。比如:

<!DOCTYPE html>
<html>
<head>
 <meta charset="utf-8">
 <meta name="viewport" content="width=device-width">
 <title>JS Bin</title>
</head>
<body>
 红色字体
 <style>
 body{
  color:#f00;
 }
 </style>
</body>
</html>

你可以狠狠点击此处 具体示例 查看效果。

当看到打字效果的时候,我们不难想到,这是要使用 间歇调用(定时函数:setInterval()) 或 超时调用(延迟函数:setTimeout()) 加 递归 去模拟实现 间歇调用 。一个包含一长串代码的字符串,它是一个个截取出来,然后分别写入页面中,在这里,我们需要用到字符串的截取方法,如 slice(),substr(),substring() 等,选择用哪个截取看个人,不过需要注意它们之间的区别。好了,让我们来实现一个简单的这样打字的效果,如下:

<!DOCTYPE html>
<html>
<head>
 <meta charset="utf-8">
 <meta name="viewport" content="width=device-width">
 <title>JS Bin</title>
</head>
<body>
 <div id="result"></div>
 <script>
  var r = document.getElementById('result');
  var c = 0;
  var code = 'body{background-color:#f00;color:#fff};'
  var timer = setInterval(function(){
   c++;
   r.innerHTML = code.substr(0,c);
   if(c >= code.length){
   clearTimeout(timer);
   }
  },50)
 </script>
</body>
</html>

你可以狠狠点击此处具体示例 查看效果。好的,让我们来分析一下以上代码的原理,首先放一个用于包含代码显示的标签,然后定义一个包含代码的字符串,接着定义一个初始值为 0 的变量,为什么要定义这样一个变量呢?我们从实际效果中看到,它是一个字一个字的写入到页面中的。初始值是没有一个字符的,所以,我们就从第 0 个开始写入, c 一个字一个字的加,然后不停的截取字符串,最后渲染到标签的内容当中去,当 c 的值大于等于了字符串的长度之后,我们需要清除定时器。定时函数看着有些不太好,让我们用超时调用结合递归来实现。

<!DOCTYPE html>
<html>
<head>
 <meta charset="utf-8">
 <meta name="viewport" content="width=device-width">
 <title>JS Bin</title>
</head>
<body>
 <div id="result"></div>
 <script>
  var r = document.getElementById('result');
  var c = 0;
  var code = 'body{background-color:#f00;color:#fff};';
  var timer;
  function write(){
  c++;
  r.innerHTML = code.substr(0,c);
  if(c >= code.length && timer){
   clearTimeout(timer)
  }else{
   setTimeout(write,50);
  }
 }
 write();
 </script>
</body>
</html>

你可以狠狠点击此处具体示例 查看效果。

好了,到此为止,算是实现了第一步,让我们继续,接下来,我们要让代码保持空白和缩进,这可以使用 <pre> 标签来实现,但其实我们还可以使用css代码的 white-space 属性来让一个普通的 div 标签保持这样的效果,为什么要这样做呢,因为我们还要实现一个功能,就是编辑它里面的代码,可以让它生效。更改一下代码,如下:

<!DOCTYPE html>
<html>
<head>
 <meta charset="utf-8">
 <meta name="viewport" content="width=device-width">
 <title>JS Bin</title>
 <style>
 #result{
  white-space:pre-wrap;
  oveflow:auto;
 }
 </style>
</head>
<body>
 <div id="result"></div>
 <script>
  var r = document.getElementById('result');
  var c = 0;
  var code = `
  body{
   background-color:#f00;
   color:#fff;
  }
  `
  var timer;
  function write(){
  c++;
  r.innerHTML = code.substr(0,c);
  if(c >= code.length && timer){
   clearTimeout(timer)
  }else{
   setTimeout(write,50);
  }
 }
 write();
 </script>
</body>
</html>

你可以狠狠点击此处 具体示例 查看效果。

接下来,我们还要让样式生效,这很简单,将代码在 style 标签中写一次即可,请看:

<!DOCTYPE html>
<html>
<head>
 <meta charset="utf-8">
 <meta name="viewport" content="width=device-width">
 <title>JS Bin</title>
 <style>
 #result{
  white-space:pre-wrap;
  overflow:auto;
 }
 </style>
</head>
<body>
 <div id="result"></div>
 <style id="myStyle"></style>
 <script>
  var r = document.getElementById('result'),
   t = document.getElementById('myStyle');
  var c = 0;
  var code = `
   body{
   background-color:#f00;
   color:#fff;
   }
  `;
  var timer;
  function write(){
  c++;
  r.innerHTML = code.substr(0,c);
  t.innerHTML = code.substr(0,c);
  if(c >= code.length){
   clearTimeout(timer);
  }else{
   setTimeout(write,50);
  }
  }
  write();
 </script>
 
</body>
</html>

你可以狠狠点击此处 具体示例 查看效果。

我们看到代码还会有高亮效果,这可以用正则表达式来实现,比如以下一个 demo :

<!DOCTYPE html>
<html lang="en">

<head>
 <meta charset="UTF-8" />
 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 <meta http-equiv="X-UA-Compatible" content="ie=edge" />
 <title>代码编辑器</title>
 <style>
  * {
   margin: 0;
   padding: 0;
  }

  .ew-code {
   tab-size: 4;
   -moz-tab-size: 4;
   -o-tab-size: 4;
   margin-left: .6em;
   background-color: #345;
   white-space: pre-wrap;
   color: #f2f2f2;
   text-indent: 0;
   margin-right: 1em;
   display: block;
   overflow: auto;
   font-size: 20px;
   border-radius: 5px;
   font-style: normal;
   font-weight: 400;
   line-height: 1.4;
   font-family: Consolas, Monaco, "宋体";
   margin-top: 1em;
  }

  .ew-code span {
   font-weight: bold;
  }
 </style>
</head>

<body>
 <code class="ew-code">
  <div id="app">
   <p>{{ greeting }} world!</p>
  </div>
 </code>
 <code class="ew-code">
  //定义一个javascript对象
  var obj = { 
   greeting: "Hello," 
  }; 
  //创建一个实例
  var vm = new Vue({ 
   data: obj 
  });
  /*将实例挂载到根元素上*/
  vm.$mount(document.getElementById('app'));
 </code>
 <script>
  var lightColorCode = {
   importantObj: ['JSON', 'window', 'document', 'function', 'navigator', 'console', 'screen', 'location'],
   keywords: ['if', 'else if', 'var', 'this', 'alert', 'return', 'typeof', 'default', 'with', 'class', 'export', 'import', 'new'],
   method: ['Vue', 'React', 'html', 'css', 'js', 'webpack', 'babel', 'angular', 'bootstap', 'jquery', 'gulp','dom'],
   // special: ["*", ".", "?", "+", "$", "^", "[", "]", "{", "}", "|", "\\", "(", ")", "/", "%", ":", "=", ';']
  }
  function setHighLight(el) {
   var htmlStr = el.innerHTML;
   //匹配单行和多行注释
   var regxSpace = /(\/\/\s?[^\s]+\s?)|(\/\*(.|\s)*?\*\/)/gm,
    matchStrSpace = htmlStr.match(regxSpace),
    spaceLen;
   //匹配特殊字符
   var regxSpecial = /[`~!@#$%^&.{}()_\-+?|]/gim,
    matchStrSpecial = htmlStr.match(regxSpecial),
    specialLen;
   var flag = false;
   if(!!matchStrSpecial){
     specialLen = matchStrSpecial.length;
    }else{
     specialLen = 0;
     return;
    }
    for(var k = 0;k < specialLen;k++){
     htmlStr = htmlStr.replace(matchStrSpecial[k],'<span style="color:#b9ff01;">' + matchStrSpecial[k] + '</span>');
    }
   for (var key in lightColorCode) {
    if (key === 'keywords') {
     lightColorCode[key].forEach(function (imp) {
      htmlStr = htmlStr.replace(new RegExp(imp, 'gim'), '<span style="color:#00ff78;">' + imp + '</span>')
     })
     flag = true;
    } else if (key === 'importantObj') {
     lightColorCode[key].forEach(function (kw) {
      htmlStr = htmlStr.replace(new RegExp(kw, 'gim'), '<span style="color:#ec1277;">' + kw + '</span>')
     })
     flag = true;
    } else if (key === 'method') {
     lightColorCode[key].forEach(function (mt) {
      htmlStr = htmlStr.replace(new RegExp(mt, 'gim'), '<span style="color:#52eeff;">' + mt + '</span>')
     })
     flag = true;
    }
   }
   if (flag) {
    if (!!matchStrSpace) {
     spaceLen = matchStrSpace.length;
    } else {
     spaceLen = 0;
     return;
    }
    for(var i = 0;i < spaceLen;i++){
     var curFont;
     if(window.innerWidth <= 1200){
      curFont = '12px';
     }else{
      curFont = '14px';
     }
     htmlStr = htmlStr.replace(matchStrSpace[i],'<span style="color:#899;font-size:'+curFont+';">' + matchStrSpace[i] + '</span>');
    }
    el.innerHTML = htmlStr;
   }
  }
  var codes = document.querySelectorAll('.ew-code');
  for (var i = 0, len = codes.length; i < len; i++) {
   setHighLight(codes[i])
  }

 </script>
</body>
</html>

你可以狠狠点击此处 具体示例 查看效果。

不过这里为了方便,我还是使用插件 Prism.js ,另外在这里,我们还要用到将一个普通文本打造成 HTML 网页的插件 marked.js 。

接下来分析如何暂停动画和继续动画,很简单,就是清除定时器,然后重新调用即可。如何让编辑的代码生效呢,这就需要用到自定义事件 .sync 事件修饰符,自行查看官网 vue.js 。

虽然这里用原生 js 也可以实现,但我们用 vue-cli 结合组件的方式来实现,这样更简单一些。好了,让我们开始吧:

新建一个 vue-cli 工程(步骤自行百度):

新建一个 styleEditor.vue 组件,代码如下:

<template>
 <div class="container">
  <div class="code" v-html="codeInstyleTag"></div>
  <div class="styleEditor" ref="container" contenteditable="true" @input="updateCode($event)" v-html="highlightedCode"></div>
 </div>
</template>
<script>
 import Prism from 'prismjs'
 export default {
  name:'Editor',
  props:['code'],
  computed:{
   highlightedCode:function(){
    //代码高亮
    return Prism.highlight(this.code,Prism.languages.css);
   },
   // 让代码生效
   codeInstyleTag:function(){
    return `<style>${this.code}</style>`
   }
  },
  methods:{
   //每次打字到最底部,就要滚动
   goBottom(){
    this.$refs.container.scrollTop = 10000;
   },
   //代码修改之后,可以重新生效
   updateCode(e){
    this.$emit('update:code',e.target.textContent);
   }
  }
 }
</script>
<style scoped>
 .code{
  display:none;
 }
</style>

新建一个 resumeEditor.vue 组件,代码如下:

<template>
 <div class = "resumeEditor" :class="{htmlMode:enableHtml}" ref = "container">
  <div v-if="enableHtml" v-html="result"></div>
  <pre v-else>{{result}}</pre>
 </div>
</template>
<script>
 import marked from 'marked'
 export default {
  props:['markdown','enableHtml'],
  name:'ResumeEditor',
  computed:{
   result:function(){
    return this.enableHtml ? marked(this.markdown) : this.markdown
   }
  },
  methods:{
   goBottom:function(){
    this.$refs.container.scrollTop = 10000
   }
  }
 }
</script>
<style scoped>
 .htmlMode{
  anmation:flip 3s;
 }
 @keyframes flip{
  0%{
   opactiy:0;
  }
  100%{
   opactiy:1;
  }
 }
</style>

新建一个底部导航菜单组件 bottomNav.vue ,代码如下:

<template>
 <div id="bottom">
  <a id="pause" @click="pauseFun">{{ !paused ? '暂停动画' : '继续动画 ||' }}</a>
  <a id="skipAnimation" @click="skipAnimationFun">跳过动画</a>
  <p>
   <span v-for="(url,index) in demourl" :key="index">
    <a :href="url.url" rel="external nofollow" >{{ url.title }}</a>
   </span>
  </p>
  <div id="music" @click="musicPause" :class="playing ? 'rotate' : ''" ref="music"></div>
 </div>
</template>
<script>
 export default{
  name:'bottom',
  data(){
   return{
    demourl:[
     {url:'http://eveningwater.com/',title:'个人网站'},
     {url:'https://github.com/eveningwater',title:'github'}
    ],
    paused:false,//暂停
    playing:false,//播放图标动画
    autoPlaying:false,//播放音频
    audio:''
   }
  },
  mounted(){
   
  },
  methods:{
   // 播放音乐
   playMusic(){
    this.playing = true;
    this.autoPlaying = true;
    // 创建audio标签
    this.audio = new Audio();
    this.audio.src = "http://eveningwater.com/project/newReact-music-player/audio/%E9%BB%84%E5%9B%BD%E4%BF%8A%20-%20%E7%9C%9F%E7%88%B1%E4%BD%A0%E7%9A%84%E4%BA%91.mp3";
    this.audio.loop = 'loop';
    this.audio.autoplay = 'autoplay';
    this.$refs.music.appendChild(this.audio);
   },
   // 跳过动画
   skipAnimationFun(e){
    e.preventDefault();
    this.$emit('on-skip');
   },
   // 暂停动画
   pauseFun(e){
    e.preventDefault();
    this.paused = !this.paused;
    this.$emit('on-pause',this.paused);
   },
   // 暂停音乐
   musicPause(){
    this.playing = !this.playing;
    if(!this.playing){
     this.audio.pause();
    }else{
     this.audio.play();
    }
   }
  }
 }
</script>
<style scoped>
 #bottom{
  position:fixed;
  bottom:5px;
  left:0;
  right:0;
 }
 #bottom p{
  float:right;
 }
 #bottom a{
  text-decoration: none;
  color: #999;
  cursor:pointer;
  margin-left:5px;
 }
 #bottom a:hover,#bottom a:active{
  color: #010a11;
 }
</style>

接下来是核心 APP.vue 组件代码:

<template>
 <div id="app">
  <div class="main">
   <StyleEditor ref="styleEditor" v-bind.sync="currentStyle"></StyleEditor>
   <ResumeEditor ref="resumeEditor" :markdown = "currentMarkdown" :enableHtml="enableHtml"></ResumeEditor>
  </div>
  <BottomNav ref ="bottomNav" @on-pause="pauseAnimation" @on-skip="skipAnimation"></BottomNav>
 </div>
</template>
<script>
 import ResumeEditor from './components/resumeEditor'
 import StyleEditor from './components/styleEditor'
 import BottomNav from './components/bottomNav'
 import './assets/common.css'
 import fullStyle from './style.js'
 import my from './my.js'
 export default {
  name: 'app',
  components: {
   ResumeEditor,
   StyleEditor,
   BottomNav
  },
  data() {
   return {
    interval: 40,//写入字的速度
    currentStyle: {
     code: ''
    },
    enableHtml: false,//是否打造成HTML网页
    fullStyle: fullStyle,
    currentMarkdown: '',
    fullMarkdown: my,
    timer: null
   }
  },
  created() {
   this.makeResume();
  },
  methods: {
   // 暂停动画
   pauseAnimation(bool) {
    if(bool && this.timer){
     clearTimeout(this.timer);
    }else{
     this.makeResume();
    }
   },
   // 快速跳过动画
   skipAnimation(){
    if(this.timer){
     clearTimeout(this.timer);
    }
    let str = '';
    this.fullStyle.map((f) => {
     str += f;
    })
    setTimeout(() => {
     this.$set(this.currentStyle,'code',str);
    },100)
    this.currentMarkdown = my;
    this.enableHtml = true;
    this.$refs.bottomNav.playMusic();
   },
   // 加载动画
   makeResume: async function() {
    await this.writeShowStyle(0)
    await this.writeShowResume()
    await this.writeShowStyle(1)
    await this.writeShowHtml()
    await this.writeShowStyle(2)
    await this.$nextTick(() => {this.$refs.bottomNav.playMusic()});
   },
   // 打造成HTML网页
   writeShowHtml: function() {
    return new Promise((resolve, reject) => {
     this.enableHtml = true;
     resolve();
    })
   },
   // 写入css代码
   writeShowStyle(n) {
    return new Promise((resolve, reject) => {
     let showStyle = (async function() {
      let style = this.fullStyle[n];
      if (!style) return;
      //计算出数组每一项的长度
      let length = this.fullStyle.filter((f, i) => i <= n).map((it) => it.length).reduce((t, c) => t + c, 0);
      //当前要写入的长度等于数组每一项的长度减去当前正在写的字符串的长度
      let prefixLength = length - style.length;
      if (this.currentStyle.code.length < length) {
       let l = this.currentStyle.code.length - prefixLength;
       let char = style.substring(l, l + 1) || ' ';
       this.currentStyle.code += char;
       if (style.substring(l - 1, l) === '\n' && this.$refs.styleEditor) {
        this.$nextTick(() => {
         this.$refs.styleEditor.goBottom();
        })
       }
       this.timer = setTimeout(showStyle, this.interval);
      } else {
       resolve();
      }
     }).bind(this)
     showStyle();
    })
   },
   // 写入简历
   writeShowResume() {
    return new Promise((resolve, reject) => {
     let length = this.fullMarkdown.length;
     let showResume = () => {
      if (this.currentMarkdown.length < length) {
       this.currentMarkdown = this.fullMarkdown.substring(0, this.currentMarkdown.length + 1);
       let lastChar = this.currentMarkdown[this.currentMarkdown.length - 1];
       let prevChar = this.currentMarkdown[this.currentMarkdown.length - 2];
       if (prevChar === '\n' && this.$refs.resumeEditor) {
        this.$nextTick(() => {
         this.$refs.resumeEditor.goBottom()
        });
       }
       this.timer = setTimeout(showResume, this.interval);
      } else {
       resolve()
      }
     }
     showResume();
    })
   }
  }
 }
</script>
<style scoped>
 #app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
 }

 .main {
  position: relative;
 }

 html {
  min-height: 100vh;
 }

 * {
  transition: all 1.3s;
 }
</style>

到此为止,一个可以快速跳过动画,可以暂停动画,还有音乐播放,还能自由编辑代码的会动的简历已经完成,代码已上传至 git源码 ,欢迎 fork ,也望不吝啬 star 。

在线预览

总结

以上所述是小编给大家介绍的vue.js实现会动的简历(包含底部导航功能,编辑功能),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
JavaScript高级程序设计 阅读笔记(四) ECMAScript中的类型转换
Feb 27 Javascript
js代码实现无缝滚动(文字和图片)
Aug 20 Javascript
详解JavaScript的回调函数
Nov 20 Javascript
angular-cli修改端口号【angular2】
Apr 19 Javascript
获取本机IP地址的实例(JavaScript / Node.js)
Nov 24 Javascript
基于angular6.0实现的一个组件懒加载功能示例
Apr 12 Javascript
koa2实现登录注册功能的示例代码
Dec 03 Javascript
编写更好的JavaScript条件式和匹配条件的技巧(小结)
Jun 27 Javascript
JavaScript面向对象中接口实现方法详解
Jul 24 Javascript
Layui数据表格跳转到指定页的实现方法
Sep 05 Javascript
JS继承定义与使用方法简单示例
Feb 19 Javascript
uniapp引入支付宝原生扫码插件步骤详解
Jul 23 Javascript
微信小程序视图控件与bindtap之间的问题的解决
Apr 08 #Javascript
微信小程序实现bindtap等事件传参
Apr 08 #Javascript
详解vue中axios请求的封装
Apr 08 #Javascript
使用taro开发微信小程序遇到的坑总结
Apr 08 #Javascript
vue+element+Java实现批量删除功能
Apr 08 #Javascript
如何为你的JavaScript代码日志着色详解
Apr 08 #Javascript
详解element-ui日期时间选择器的日期格式化问题
Apr 08 #Javascript
You might like
德生PL990的分析评价
2021/03/02 无线电
PHP脚本的10个技巧(4)
2006/10/09 PHP
用PHP查询搜索引擎排名位置的代码
2010/01/05 PHP
PHP 7的一些引人注目的新特性简单介绍
2015/11/08 PHP
Yii2简单实现给表单添加验证码的方法
2016/07/18 PHP
thinkPHP5.0框架自动加载机制分析
2017/03/18 PHP
ThinkPHP框架实现导出excel数据的方法示例【基于PHPExcel】
2018/05/12 PHP
在 Laravel 项目中使用 webpack-encore的方法
2019/07/21 PHP
Thinkphp自定义生成缩略图尺寸的方法
2019/08/05 PHP
javascript对象之内置对象Math使用方法
2010/04/16 Javascript
express的中间件cookieParser详解
2014/12/04 Javascript
深入学习jQuery Validate表单验证(二)
2016/01/18 Javascript
JavaScript+html5 canvas绘制的圆弧荡秋千效果完整实例
2016/01/26 Javascript
JavaScript预解析及相关技巧分析
2016/04/21 Javascript
快速使用Bootstrap搭建传送带
2016/05/06 Javascript
总结十个Angular.js由浅入深的面试问题
2016/08/26 Javascript
微信小程序 picker-view 组件详解及简单实例
2017/01/10 Javascript
Bootstrap标签页(Tab)插件使用方法
2017/03/21 Javascript
layui radio性别单选框赋值方法
2018/08/15 Javascript
vue.js编译时给生成的文件增加版本号
2018/09/17 Javascript
用Python的Django框架编写从Google Adsense中获得报表的应用
2015/04/17 Python
Python中使用strip()方法删除字符串中空格的教程
2015/05/20 Python
基于wxpython实现的windows GUI程序实例
2015/05/30 Python
深入学习python的yield和generator
2016/03/10 Python
centos6.7安装python2.7.11的具体方法
2017/01/16 Python
Python3 集合set入门基础
2020/02/10 Python
python实现银行实战系统
2020/02/26 Python
皮尔·卡丹巴西官方商店:Pierre Cardin
2017/07/21 全球购物
中软Java笔试题
2012/11/11 面试题
英语系毕业生自荐信
2013/10/31 职场文书
设计部经理的岗位职责
2013/11/16 职场文书
优秀社区干部事迹材料
2014/02/03 职场文书
初中生思想道德自我评价
2015/03/09 职场文书
2015年班长个人工作总结
2015/04/03 职场文书
煤矿隐患排查制度
2015/08/05 职场文书
工伤调解协议书
2016/03/21 职场文书