Sneak Peek
As shown in the figure below, the code writes the program line by line, and gradually draws the appearance of a lantern (161e754927eee9 PC mobile terminal supports ), do you want to know how it is realized? Let's explore with the fat head fish O(∩_∩)O~
You can also click on with animation program from a lantern experience something, Pangtou Nuggets activities warehouse View source
Principle inquiry
The effect is as if a typist were constantly typing text, and the page was dynamic. It was as if a film had already been recorded and we were just sitting in front of the projector watching it.
The principle itself is also very simple. As long as you have a little front-end knowledge, you can make one by yourself right away.
1. Scrolling code
timer character accumulation: believe you are smart enough to have guessed that thehtml
andcss
codes scrolling on the screen are to start a timer, and then accumulate the pre-prepared characters into apre
tag.
2. Lantern layout
dynamically addHTML fragment and
, a static pages ofCSS fragments:
html
andcss
composition, lantern can be constantly changing, behind nature is composed of lanternshtml
andcss
changing results.
3. Example explanation
Imagine that you want to add a to a web page every 0.1 seconds. Is it possible to start a timer and
into the body intermittently? Yes, this step completes the first part of the principle
into the page, I also want to change the font color of the word ah and the background color of the webpage, so what should I do? Is it OK to execute the following code?
.xxx{
color: blue;
background: red;
}
That's right, it's just that changing the font and background color is not a sudden change, but a timer is style
, and the following code is inserted into the 061e754927f0f2 tag intermittently, so that the second step of the principle is completed. Is it simple? Next Let's do it step by step.
Brief analysis
1. Editor layout
If a worker wants to do a good job, he must first sharpen his tools. The premise of realizing the code to draw by himself is to have a similar editor place to give himshow
, so there will be three areas forhtml
,css
Mobile layout
The upper and lower structure layout, the upper part ishtml
andcss
, and the lower part is the lantern display area
PC side layout
Left and right structure layout, the left ishtml
andcss
, and the right is the lantern display area
Template
<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>
side control
Simply do the adaptation of the mobile terminal and the PC terminal, and then control the layout through the style
computed: {
containerClasses () {
// 做一个简单的适配
return [
'container',
isMobile() ? 'container-mobile' : ''
]
}
}
2. Code highlighting
The code highlighting in the example isprismjs
andpre
. You only need to fill in the code you want to highlight and select the highlighted language to achieve the above effect.
// 核心代码,只有一行
this.styleEditPre = Prism.highlight(previewStylesSource, Prism.languages.css)
3. Lantern layout implementation
To achieve lanterns changing the layout, you need two things, one is the lantern itselfhtml
element there is the controlhtml
stylecss
By preview-html
carrier `HTML fragments by previewStyles
carried by style
css` label wrapping style
// 容器
<div class="preview">
<!-- 这是预览区域,灯笼最终会被画到这里噢 -->
<div class="preview-html" v-html="previewHtmls"></div>
<!-- 这里是样式真正起作用的地方 -->
<div v-html="previewStyles"></div>
</div>
Logic code
// 样式控制核心代码
this.previewStyles = `
<style>
${previewStylesSource}
</style>
`
// html控制核心代码
this.previewHtmls = previewHtmls
4. Code configuration preview
We execute the code step by step, and the code itself is configured through two files, one ishtml
, and the other is the filecss
Each step is an item of the array
4.1 html configuration
Note that the following code format is intentionally made into this format, not out of alignment
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 configuration
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;
}
`
]
Overall process
The knowledge points required to realize the principle and the whole process, through a brief analysis, I believe you have understood, the next thing we have to do is to combine these knowledge points to complete the automatic painting.
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)
})
},
}
}
end
New year is coming soon! I wish everyone a happy new year and "code" to success.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。