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 相关文章推荐
用脚本调用样式的几种方法
Dec 09 Javascript
Google Map API更新实现用户自定义标注坐标
Jul 29 Javascript
jquery及原生js获取select下拉框选中的值示例
Oct 25 Javascript
JQuery1.8 判断元素是否绑定事件的方法
Jul 10 Javascript
javascript学习笔记(五)原型和原型链详解
Oct 08 Javascript
JS中FRAME的操作问题实例分析
Oct 21 Javascript
BootStrap实现带有增删改查功能的表格(DEMO详解)
Oct 26 Javascript
Bootstrap基本插件学习笔记之Tooltip提示工具(18)
Dec 08 Javascript
jQuery快速高效制作网页交互特效
Feb 24 Javascript
微信小程序页面传值实例分析
Apr 19 Javascript
微信小程序实现折叠面板
Jan 31 Javascript
在Layui中操作数据表格,给指定单元格添加事件示例
Oct 26 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
提升PHP速度全攻略
2006/10/09 PHP
PHP去掉从word直接粘贴过来的没有用格式的函数
2012/10/29 PHP
PHP 访问数据库配置通用方法(json)
2018/05/20 PHP
用JavaScript获取网页中的js、css、Flash等文件
2006/12/20 Javascript
javascript 清空form表单中某种元素的值
2009/12/26 Javascript
Javascript学习笔记一 之 数据类型
2010/12/15 Javascript
JS去除字符串两端空格的简单实例
2013/12/27 Javascript
小米公司JavaScript面试题
2014/12/29 Javascript
JS获取网页图片name属性的方法
2015/04/01 Javascript
javascript实现输出指定行数正方形图案的方法
2015/08/03 Javascript
JS实现密码框根据焦点的获取与失去控制文字的消失与显示效果
2015/11/26 Javascript
javascript时间戳和日期字符串相互转换代码(超简单)
2016/06/22 Javascript
AngularJS全局scope与Isolate scope通信用法示例
2016/11/22 Javascript
使用travis-ci如何持续部署node.js应用详解
2017/07/30 Javascript
jquery树形插件zTree高级使用详解
2019/08/16 jQuery
Vue v-for循环之@click点击事件获取元素示例
2019/11/09 Javascript
jQuery操作动画完整实例分析
2020/01/10 jQuery
Python使用代理抓取网站图片(多线程)
2014/03/14 Python
跟老齐学Python之使用Python查询更新数据库
2014/11/25 Python
python以环状形式组合排列图片并输出的方法
2015/03/17 Python
使用Python的Django和layim实现即时通讯的方法
2018/05/25 Python
对python添加模块路径的三种方法总结
2018/10/16 Python
树莓派使用python-librtmp实现rtmp推流h264的方法
2019/07/22 Python
python multiprocessing模块用法及原理介绍
2019/08/20 Python
Python常用base64 md5 aes des crc32加密解密方法汇总
2020/11/06 Python
css3学习心得分享
2013/08/19 HTML / CSS
土耳其时尚潮流在线购物网站:Trendyol
2017/10/10 全球购物
美国首屈一指的礼品篮供应商:GiftTree
2018/01/06 全球购物
Michael Kors澳大利亚官网:世界知名的奢侈饰品和成衣设计师
2020/02/13 全球购物
几个Shell Script面试题
2012/08/31 面试题
幼儿教师研修感言
2014/02/12 职场文书
交通文明倡议书
2014/05/16 职场文书
竞聘自述材料
2014/08/25 职场文书
2014年幼儿园教研工作总结
2014/12/04 职场文书
Django使用redis配置缓存的方法
2021/06/01 Redis
NASA 机智号火星直升机拍到了毅力号设备碎片
2022/04/29 数码科技