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 相关文章推荐
JS 密码强度验证(兼容IE,火狐,谷歌)
Mar 15 Javascript
jQuery Validation实例代码 让验证变得如此容易
Oct 18 Javascript
在多个页面使用同一个HTML片段《续》
Mar 04 Javascript
使用Node.js实现HTTP 206内容分片的教程
Jun 23 Javascript
javascript禁止超链接跳转的方法
Feb 02 Javascript
浅谈JavaScript的push(),pop(),concat()方法
Jun 03 Javascript
jQuery序列化后的表单值转换成Json
Jun 16 jQuery
JS实现延迟隐藏功能的方法(类似QQ头像鼠标放上展示信息)
Dec 28 Javascript
react redux入门示例
Apr 19 Javascript
解决vue2 在mounted函数无法获取prop中的变量问题
Nov 15 Javascript
electron制作仿制qq聊天界面的示例代码
Nov 26 Javascript
Vue开发中常见的套路和技巧总结
Nov 24 Vue.js
微信小程序视图控件与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
全国FM电台频率大全 - 15 山东省
2020/03/11 无线电
Php+SqlServer实现分页显示
2006/10/09 PHP
php中ob(Output Buffer 输出缓冲)函数使用方法
2007/07/21 PHP
PHP判断一个字符串是否是回文字符串的方法
2015/03/23 PHP
浅谈Coreseek、Sphinx-for-chinaese、Sphinx+Scws的区别
2016/12/15 PHP
PHP实现将多个文件中的内容合并为新文件的方法示例
2017/06/10 PHP
通过PHP的Wrapper无缝迁移原有项目到新服务的实现方法
2020/04/02 PHP
利用Ext Js生成动态树实例代码
2008/09/08 Javascript
网站页面自动跳转实现方法PHP、JSP(上)
2010/08/01 Javascript
基于JQuery的日期联动实现代码
2011/02/24 Javascript
js实现拖拽 闭包函数详细介绍
2012/11/25 Javascript
如何使用jQuery Draggable和Droppable实现拖拽功能
2013/07/05 Javascript
jquery 快速回到页首的方法
2013/12/05 Javascript
jQuery显示和隐藏 常用的状态判断方法
2015/01/29 Javascript
JS实现让访问者自助选择网页文字颜色的方法
2015/02/24 Javascript
JavaScript操作select元素和option的实例代码
2016/01/29 Javascript
基于JS实现类似支付宝支付密码输入框
2016/09/02 Javascript
js 公式编辑器 - 自定义匹配规则 - 带提示下拉框 - 动态获取光标像素坐标
2018/01/04 Javascript
vue利用axios来完成数据的交互
2018/03/23 Javascript
vue中slot(插槽)的介绍与使用
2018/11/12 Javascript
vue实现购物车选择功能
2020/01/10 Javascript
详细分析Node.js 模块系统
2020/06/28 Javascript
vue cli3.0打包上线静态资源找不到路径的解决操作
2020/08/03 Javascript
微信小程序开发数据缓存基础知识辨析及运用实例详解
2020/11/06 Javascript
Django中几种重定向方法
2015/04/28 Python
Python中的自省(反射)详解
2015/06/02 Python
详解字典树Trie结构及其Python代码实现
2016/06/03 Python
浅谈用Python实现一个大数据搜索引擎
2017/11/28 Python
加拿大最大的五金、家居装修和园艺产品商店:RONA
2017/01/27 全球购物
软件测试工程师笔试题带答案
2015/03/27 面试题
经典的毕业生自荐信范文
2014/04/14 职场文书
捐助贫困学生倡议书
2014/05/16 职场文书
党性教育心得体会
2014/09/03 职场文书
个人欠条范本
2015/07/03 职场文书
怎样写好演讲稿题目?
2019/08/21 职场文书
导游词之西安大清真寺
2019/12/17 职场文书