先睹为快
如下图,代码在本人一行一行写程序,逐步画出一个喜气灯笼的模样(PC挪动端都反对噢),想不想晓得是它怎么实现的呢?和胖头鱼一起来探索一番吧O(∩_∩)O~
你也能够间接点击 用程序主动画了一个灯笼 体验一番,胖头鱼的掘金流动仓库查看源码
原理探索
这个成果就如同一个打字员在一直地录入文字,页面出现动态效果。又如同一个早曾经录制好影片,而咱们只是坐在放映机前观看。
原理自身也非常简单,只有你会一点点前端常识,就能够马上亲手做一个进去。
1. 滚动的代码
定时器字符累加: 置信聪慧的你早曾经猜到屏幕中滚动的html
、css
代码就是通过启动一个定时器,而后将事后筹备好的字符,一直累加到一个pre
标签中。
2. 灯笼的布局
动静增加html片段
和css片段
:,一张动态网页由html
和css
组成,灯笼能一直地发生变化,背地天然是组成灯笼的html
和css
一直变动的后果。
3. 例子解释
设想一下你要往一张网页每距离0.1秒减少一个啊
字,是不是开个定时器,间断地往body外面塞啊
,就能够啊!没错,做到这一步就实现了原理的第一局部
再设想一下,在往页面外面塞啊
的时候,我还想扭转啊字的字体色彩以及网页背景色彩,那应该怎么做呢,是不是执行上面的代码就能够呢?
.xxx{ color: blue; background: red; }
没错,只不过更改字体和背景色不是忽然扭转的,而是开个定时器,间断地往style
标签中塞入以下代码,这样就实现了原理的第二步,是不是好简略 , 接下来让咱们一步步实现它。
简要解析
1.编辑器布局
工欲善其事,必先利其器。在实现代码本人画画的前提是有个相似编辑器中央给他show
,所以会有编辑html
、css
和预览三个区域。
挪动端布局
高低构造布局,下面是html
、css
的编辑区域,上面的灯笼的展现区域
PC端布局
左右构造布局,右边是html
、css
的编辑区域,左边是灯笼的展现区域
模板
<template> <div :class="containerClasses"> <div class="edit"> <div class="html-edit" ref="htmlEditRef"> <!-- 这是html代码编辑区域 --> <pre v-html="htmlEditPre" ref="htmlEditPreRef"></pre> </div> <div class="css-edit" ref="cssEditRef"> <!-- 这是css代码编辑区域 --> <pre v-html="styleEditPre"></pre> </div> </div> <div class="preview"> <!-- 这是预览区域,灯笼最终会被画到这里噢 --> <div class="preview-html" v-html="previewHtmls"></div> <!-- 这里是款式真正起作用的中央,明码就暗藏... --> <div v-html="previewStyles"></div> </div> </div></template>
端管制
简略的做一下挪动端和PC端的适配,而后通过款式去管制布局即可
computed: {containerClasses () { // 做一个简略的适配 return [ 'container', isMobile() ? 'container-mobile' : '' ]}}
2.代码高亮
示例中的代码高亮是借助prismjs
和pre
进行转化解决的,只须要填充你想要高亮的代码,以及抉择高亮的语言就能够实现上述成果。
// 外围代码,只有一行this.styleEditPre = Prism.highlight(previewStylesSource, Prism.languages.css)
3. 灯笼布局实现
要实现灯笼一直变动的布局,须要两个货色,一个是灯笼自身的html
元素还有就是管制html
款式的css
通过preview-html
`承载html片段,通过
previewStyles承载由
style标签包裹的
css`款式
// 容器<div class="preview"> <!-- 这是预览区域,灯笼最终会被画到这里噢 --> <div class="preview-html" v-html="previewHtmls"></div> <!-- 这里是款式真正起作用的中央 --> <div v-html="previewStyles"></div></div>
逻辑代码
// 款式管制外围代码this.previewStyles = ` <style> ${previewStylesSource} </style>`// html管制外围代码this.previewHtmls = previewHtmls
4. 代码配置预览
咱们通过一个个步骤将代码按阶段去执行,而代码自身是通过两个文件进行配置的,一个是管制html
的文件,一个是管制css
的文件。每一个步骤都是数组的一项
4.1 html配置
留神上面的代码格局是成心弄成这种格局的,并非是没有对齐
export default [ // 结尾寒暄 ` <!-- XDM好,我是前端胖头鱼~~~ 据说掘金又在搞流动了,奖品还很丰富... 我能要那个美腻的小姐姐吗? --> `, // 阐明宗旨 ` <!-- 以前都是用“手”写代码,明天想尝试一下 “代码写代码”,主动画一个喜庆的灯笼 --> `, // 创立编辑器 ` <!-- 第①步,先创立一个编辑器 --> `, // 创立编辑器html构造 ` <div class="container"> <div class="edit"> <div class="html-edit"> <!-- 这是html代码编辑区域 --> <pre v-html="htmlEditPre"> <!-- htmlStep0 --> </pre> </div> <div class="css-edit"> <!-- 这是css代码编辑区域 --> <pre v-html="cssEditPre"></pre> </div> </div> <div class="preview"> <!-- 这是预览区域,灯笼最终会被画到这里噢 --> <div class="preview-html"></div> <!-- 这里是款式真正起作用的中央,明码就暗藏... --> <div v-html="cssEditPre"></div> </div> </div> `, // 开始画款式 ` <!-- 第②步,给编辑器来点款式,我要开始画了喔~~ --> `, // 画灯笼的大肚子 ` <!-- 第③步,先画灯笼的大肚子构造 --> <div class="lantern-container"> <!-- htmlStep1 --> <!-- 大红灯笼区域 --> <div class="lantern-light"> <!-- htmlStep2 --> </div> </div> `, // 提着灯笼的线 ` <!-- 第④步,灯笼顶部是有根线的 --> <div class="lantern-top-line"></div> `, ` <!-- 第⑤步,给灯笼加两个盖子 --> <div class="lantern-hat-top"></div> <div class="lantern-hat-bottom"></div> <!-- htmlStep3 --> `, ` <!-- 第⑥步,感觉灯笼快要成了,再给他加上四根线吧 --> <div class="lantern-line-out"> <div class="lantern-line-innner"> <!-- htmlStep5 --> </div> </div> <!-- htmlStep4 --> `, ` <!-- 第⑦步,灯笼是不是还有底部的小尾巴呀 --> <div class="lantern-rope-top"> <div class="lantern-rope-middle"></div> <div class="lantern-rope-bottom"></div> </div> `, ` <!-- 第⑧步,最初当然少不了送给大家的福啦 --> <div class="lantern-fu">福</div> `]
4.2 css配置
export default [ // 0. 增加根本款式 ` /* 首先给所有元素加上过渡成果 */ * { transition: all .3s; -webkit-transition: all .3s; } /* 红色背景太枯燥了,咱们来点背景 */ html { color: rgb(222,222,222); background: rgb(0,43,54); } /* 代码高亮 */ .token.selector{ color: rgb(133,153,0); } .token.property{ color: rgb(187,137,0); } .token.punctuation{ color: yellow; } .token.function{ color: rgb(42,161,152); } `, // 1.创立编辑器自身的款式 ` /* 咱们须要做一个铺满全屏的容器 */ .container{ width: 100%; height: 100vh; display: flex; justify-content: space-between; align-items: center; } /* 代码编辑区域50%宽度,留一些空间给预览区域 */ .edit{ width: 50%; height: 100%; background-color: #1d1f20; display: flex; flex-direction: column; justify-content: space-between; } .html-edit, .css-edit{ flex: 1; overflow: scroll; padding: 10px; } .html-edit{ border-bottom: 5px solid #2b2e2f; } /* 预览区域有50%的空间 */ .preview{ flex: 1; height: 100%; background-color: #2f1f47; } .preview-html{ display: flex; align-items: center; justify-content: center; height: 100%; } /* 好啦~ 你应该看到一个编辑器的根本感觉了,咱们要开始画灯笼咯 */ `, // 2 ` /* 给灯笼的大肚子整款式 */ .lantern-container { position: relative; } .lantern-light { position: relative; width: 120px; height: 90px; background-color: #ff0844; border-radius: 50%; box-shadow: -5px 5px 100px 4px #fa6c00; animation: wobble 2.5s infinite ease-in-out; transform-style: preserve-3d; } /* 让他动起来吧 */ @keyframes wobble { 0% { transform: rotate(-6deg); } 50% { transform: rotate(6deg); } 100% { transform: rotate(-6deg); } } `, // 3 ` /* 顶部的灯笼线 */ .lantern-top-line { width: 4px; height: 50px; background-color: #d1bb73; position: absolute; left: 50%; transform: translateX(-50%); top: -20px; border-radius: 2px 2px 0 0; } `, // 4 ` /* 灯笼顶部、底部盖子款式 */ .lantern-hat-top, .lantern-hat-bottom { content: ""; position: absolute; width: 60px; height: 12px; background-color: #ffa500; left: 50%; transform: translateX(-50%); } /* 顶部地位 */ .lantern-hat-top { top: -8px; border-radius: 6px 6px 0 0; } /* 底部地位 */ .lantern-hat-bottom { bottom: -8px; border-radius: 0 0 6px 6px; } `, // 5 ` /* 灯笼两头的线条 */ .lantern-line-out, .lantern-line-innner { height: 90px; border-radius: 50%; border: 2px solid #ffa500; background-color: rgba(216, 0, 15, 0.1); } /* 线条外层 */ .lantern-line-out { width: 100px; margin: 12px 8px 8px 10px; } /* 线条内层 */ .lantern-line-innner { margin: -2px 8px 8px 26px; width: 45px; display: flex; align-items: center; justify-content: center; } `, // 6 ` /* 灯笼底部线条 */ .lantern-rope-top { width: 6px; height: 18px; background-color: #ffa500; border-radius: 0 0 5px 5px; position: relative; margin: -5px 0 0 60px; /* 让灯穗也有一个动画成果 */ animation: wobble 2.5s infinite ease-in-out; } .lantern-rope-middle, .lantern-rope-bottom { position: absolute; width: 10px; left: -2px; } .lantern-rope-middle { border-radius: 50%; top: 14px; height: 10px; background-color: #dc8f03; z-index: 2; } .lantern-rope-bottom { background-color: #ffa500; border-bottom-left-radius: 5px; height: 35px; top: 18px; z-index: 1; } `, // 7 ` /* 福款式 */ .lantern-fu { font-size: 30px; font-weight: bold; color: #ffa500; } `]
整体流程
实现原理和整个过程所需的知识点,通过简要解析置信你曾经明确了,接下来咱们要做的事件就是把这些知识点组合在一起,实现主动画画。
import Prism from 'prismjs'import htmls from './config/htmls'import styles from './config/styles'import { isMobile, delay } from '../../common/utils'export default { name: 'newYear2022', data () { return { // html代码展现片段 htmlEditPre: '', htmlEditPreSource: '', // css代码展现片段 styleEditPre: '', // 理论起作用的css previewStylesSource: '', previewStyles: '', // 预览的html previewHtmls: '', } }, computed: { containerClasses () { // 做一个简略的适配 return [ 'container', isMobile() ? 'container-mobile' : '' ] } }, async mounted () { // 1. 打招呼 await this.doHtmlStep(0) // 2. 阐明宗旨 await this.doHtmlStep(1) await delay(500) // 3. 第一步申明 await this.doHtmlStep(2) await delay(500) // 4. 创立写代码的编辑器 await this.doHtmlStep(3) await delay(500) // 5. 筹备写编辑器的款式 await this.doHtmlStep(4) await delay(500) // 6. 根本款式 await this.doStyleStep(0) await delay(500) // 7. 编辑器的款式 await this.doStyleStep(1) await delay(500) // 8. 画灯笼的大肚子html await Promise.all([ this.doHtmlStep(5, 0), this.doEffectHtmlsStep(5, 0), ]) await delay(500) // 8. 画灯笼的大肚子css await this.doStyleStep(2) await delay(500) // 9. 提着灯笼的线html await Promise.all([ this.doHtmlStep(6, 1), this.doEffectHtmlsStep(6, 1), ]) await delay(500) // 10. 提着灯笼的线css await this.doStyleStep(3) await delay(500) // 11. 给灯笼加两个盖子html await Promise.all([ this.doHtmlStep(7, 2), this.doEffectHtmlsStep(7, 2), ]) await delay(500) // 12. 给灯笼加两个盖子css await this.doStyleStep(4) await delay(500) // 13. 感觉灯笼快要成了,再给他加上四根线吧html await Promise.all([ this.doHtmlStep(8, 3), this.doEffectHtmlsStep(8, 3), ]) await delay(500) // 14. 感觉灯笼快要成了,再给他加上四根线吧css await this.doStyleStep(5) await delay(500) // 15. 灯笼是不是还有底部的小尾巴呀html await Promise.all([ this.doHtmlStep(9, 4), this.doEffectHtmlsStep(9, 4), ]) await delay(500) // 16. 灯笼是不是还有底部的小尾巴呀css await this.doStyleStep(6) await delay(500) // 17. 最初当然少不了送给大家的福啦html await Promise.all([ this.doHtmlStep(10, 5), this.doEffectHtmlsStep(10, 5), ]) await delay(500) // 18. 最初当然少不了送给大家的福啦css await this.doStyleStep(7) await delay(500) }, methods: { // 渲染css doStyleStep (step) { const cssEditRef = this.$refs.cssEditRef return new Promise((resolve) => { // 从css配置文件中取出第n步的款式 const styleStepConfig = styles[ step ] if (!styleStepConfig) { return } let previewStylesSource = this.previewStylesSource let start = 0 let timter = setInterval(() => { // 挨个累加 let char = styleStepConfig.substring(start, start + 1) previewStylesSource += char if (start >= styleStepConfig.length) { console.log('css完结') clearInterval(timter) resolve(start) } else { this.previewStylesSource = previewStylesSource // 右边编辑器展现给用户看的 this.styleEditPre = Prism.highlight(previewStylesSource, Prism.languages.css) // 左边预览区域理论起作用的css this.previewStyles = ` <style> ${previewStylesSource} </style> ` start += 1 // 因为要一直滚动到底部,简略粗犷解决一下 document.documentElement.scrollTo({ top: 10000, left: 0, }) // 因为要一直滚动到底部,简略粗犷解决一下 cssEditRef && cssEditRef.scrollTo({ top: 100000, left: 0, }) } }, 0) }) }, // 渲染html doEffectHtmlsStep (step, insertStepIndex = -1) { // 留神html局部和css局部最大的不同在于前面的步骤是有可能插入到之前的代码两头的,并不是一味地增加到尾部 // 所以须要先找到标识,而后插入 const insertStep = insertStepIndex !== -1 ? `<!-- htmlStep${insertStepIndex} -->` : -1 return new Promise((resolve) => { const htmlStepConfig = htmls[ step ] let previewHtmls = this.previewHtmls const index = previewHtmls.indexOf(insertStep) const stepInHtmls = index !== -1 let frontHtml = stepInHtmls ? previewHtmls.slice(0, index + insertStep.length) : previewHtmls let endHtml = stepInHtmls ? previewHtmls.slice(index + insertStep.length) : '' let start = 0 let chars = '' let timter = setInterval(() => { let char = htmlStepConfig.substring(start, start + 1) // 累加字段 chars += char previewHtmls = frontHtml + chars + endHtml if (start >= htmlStepConfig.length) { console.log('html完结') clearInterval(timter) resolve(start) } else { // 赋值html片段 this.previewHtmls = previewHtmls start += 1 } }, 0) }) }, // 编辑区域html高亮代码 doHtmlStep (step, insertStepIndex = -1) { const htmlEditRef = this.$refs.htmlEditRef const htmlEditPreRef = this.$refs.htmlEditPreRef // 同上须要找到插入标记 const insertStep = insertStepIndex !== -1 ? `<!-- htmlStep${insertStepIndex} -->` : -1 return new Promise((resolve) => { const htmlStepConfig = htmls[ step ] let htmlEditPreSource = this.htmlEditPreSource const index = htmlEditPreSource.indexOf(insertStep) const stepInHtmls = index !== -1 // 依照条件拼接代码 let frontHtml = stepInHtmls ? htmlEditPreSource.slice(0, index + insertStep.length) : htmlEditPreSource let endHtml = stepInHtmls ? htmlEditPreSource.slice(index + insertStep.length) : '' let start = 0 let chars = '' let timter = setInterval(() => { let char = htmlStepConfig.substring(start, start + 1) chars += char htmlEditPreSource = frontHtml + chars + endHtml if (start >= htmlStepConfig.length) { console.log('html完结') clearInterval(timter) resolve(start) } else { this.htmlEditPreSource = htmlEditPreSource // 代码高亮解决 this.htmlEditPre = Prism.highlight(htmlEditPreSource, Prism.languages.html) start += 1 if (insertStep !== -1) { // 当要插入到两头时,滚动条滚动到两头,不便看代码 htmlEditRef && htmlEditRef.scrollTo({ top: (htmlEditPreRef.offsetHeight - htmlEditRef.offsetHeight) / 2, left: 1000, }) } else { // 否则间接滚动到底部 htmlEditRef && htmlEditRef.scrollTo({ top: 100000, left: 0, }) } } }, 0) }) }, }}
结尾
马上就要新年啦!愿大家新年快乐,“码”到胜利。
参考
- 过年了~我用CSS画了个灯笼,看着真喜庆
- 用原生 js 写一个 "多动症" 的简历