vue.js實(shí)現(xiàn)會(huì)動(dòng)的簡(jiǎn)歷(包含底部導(dǎo)航功能,編輯功能)
在網(wǎng)上看到一個(gè)這樣的網(wǎng)站,STRML 它的效果看著十分有趣,如下圖所示:
這個(gè)網(wǎng)站是用 react.js 來(lái)寫(xiě)的,于是,我就想著用 vue.js 也來(lái)寫(xiě)一版,開(kāi)始擼代碼。
首先要分析打字的原理實(shí)現(xiàn),假設(shè)我們定義一個(gè)字符串 str ,它等于一長(zhǎng)串注釋加 CSS 代碼,并且我們看到,當(dāng) css 代碼寫(xiě)完一個(gè)分號(hào)的時(shí)候,它寫(xiě)的樣式就會(huì)生效。我們知道要想讓一段 CSS 代碼在頁(yè)面生效,只需要將其放在一對(duì) <style> 標(biāo)簽對(duì)中即可。比如:
<!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è)包含一長(zhǎng)串代碼的字符串,它是一個(gè)個(gè)截取出來(lái),然后分別寫(xiě)入頁(yè)面中,在這里,我們需要用到字符串的截取方法,如 slice(),substr(),substring()
等,選擇用哪個(gè)截取看個(gè)人,不過(guò)需要注意它們之間的區(qū)別。好了,讓我們來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎ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 = setInterval(function(){ c++; r.innerHTML = code.substr(0,c); if(c >= code.length){ clearTimeout(timer); } },50) </script> </body> </html>
你可以狠狠點(diǎn)擊此處具體示例 查看效果。好的,讓我們來(lái)分析一下以上代碼的原理,首先放一個(gè)用于包含代碼顯示的標(biāo)簽,然后定義一個(gè)包含代碼的字符串,接著定義一個(gè)初始值為 0 的變量,為什么要定義這樣一個(gè)變量呢?我們從實(shí)際效果中看到,它是一個(gè)字一個(gè)字的寫(xiě)入到頁(yè)面中的。初始值是沒(méi)有一個(gè)字符的,所以,我們就從第 0 個(gè)開(kāi)始寫(xiě)入, c 一個(gè)字一個(gè)字的加,然后不停的截取字符串,最后渲染到標(biāo)簽的內(nèi)容當(dāng)中去,當(dāng) c 的值大于等于了字符串的長(zhǎng)度之后,我們需要清除定時(shí)器。定時(shí)函數(shù)看著有些不太好,讓我們用超時(shí)調(diào)用結(jié)合遞歸來(lái)實(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ù),接下來(lái),我們要讓代碼保持空白和縮進(jìn),這可以使用 <pre> 標(biāo)簽來(lái)實(shí)現(xiàn),但其實(shí)我們還可以使用css代碼的 white-space 屬性來(lái)讓一個(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)擊此處 具體示例 查看效果。
接下來(lái),我們還要讓樣式生效,這很簡(jiǎn)單,將代碼在 style 標(biāo)簽中寫(xiě)一次即可,請(qǐng)看:
<!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á)式來(lái)實(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對(duì)象 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)擊此處 具體示例 查看效果。
不過(guò)這里為了方便,我還是使用插件 Prism.js ,另外在這里,我們還要用到將一個(gè)普通文本打造成 HTML 網(wǎng)頁(yè)的插件 marked.js 。
接下來(lái)分析如何暫停動(dòng)畫(huà)和繼續(xù)動(dòng)畫(huà),很簡(jiǎn)單,就是清除定時(shí)器,然后重新調(diào)用即可。如何讓編輯的代碼生效呢,這就需要用到自定義事件 .sync 事件修飾符,自行查看官網(wǎng) vue.js 。
雖然這里用原生 js 也可以實(shí)現(xiàn),但我們用 vue-cli 結(jié)合組件的方式來(lái)實(shí)現(xiàn),這樣更簡(jiǎn)單一些。好了,讓我們開(kāi)始吧:
新建一個(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)畫(huà)' : '繼續(xù)動(dòng)畫(huà) ||' }}</a> <a id="skipAnimation" @click="skipAnimationFun">跳過(guò)動(dòng)畫(huà)</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)畫(huà) autoPlaying:false,//播放音頻 audio:'' } }, mounted(){ }, methods:{ // 播放音樂(lè) 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); }, // 跳過(guò)動(dòng)畫(huà) skipAnimationFun(e){ e.preventDefault(); this.$emit('on-skip'); }, // 暫停動(dòng)畫(huà) pauseFun(e){ e.preventDefault(); this.paused = !this.paused; this.$emit('on-pause',this.paused); }, // 暫停音樂(lè) 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>
接下來(lái)是核心 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,//寫(xiě)入字的速度 currentStyle: { code: '' }, enableHtml: false,//是否打造成HTML網(wǎng)頁(yè) fullStyle: fullStyle, currentMarkdown: '', fullMarkdown: my, timer: null } }, created() { this.makeResume(); }, methods: { // 暫停動(dòng)畫(huà) pauseAnimation(bool) { if(bool && this.timer){ clearTimeout(this.timer); }else{ this.makeResume(); } }, // 快速跳過(guò)動(dòng)畫(huà) 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)畫(huà) 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)頁(yè) writeShowHtml: function() { return new Promise((resolve, reject) => { this.enableHtml = true; resolve(); }) }, // 寫(xiě)入css代碼 writeShowStyle(n) { return new Promise((resolve, reject) => { let showStyle = (async function() { let style = this.fullStyle[n]; if (!style) return; //計(jì)算出數(shù)組每一項(xiàng)的長(zhǎng)度 let length = this.fullStyle.filter((f, i) => i <= n).map((it) => it.length).reduce((t, c) => t + c, 0); //當(dāng)前要寫(xiě)入的長(zhǎng)度等于數(shù)組每一項(xiàng)的長(zhǎng)度減去當(dāng)前正在寫(xiě)的字符串的長(zhǎ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(); }) }, // 寫(xiě)入簡(jiǎn)歷 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è)可以快速跳過(guò)動(dòng)畫(huà),可以暫停動(dòng)畫(huà),還有音樂(lè)播放,還能自由編輯代碼的會(huì)動(dòng)的簡(jiǎn)歷已經(jīng)完成,代碼已上傳至 git源碼 ,歡迎 fork ,也望不吝嗇 star 。
總結(jié)
以上所述是小編給大家介紹的vue.js實(shí)現(xiàn)會(huì)動(dòng)的簡(jiǎn)歷(包含底部導(dǎo)航功能,編輯功能),希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
Vue路由切換和Axios接口取消重復(fù)請(qǐng)求詳解
在web項(xiàng)目開(kāi)發(fā)的過(guò)程中,經(jīng)常會(huì)遇到客服端重復(fù)發(fā)送請(qǐng)求的場(chǎng)景,下面這篇文章主要給大家介紹了關(guān)于Vue路由切換和Axios接口取消重復(fù)請(qǐng)求的相關(guān)資料,需要的朋友可以參考下2022-05-05Vue3+script setup+ts+Vite+Volar搭建項(xiàng)目
本文主要介紹了Vue3+script setup+ts+Vite+Volar搭建項(xiàng)目,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08axios封裝,使用攔截器統(tǒng)一處理接口,超詳細(xì)的教程(推薦)
這篇文章主要介紹了axios封裝使用攔截器處理接口,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05稍微學(xué)一下Vue的數(shù)據(jù)響應(yīng)式(Vue2及Vue3區(qū)別)
這篇文章主要介紹了稍微學(xué)一下 Vue 的數(shù)據(jù)響應(yīng)式(Vue2 及 Vue3),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11vue 搭建后臺(tái)系統(tǒng)模塊化開(kāi)發(fā)詳解
這篇文章主要介紹了vue 搭建后臺(tái)系統(tǒng)模塊化開(kāi)發(fā)詳解,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-05-05vue2.0.js的多級(jí)聯(lián)動(dòng)選擇器實(shí)現(xiàn)方法
下面小編就為大家分享一篇vue2.0.js的多級(jí)聯(lián)動(dòng)選擇器實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-02-02在axios中使用params傳參的時(shí)候傳入數(shù)組的方法
今天小編就為大家分享一篇在axios中使用params傳參的時(shí)候傳入數(shù)組的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-09-09vant van-list下拉加載更多onload事件問(wèn)題
這篇文章主要介紹了vant van-list下拉加載更多onload事件問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01