您好,登錄后才能下訂單哦!
在網上看到一個這樣的網站,STRML 它的效果看著十分有趣,如下圖所示:
這個網站是用 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 >' + matchStrSpecial[k] + '</span>'); } for (var key in lightColorCode) { if (key === 'keywords') { lightColorCode[key].forEach(function (imp) { htmlStr = htmlStr.replace(new RegExp(imp, 'gim'), '<span >' + imp + '</span>') }) flag = true; } else if (key === 'importantObj') { lightColorCode[key].forEach(function (kw) { htmlStr = htmlStr.replace(new RegExp(kw, 'gim'), '<span >' + kw + '</span>') }) flag = true; } else if (key === 'method') { lightColorCode[key].forEach(function (mt) { htmlStr = htmlStr.replace(new RegExp(mt, 'gim'), '<span >' + 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 >' + 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實現會動的簡歷(包含底部導航功能,編輯功能),希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對億速云網站的支持!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。