5
头图

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 the html and css codes scrolling on the screen are to start a timer, and then accumulate the pre-prepared characters into a pre tag.

2. Lantern layout

dynamically add HTML fragment and CSS fragments: , a static pages of html and css composition, lantern can be constantly changing, behind nature is composed of lanterns html and css 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 him show , so there will be three areas for html , css

Mobile layout

The upper and lower structure layout, the upper part is html and css , and the lower part is the lantern display area

PC side layout

Left and right structure layout, the left is html and css , 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 is prismjs and pre . 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 itself html element there is the control html style css

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 is html , and the other is the file css 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.

refer to

  1. New Year~ I used CSS to draw a lantern, it looks so festive
  2. Write a "ADHD" resume with native js

前端胖头鱼
3.7k 声望6.2k 粉丝