关于前端:几个骚操作让代码自动学会画画太好玩啦

27次阅读

共计 10535 个字符,预计需要花费 27 分钟才能阅读完成。

先睹为快

如下图,代码在本人一行一行写程序,逐步画出一个喜气灯笼的模样(PC 挪动端都反对噢),想不想晓得是它怎么实现的呢?和胖头鱼一起来探索一番吧 O(∩_∩)O~

你也能够间接点击 用程序主动画了一个灯笼 体验一番,胖头鱼的掘金流动仓库查看源码

原理探索

这个成果就如同一个打字员在一直地录入文字,页面出现动态效果。又如同一个早曾经录制好影片,而咱们只是坐在放映机前观看。

原理自身也非常简单,只有你会一点点前端常识,就能够马上亲手做一个进去。

1. 滚动的代码

定时器字符累加: 置信聪慧的你早曾经猜到屏幕中滚动的 htmlcss 代码就是通过启动一个定时器,而后将事后筹备好的字符,一直累加到一个 pre 标签中。

2. 灯笼的布局

动静增加 html 片段css 片段 ,一张动态网页由htmlcss组成,灯笼能一直地发生变化,背地天然是组成灯笼的 htmlcss一直变动的后果。

3. 例子解释

设想一下你要往一张网页每距离 0.1 秒减少一个 字,是不是开个定时器,间断地往 body 外面塞 ,就能够啊!没错,做到这一步就实现了原理的第一局部

再设想一下,在往页面外面塞 的时候,我还想扭转啊字的字体色彩以及网页背景色彩,那应该怎么做呢,是不是执行上面的代码就能够呢?

.xxx{
  color: blue;
  background: red; 
}

没错,只不过更改字体和背景色不是忽然扭转的,而是开个定时器,间断地往 style 标签中塞入以下代码,这样就实现了原理的第二步,是不是好简略,接下来让咱们一步步实现它。

简要解析

1. 编辑器布局

工欲善其事,必先利其器。在实现代码本人画画的前提是有个相似编辑器中央给他 show,所以会有编辑htmlcss 和预览三个区域。

挪动端布局

高低构造布局,下面是 htmlcss 的编辑区域,上面的灯笼的展现区域

PC 端布局

左右构造布局,右边是 htmlcss 的编辑区域,左边是灯笼的展现区域

模板

<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. 代码高亮

示例中的代码高亮是借助 prismjspre进行转化解决的,只须要填充你想要高亮的代码,以及抉择高亮的语言就能够实现上述成果。

// 外围代码, 只有一行
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)
      })
    },
  }
}

结尾

马上就要新年啦!愿大家新年快乐,“码”到胜利。

参考

  1. 过年了~ 我用 CSS 画了个灯笼,看着真喜庆
  2. 用原生 js 写一个 “ 多动症 ” 的简历

正文完
 0