vue.js實(shí)現(xiàn)會(huì)動(dòng)的簡歷(包含底部導(dǎo)航功能,編輯功能)
在網(wǎng)上看到一個(gè)這樣的網(wǎng)站,STRML 它的效果看著十分有趣,如下圖所示:

這個(gè)網(wǎng)站是用 react.js 來寫的,于是,我就想著用 vue.js 也來寫一版,開始擼代碼。
首先要分析打字的原理實(shí)現(xiàn),假設(shè)我們定義一個(gè)字符串 str ,它等于一長串注釋加 CSS 代碼,并且我們看到,當(dāng) css 代碼寫完一個(gè)分號的時(shí)候,它寫的樣式就會(huì)生效。我們知道要想讓一段 CSS 代碼在頁面生效,只需要將其放在一對 <style> 標(biāo)簽對中即可。比如:
<!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>
你可以狠狠點(diǎn)擊此處 具體示例 查看效果。
當(dāng)看到打字效果的時(shí)候,我們不難想到,這是要使用 間歇調(diào)用(定時(shí)函數(shù):setInterval()) 或 超時(shí)調(diào)用(延遲函數(shù):setTimeout()) 加 遞歸 去模擬實(shí)現(xiàn) 間歇調(diào)用 。一個(gè)包含一長串代碼的字符串,它是一個(gè)個(gè)截取出來,然后分別寫入頁面中,在這里,我們需要用到字符串的截取方法,如 slice(),substr(),substring() 等,選擇用哪個(gè)截取看個(gè)人,不過需要注意它們之間的區(qū)別。好了,讓我們來實(shí)現(xiàn)一個(gè)簡單的這樣打字的效果,如下:
<!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>
你可以狠狠點(diǎn)擊此處具體示例 查看效果。好的,讓我們來分析一下以上代碼的原理,首先放一個(gè)用于包含代碼顯示的標(biāo)簽,然后定義一個(gè)包含代碼的字符串,接著定義一個(gè)初始值為 0 的變量,為什么要定義這樣一個(gè)變量呢?我們從實(shí)際效果中看到,它是一個(gè)字一個(gè)字的寫入到頁面中的。初始值是沒有一個(gè)字符的,所以,我們就從第 0 個(gè)開始寫入, c 一個(gè)字一個(gè)字的加,然后不停的截取字符串,最后渲染到標(biāo)簽的內(nèi)容當(dāng)中去,當(dāng) c 的值大于等于了字符串的長度之后,我們需要清除定時(shí)器。定時(shí)函數(shù)看著有些不太好,讓我們用超時(shí)調(diào)用結(jié)合遞歸來實(shí)現(xiàn)。
<!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>
你可以狠狠點(diǎn)擊此處具體示例 查看效果。
好了,到此為止,算是實(shí)現(xiàn)了第一步,讓我們繼續(xù),接下來,我們要讓代碼保持空白和縮進(jìn),這可以使用 <pre> 標(biāo)簽來實(shí)現(xiàn),但其實(shí)我們還可以使用css代碼的 white-space 屬性來讓一個(gè)普通的 div 標(biāo)簽保持這樣的效果,為什么要這樣做呢,因?yàn)槲覀冞€要實(shí)現(xiàn)一個(gè)功能,就是編輯它里面的代碼,可以讓它生效。更改一下代碼,如下:
<!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>
你可以狠狠點(diǎn)擊此處 具體示例 查看效果。
接下來,我們還要讓樣式生效,這很簡單,將代碼在 style 標(biāo)簽中寫一次即可,請看:
<!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>
你可以狠狠點(diǎn)擊此處 具體示例 查看效果。
我們看到代碼還會(huì)有高亮效果,這可以用正則表達(dá)式來實(shí)現(xiàn),比如以下一個(gè) 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">
//定義一個(gè)javascript對象
var obj = {
greeting: "Hello,"
};
//創(chuàng)建一個(gè)實(shí)例
var vm = new Vue({
data: obj
});
/*將實(shí)例掛載到根元素上*/
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>
你可以狠狠點(diǎn)擊此處 具體示例 查看效果。
不過這里為了方便,我還是使用插件 Prism.js ,另外在這里,我們還要用到將一個(gè)普通文本打造成 HTML 網(wǎng)頁的插件 marked.js 。
接下來分析如何暫停動(dòng)畫和繼續(xù)動(dòng)畫,很簡單,就是清除定時(shí)器,然后重新調(diào)用即可。如何讓編輯的代碼生效呢,這就需要用到自定義事件 .sync 事件修飾符,自行查看官網(wǎng) vue.js 。
雖然這里用原生 js 也可以實(shí)現(xiàn),但我們用 vue-cli 結(jié)合組件的方式來實(shí)現(xiàn),這樣更簡單一些。好了,讓我們開始吧:
新建一個(gè) vue-cli 工程(步驟自行百度):
新建一個(gè) 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:{
//每次打字到最底部,就要滾動(dòng)
goBottom(){
this.$refs.container.scrollTop = 10000;
},
//代碼修改之后,可以重新生效
updateCode(e){
this.$emit('update:code',e.target.textContent);
}
}
}
</script>
<style scoped>
.code{
display:none;
}
</style>
新建一個(gè) 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>
新建一個(gè)底部導(dǎo)航菜單組件 bottomNav.vue ,代碼如下:
<template>
<div id="bottom">
<a id="pause" @click="pauseFun">{{ !paused ? '暫停動(dòng)畫' : '繼續(xù)動(dòng)畫 ||' }}</a>
<a id="skipAnimation" @click="skipAnimationFun">跳過動(dòng)畫</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:'個(gè)人網(wǎng)站'},
{url:'https://github.com/eveningwater',title:'github'}
],
paused:false,//暫停
playing:false,//播放圖標(biāo)動(dòng)畫
autoPlaying:false,//播放音頻
audio:''
}
},
mounted(){
},
methods:{
// 播放音樂
playMusic(){
this.playing = true;
this.autoPlaying = true;
// 創(chuàng)建audio標(biāo)簽
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);
},
// 跳過動(dòng)畫
skipAnimationFun(e){
e.preventDefault();
this.$emit('on-skip');
},
// 暫停動(dòng)畫
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網(wǎng)頁
fullStyle: fullStyle,
currentMarkdown: '',
fullMarkdown: my,
timer: null
}
},
created() {
this.makeResume();
},
methods: {
// 暫停動(dòng)畫
pauseAnimation(bool) {
if(bool && this.timer){
clearTimeout(this.timer);
}else{
this.makeResume();
}
},
// 快速跳過動(dòng)畫
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();
},
// 加載動(dòng)畫
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網(wǎng)頁
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;
//計(jì)算出數(shù)組每一項(xiàng)的長度
let length = this.fullStyle.filter((f, i) => i <= n).map((it) => it.length).reduce((t, c) => t + c, 0);
//當(dāng)前要寫入的長度等于數(shù)組每一項(xiàng)的長度減去當(dāng)前正在寫的字符串的長度
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>
到此為止,一個(gè)可以快速跳過動(dòng)畫,可以暫停動(dòng)畫,還有音樂播放,還能自由編輯代碼的會(huì)動(dòng)的簡歷已經(jīng)完成,代碼已上傳至 git源碼 ,歡迎 fork ,也望不吝嗇 star 。
總結(jié)
以上所述是小編給大家介紹的vue.js實(shí)現(xiàn)會(huì)動(dòng)的簡歷(包含底部導(dǎo)航功能,編輯功能),希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
vue中內(nèi)嵌iframe的src更新頁面未刷新問題及解決
這篇文章主要介紹了vue中內(nèi)嵌iframe的src更新頁面未刷新問題及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12
vite+vue3+tsx項(xiàng)目打包后動(dòng)態(tài)路由無法加載頁面的問題及解決
這篇文章主要介紹了vite+vue3+tsx項(xiàng)目打包后動(dòng)態(tài)路由無法加載頁面的問題及解決,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03
vue里的axios如何獲取本地json數(shù)據(jù)
這篇文章主要介紹了vue里的axios如何獲取本地json數(shù)據(jù),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08
vue插件開發(fā)之使用pdf.js實(shí)現(xiàn)手機(jī)端在線預(yù)覽pdf文檔的方法
這篇文章主要介紹了vue插件開發(fā)之使用pdf.js實(shí)現(xiàn)手機(jī)端在線預(yù)覽pdf文檔的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-07-07
Vue3.0實(shí)現(xiàn)圖片預(yù)覽組件(媒體查看器)功能
最近項(xiàng)目中有個(gè)場景,一組圖片、視頻、音頻、文件數(shù)據(jù),要求點(diǎn)擊圖片可以放大預(yù)覽,左右可以切換音視頻、文件,支持鼠標(biāo)及各種鍵控制?縮放,左右旋轉(zhuǎn),移動(dòng)等功能,這篇文章主要介紹了Vue3.0實(shí)現(xiàn)圖片預(yù)覽組件(媒體查看器),需要的朋友可以參考下2023-12-12
VueTreeselect?參數(shù)options的數(shù)據(jù)轉(zhuǎn)換-參數(shù)normalizer解析
這篇文章主要介紹了VueTreeselect?參數(shù)options的數(shù)據(jù)轉(zhuǎn)換-參數(shù)normalizer解析,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07

