当春节前不想写业务的前端会做什么

26,683 阅读16分钟

背景

今天周日,本来应该是在家躺着的日子却被迫过来调休,想必各位大佬已经踏上了回家的列车吧,那么小弟在这里提前祝福各位新年快乐!

由于春节将至也确实没有想写业务的打算(boring)于是乎我就来掘金摸鱼,正巧看到了一篇文章😈当一个摆子前端太闲的时候会做什么 - 掘金 (juejin.cn),这位大佬实现了一个桌面小精灵,我突然想起了我小时候充不起的超能NoNo,文章和我逝去的童年一拍即合于是乎我的超能NoNo--Momo诞生了。

image.png

作为一名优秀的切图仔,我肯定不能直接搬运(炒)前辈的代码,而且前辈的代码跟我想法不一样,主要是我觉得吧他的大眼虽然很炫但不够萌萌哒,只能自己去画咯。

以下是Momo的简单介绍:

  • 名称: Momo
  • 性别: 不详
  • 情绪: 默认/生气
  • 状态: Loading/working
  • 自述: 大家好我是Momo,诞生于2024年,我是一名智能桌面助手哦。

Momo生活照

image.png

代码

画页面

先画萌萌的外表

搞一个圆滚滚的身体

<style>
  *{
    padding: 0;
    margin: 0;
  }
  body{
    background-color: #000;
    display: flex;
    height: 100vh;
    overflow: hidden;
  }
  .Momo{
      border-radius: 50%;
      width: 150px;
      height: 150px;
      margin: auto;
      background-color: #cbe8d7;
      position: relative;
      display: flex;
      align-items: center;
      justify-content: center;
    }
</style>

 <main class="Momo">
 </main>

效果:

image.png ps: 这啥呀太丑了,

画个脸再加个眼睛呢?

老实讲,画脸这步还真难到我了,因为这种不太规则的椭圆确实不太好弄, 我记得border-radius可以实现这种效果,我还记得有个网站可以可视化拖动 Fancy Border Radius Generator (9elements.github.io)

image.png 于是乎...有了下面一行代码, border-radius: 55% 45% 77% 23% / 54% 22% 78% 46% ; 好了小脸蛋有着落了下一步画上去再加个眼睛再加一点细节,请看VCR

<style>
    .face{
      position: absolute;
      width: 100px;
      height: 100px;
      background-color: #000;
      transform: rotate(45deg);
      margin-top: -10px;
      /* border-radius: 58% 42% 81% 19% / 54% 20% 80% 46%; */
      border-radius: 55% 45% 77% 23% / 54% 22% 78% 46% ;
      border:7px solid #1e80e260;
    }
    .inner{
      transform: rotate(-45deg);
      position: absolute;
      width: 100px;
      height: 100px;
    }

    .leftEye,.rightEye{
      width: 10px;
      border-top: 0px solid #000;
      border-bottom: 0px solid #000;
      background-color: #02feff;
      height: 26px;
      box-sizing: border-box;

      /* background-color: #02feff; */
  
      position: absolute;
      top: 37%;
    }
    .leftEye{
      left: 20%;
    }
    .rightEye{
      left: 70%;
    }
</style
  <main class="Momo">
    <div class="face">
      <div class="inner">
        <div class="leftEye zy"></div>
        <div class="rightEye zy"></div>
      </div>
    </div>
  </main>

效果:

image.png 你再看再看?再看我就把你喝掉!!!
解释一下,这里由于画的椭圆是45度的后续有的眼睛需要定位一下,flex也行但是想到后面可能要加东西我就没用flex,但是定位是根据旋转后的进行定位的不太方便,于是乎我加了一个inner 反向旋转负负得正...

萌萌的小耳朵我来咯

耳朵就更难画了呜呜呜,

image.png 为什么我说难画,难就难在这里白色的身体是个圆弧状还要把这个耳朵包起来,太难了...由于小弟没有美工,svg也不太熟没办法了上clip-path

<style>
    .face{
      position: absolute;
      width: 100px;
      height: 100px;
      background-color: #000;
      transform: rotate(45deg);
      margin-top: -10px;
      /* border-radius: 58% 42% 81% 19% / 54% 20% 80% 46%; */
      border-radius: 55% 45% 77% 23% / 54% 22% 78% 46% ;
      border:7px solid #1e80e260;
    }
    .inner{
      transform: rotate(-45deg);
      position: absolute;
      width: 100px;
      height: 100px;
    }

    .leftEye,.rightEye{
      width: 10px;
      border-top: 0px solid #000;
      border-bottom: 0px solid #000;
      background-color: #02feff;
      height: 26px;
      box-sizing: border-box;

      /* background-color: #02feff; */
  
      position: absolute;
      top: 37%;
    }
    .leftEye{
      left: 20%;
    }
    .rightEye{
      left: 70%;
    }
    
    .lefEar{
      position: absolute;
      height: 75px;
      width: 30px;
      background-color: #02feff;
      left: 0;
      top: 0;
      border-radius: 44% 49% 22% 65% / 100% 100% 0% 0%;
      /* border-radius: 170% 170% 5% 5%;   */
      transform: rotate(-35deg);
      top: -60px;
      left: -25px;
    }
    .lefEar::after{
      content: '';
      display: inline-block;
      width: 40px;
      height: 50px;
      border-radius: 44% 49% 22% 65% / 100% 100% 0% 0%;
      background-color: #cbe8d7;
      position: absolute;
      transform: rotate(180deg);
      bottom: -25px;
      left: -5px;
      clip-path: polygon(
      28% 0, 
      35% 10%, 
      70% 0, 
      90% 40%, 
      100% 50%, 
      90% 85%, 
      90% 90%, 
      83% 100%, 
      50% 50%, 
      20% 100%, 
      0 43%);
    }

    .rigEar{
      position: absolute;
      height: 75px;
      width: 30px;
      background-color: #02feff;
      left: 0;
      top: 0;
      border-radius: 44% 49% 22% 65% / 100% 100% 0% 0%;
      /* border-radius: 170% 170% 5% 5%;   */
      transform: rotate(35deg);
      top: -60px;
      left: 140px;
    }
    .rigEar::after{
      content: '';
      display: inline-block;
      width: 40px;
      height: 50px;
      border-radius: 44% 49% 22% 65% / 100% 100% 0% 0%;
      background-color: #cbe8d7;
      position: absolute;
      transform: rotate(180deg);
      bottom: -25px;
      left: -5px;
      clip-path: polygon(
      28% 0, 
      35% 10%, 
      70% 0, 
      90% 40%, 
      100% 50%, 
      90% 85%, 
      90% 90%, 
      83% 100%, 
      50% 50%, 
      20% 100%, 
      0 43%);
    }
</style
  <main class="Momo">
    <div class="face">
      <div class="inner">
        <div class="leftEye zy"></div>
        <div class="rightEye zy"></div>
      </div>
    </div>
    <div class="lefEar"></div>
    <div class="rigEar"></div>
  </main>

效果 image.png

很明显看到这里耳朵不够丝滑,不过效果有了,后面再慢慢调整便是,没啥子问题先做后面的功能 image.png ps: 真的很萌!!!

要用类和css变量吗?

想着,这里精灵的大小不能固定写死就150px了,我得做大做强再创辉煌,于是先后改成了计算大小和类,情况VCR

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <style>
        body{
          background-color: #000;
          display: flex;
          height: 100vh;
          overflow: hidden;
          --blinkTime:1s;
          transform-style: preserve-3d;
        }

        .ToyarMomo{
          border-radius: 50%;
          width:  var(--size);
          height: var(--size);
          margin: auto;
          background-color: var(--mainColor);
          position: relative;
          display: flex;
          align-items: center;
          justify-content: center;
          transition: all .5s;
        }
        .ToyarMomo.ball{
          overflow: hidden;
        }
        .Momo-left_Ear{
          position: absolute;
          height: calc(var(--size)/2);
          width:  calc(var(--size)/5);
          background-color: var(--secondColor);
          left: 0;
          border-radius: 44% 49% 22% 65% / 100% 100% 0% 0%;
          /* border-radius: 170% 170% 5% 5%;   */
          transform: rotate(-35deg);
          transform-origin: bottom;
          top: calc(var(--size) * -2.8/7);
        }
        .Momo-left_Ear::after{
          content: '';
          display: inline-block;
          width: calc(var(--size) * 0.27);
          height: calc(var(--size) * 1/3);
          border-radius: 44% 49% 22% 65% / 100% 100% 0% 0%;
          background-color: var(--mainColor);
          position: absolute;
          transform: rotate(180deg);
          bottom: calc(var(--size) * -1/6);
          left: -15%;
          clip-path: polygon(36% 0, 80% -5%, 93% 0,
          95% 48%, 97% 68%, 94% 83%, 93% 86%,
          90% 92%,
          88% 95%, 91% 90%, 87% 95%, 81% 98%, 79% 98%, 78% 93%, 77% 88%, 76% 83%, 73% 76%, 70% 71%, 67% 66%, 56% 50%,
          56% 49%, 39% 70%, 34% 81%, 30% 96%, 27% 100%, 19% 94%, 16% 91%,
          11% 78%, 9% 73%, 5% 49%, 0 30%);
        }

        .Momo-right_Ear{
          position: absolute;
          height: calc(var(--size)/2);
          width:  calc(var(--size)/5);
          left: 0;
          top: 0;
          border-radius: 44% 49% 22% 65% / 100% 100% 0% 0%;
          /* border-radius: 170% 170% 5% 5%;   */
          transform: rotate(35deg);
          top: calc(var(--size) * -2.8/7);
          left: calc(var(--size) * 0.81);
          transform-origin: bottom;
          background-color: var(--secondColor);
          z-index: -1;
        }
        .Momo-right_Ear::after{
          content: '';
          display: inline-block;
          width: calc(var(--size) * 0.27);
          height: calc(var(--size) * 1/3);
          border-radius: 44% 49% 22% 65% / 100% 100% 0% 0%;
          background-color: var(--mainColor);
          position: absolute;
          transform: rotate(180deg);
          bottom: calc(var(--size) * -1/6);
          left: -15%;
          /* clip-path: polygon(
          28% 0, 
          35% 10%, 
          70% 0, 
          90% 40%, 
          100% 50%, 
          90% 85%, 
          90% 90%, 
          83% 100%, 
          50% 50%, 
          20% 100%, 
          0 43%); */
          clip-path: polygon(36% 0, 80% -5%, 93% 0,
          95% 48%, 97% 68%, 94% 83%, 93% 86%,
          90% 92%,
          88% 95%, 91% 90%, 87% 95%, 81% 98%, 79% 98%, 78% 93%, 77% 88%, 76% 83%, 73% 76%, 70% 71%, 67% 66%, 56% 50%,
          56% 49%, 39% 70%, 34% 81%, 30% 96%, 27% 100%, 19% 94%, 16% 91%,
          11% 78%, 9% 73%, 5% 49%, 0 30%);
        }
        .MomoBody{
          position: absolute;
          width: calc( var(--size) * 2/3); 
          height: calc( var(--size) * 2/3);
          background-color: #000;
          transform: rotate(45deg);
          margin-top: -10px;
          /* border-radius: 58% 42% 81% 19% / 54% 20% 80% 46%; */
          border-radius: 55% 45% 77% 23% / 54% 22% 78% 46% ;
          border:calc(var(--size) * 0.045 ) solid #2f6ead5e;
          z-index: 15;
        }
        .inner{
          transform: rotate(-45deg);
          position: absolute;
          width: calc( var(--size) * 2/3);
          height: calc( var(--size) * 2/3);
          overflow: hidden
        }


        .Momo-left_Eye,.Momo-right_Eye{
          width: calc(var(--size) * 1.5/15);
          border: 0px solid #000;
          border-top: 0px solid #000;
          border-bottom: 0px solid #000;
          background-color: var(--secondColor);
          height: calc( var(--size) * 12/75);
          box-sizing: border-box;
          position: absolute;
          top: 37%;
          
        }
        .Momo-left_Eye{
          left: 20%;
        }
        .Momo-right_Eye{
          left: 70%;
        }
        .momo-blink{
          animation: blink var(--blinkTime) linear;
        }
        .momo-leftRotate{
          animation: lRotate var(--blinkTime) linear;
        }
        .momo-rightRotate{
          animation: rRotate var(--blinkTime) linear;
        }

        /* .leftVg{
          border: 3px solid #000;
          width: 50px;
          height: 50px;
          position: absolute;
          left: 0;
          bottom: 0;
          border-radius: 30% 0 30% 0;
        }
        .rightVg{
          border: 3px solid #000;
          width: 50px;
          height: 50px;
          position: absolute;
          right: 0;
          bottom: 0;
          border-radius: 0 30% 0 30% ;
        } */

  </style>
  <script>
    class ToyarMomo {
      constructor(size,{
        mainColor='#cbe8d7',
        secondColor='#02feff',
      }) {
        this.size=size
        this.mainColor = mainColor
        this.secondColor = secondColor
        this.init()
      }
      init() {
        this.appendStyles()
        this.createMomo()
      }
      createLeftEar(){
        const leftEar = document.createElement('div')
        leftEar.className = 'Momo-left_Ear ear-light'
        this.leftEar =leftEar
        return leftEar
      }
      createRightEar(){
        const rightEar = document.createElement('div')
        rightEar.className = 'Momo-right_Ear ear-light'
        this.rightEar =rightEar
        return rightEar
      }
      createLeftEye(){
        const leftEye = document.createElement('div');
        leftEye.className ='Momo-left_Eye'
        this.leftEye = leftEye
        return leftEye
      }
      createRightEye(){
        const rightEye = document.createElement('div');
        rightEye.className ='Momo-right_Eye'
        this.rightEye = rightEye
        return rightEye
      }
      createInner(){
        const inner = document.createElement('div');
        inner.className='inner'
        this.inner = inner
        const leftEye = this.createLeftEye()
        const rightEye = this.createRightEye()
        inner.append(leftEye)
        inner.append(rightEye)
        return inner
      }
      createBody(){
        const body = document.createElement('div');
        body.className = 'MomoBody'
        this.body = body
        const inner =this.createInner()
        body.append(inner)
        return body
      }
      createMomo() {
        let main = document.createElement('main');
        main.id='Momo'
        main.className = 'ToyarMomo'
        main.style.setProperty('--size',this.size+'px')
        main.style.setProperty('--mainColor',this.mainColor)
        main.style.setProperty('--secondColor',this.secondColor)

        this.main = main
        const body= this.createBody()
        const leftEar= this.createLeftEar()
        const rightEar= this.createRightEar()
        main.append(body)
        main.append(leftEar)
        main.append(rightEar)

        const open =document.createElement('div')
        open.className='open'
        main.append(open)

        
        const leftVg =document.createElement('div')
        leftVg.className='leftVg'
        main.append(leftVg)

        const rightVg =document.createElement('div')
        rightVg.className='rightVg'
        main.append(rightVg)

        const dialog =document.createElement('div')
        dialog.className='dialog'
        this.dialog =dialog
        main.append(dialog)

        document.body.append(main)
        this.loading()
      }
      
      appendStyles() {
        let staticStyle = `
        `
        let style = document.createElement('style')
        style.innerText = staticStyle
        document.body.appendChild(style)
      }
     
    }
    let momo = new ToyarMomo(150,{
      // mainColor:'#cbe8d7',
      // secondColor:'#02feff',
      // mainColor:'red',
      // secondColor:'blue'
    })
  </script>
</body>

</html>

于是乎,我的代码变成了类和计算(真费时间啊...),类接受一个size大小属性,一个option 传递一些配置项,好啦终于可以实现功能了

实现动效

先来一个萌萌哒的眨眼吧

<style>
    @keyframes blink{
      0%{
        border-top: 0px solid #000;
        border-bottom: 0px solid #000;
      }
      50%{
        border-top: calc(var(--size) * 12/150) solid #000;
        border-bottom: calc(var(--size) * 12/150)  solid #000;
      }
      100%{
        border-top: 0px  solid #000;
        border-bottom: 0px  solid #000;
      }
    }
    .momo-blink{
      animation: blink var(--blinkTime) linear;
    }
</style>
<script>
class ToyarMomo {
  ...
  blink(){
    this.leftEye.classList.toggle('momo-blink')
    this.rightEye.classList.toggle('momo-blink')
  }
}
</script>

这样只要调用这个blink 就会进行眨眼动画

218jz-qxvac.gif

但是谁家好人一直眨眼啊,所以优化成随机眨眼

<script>
<script>
class ToyarMomo {
  ...
  startStatus() {
    setInterval(() => {
      let num =Math.random()
      if (num > 0 && num < 0.4) {
        this.blink()
      }
    }, 1000)
  }
  blink(){
    this.leftEye.classList.toggle('momo-blink')
    this.rightEye.classList.toggle('momo-blink')
  }
}
</script>

这样就可以随机进行眨眼了

loading

<style>
    .ToyarMomo.ball{
      overflow: hidden;
    }
    .loading{
      height:100%;
      line-height: calc(var(--size ) * 2/3);
      text-align: center;
      color: var(--secondColor);;
      position: absolute;
      width: 100%;
      font-weight: bold;
      letter-spacing: 1px;
      font-size:calc(var(--size ) * 1/10);
    }
</style>
    <script>
    class ToyarMomo {
      ...
        loading(){
            const loading = document.createElement('div')
            loading.className='loading'
            loading.innerText='Loading...'
            this.inner.innerHTML=''
            this.inner.append(loading)
            let left =0
            this.main.classList.add('ball')
            
            let timer = setInterval(()=>{
              left-=5
              if(left <=-this.size* 2/3 ){
                left=this.size*2/3
              }
              loading.style.left= left +'px'
            },100)
            setTimeout(()=>{
              clearInterval(timer)
              this.main.classList.remove('ball')
              this.open()
            },3000)
       }
       open(){
        this.inner.innerHTML=''
        this.inner.append(this.leftEye)
        this.inner.append(this.rightEye)
        this.startStatus()
      }
    }
    </script>

比较简单没啥好说的 3b9ga-mayrb.gif

眼睛跟随

   <script>
    class ToyarMomo {
      ...
      mouseMove(e){
        const multiple = 80;
          const transformElement=(x, y)=> {
            let box =  this.rightEye.getBoundingClientRect();
            let calcX = (y - box.y - (box.height / 2)) / multiple;
            let calcY = (x - box.x - (box.width / 2)) / multiple;
            this.rightEye.style.transform  =
            `translate(${calcY}px, ${calcX}px)`
            this.leftEye.style.transform  =
            `translate(${calcY}px, ${calcX}px)`
          }
        window.requestAnimationFrame(function(){
              transformElement(e.clientX, e.clientY);
        });
      }
      startEyeFollow(){
          document.getElementsByTagName("body")[0].addEventListener('mousemove', (e) => {
           this.mouseMove(e)
          });
      }
    }
   </script>

02ofe-ge3t5.gif

各种不同眼睛


    <style>
        /* **********不同的眼睛 ***********/
        /* 星星眼 */
        .Momo-left_Eye.starEye,
        .Momo-right_Eye.starEye{
          clip-path: polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%);
        }
        /* 椭圆眼 */
        .Momo-left_Eye.ellipse,
        .Momo-right_Eye.ellipse{
          clip-path: circle(50% at 50% 50%);
        }
        /* 圆眼 */
        .Momo-left_Eye.round,
        .Momo-right_Eye.round{
          clip-path: circle(50% at 50% 50%);
          width: calc(var(--size) * 12/75);
        }
        /* 左眼 */
        .Momo-left_Eye.left,
        .Momo-right_Eye.left{
          clip-path: polygon(40% 0%, 40% 33%, 100% 33%, 100% 68%, 40% 68%, 40% 100%, 0% 50%);
          width: calc(var(--size) * 12/75);
        }
        /* 右眼 */
        .Momo-left_Eye.right,
        .Momo-right_Eye.right{
          clip-path: polygon(0 34%, 60% 34%, 60% 0%, 100% 50%, 60% 100%, 60% 68%, 0 68%);
          width: calc(var(--size) * 12/75);
        }
        /* 菱形眼 */
        .Momo-left_Eye.rhombus,
        .Momo-right_Eye.rhombus{
          clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
        }
        /* 爱心眼 */
        .Momo-left_Eye.love,
        .Momo-right_Eye.love{
          clip-path:polygon(25% 0, 50% 20%, 75% 0, 100% 20%, 100% 60%, 50% 95%, 0 60%, 0 20%);
          width: calc(var(--size) * 12/75);
        }
    </style>

添加不同的类名即可有不同的眼睛

007b321b605312bc4566e7ee9d580b8.png

说点什么吧


<style>
.dialog{
  width: 230px;
  display: inline-block;
  /* background-color: red; */
  position: absolute;
  left: 130%;
  top: -50%;
  border: 3px solid transparent;
  border-image: url("./src/border.png");
  border-width: 23px 35px 33px 35px;
  border-image-slice: 51 38 20 132;
  box-sizing: border-box;
  color: #fff;
  word-break: break-all;
  transition: all 0.5s;
}
.dialog:empty{
  /* display: none; */
  height: 0;
  border-width: 0;
}
</style>
<script>
    class ToyarMomo {
      ...
      createMomo() {
        const dialog =document.createElement('div')
        dialog.className='dialog'
        this.dialog =dialog
        main.append(dialog)
      }
      say(info){
        this.dialog.innerText=info
        setTimeout(()=>{
          this.dialog.innerHTML =''
        },1000)
      }
    }
</script

这里用到了一个科技感十足的边框素材,使用边框图片技术,当我们这个dialog没有内容高度为0,这样就做完了一个小动画 border.png

a02cb-q4tr4.gif

动动耳朵?


<style>
    @keyframes lRotate{
      0%{
        transform: rotate(-35deg);
      }
      50%{
        transform: rotate(-55deg);
      }
      100%{
        transform: rotate(-35deg);
      }
    }
    @keyframes rRotate{
      0%{
        transform: rotate(35deg);
      }
      50%{
        transform: rotate(15deg);
      }
      100%{
        transform: rotate(35deg);
      }
    }
    .momo-leftRotate{
      animation: lRotate var(--blinkTime) linear;
    }
    .momo-rightRotate{
      animation: rRotate var(--blinkTime) linear;
    }
</style>
<script>
    class ToyarMomo {
      ...
      startStatus() {
        setInterval(() => {
          let data =Math.random()
          if (data > 0 && data < 0.4) {
            this.blink()
          }
          if(data>0.1 && data < 0.6){
            this.rotateEar()
          }
          rotateEar(){
            this.leftEar.classList.toggle('momo-leftRotate')
            this.rightEar.classList.toggle('momo-rightRotate')
          }
        }, 1000)
      }
    }
</script

p8snh-yw896.gif

我要生气啦


<style>

</style>
<script>
    class ToyarMomo {
      ...
      startStatus() {
        setInterval(() => {
          let data =Math.random()
          if (data > 0 && data < 0.4) {
            this.blink()
          }
          if(data>0.1 && data < 0.6){
            this.error()
          }
          rotateEar(){
            this.leftEar.classList.toggle('momo-leftRotate')
            this.rightEar.classList.toggle('momo-rightRotate')
          }
        }, 1000)
      },
      error(){
        if(this.main.style.getPropertyValue('--secondColor') === 'red'){
          return this.main.style.setProperty('--secondColor',this.secondColor)
        }
        this.main.style.setProperty('--secondColor','red')
      }
    }
</script

完整代码

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <style>
        body{
          background-color: #000;
          display: flex;
          height: 100vh;
          overflow: hidden;
          --blinkTime:1s;
          transform-style: preserve-3d;
        }

        @keyframes blink{
          0%{
            border-top: 0px solid #000;
            border-bottom: 0px solid #000;
          }
          50%{
            border-top: calc(var(--size) * 12/150) solid #000;
            border-bottom: calc(var(--size) * 12/150)  solid #000;
          }
          100%{
            border-top: 0px  solid #000;
            border-bottom: 0px  solid #000;
          }
        }
        @keyframes lRotate{
          0%{
            transform: rotate(-35deg);
          }
          50%{
            transform: rotate(-55deg);
          }
          100%{
            transform: rotate(-35deg);
          }
        }
        @keyframes rRotate{
          0%{
            transform: rotate(35deg);
          }
          50%{
            transform: rotate(15deg);
          }
          100%{
            transform: rotate(35deg);
          }
        }
        @keyframes startlight{
          0%{
            background-color: var(--secondColor);
            box-shadow: 0px 0px 0px var(--secondColor);
          }
          100%{
            background-color: var(--secondColor);
            box-shadow: 0px 0px calc(var(--size) * 1/3) var(--secondColor);
          }
        }

        
        .ToyarMomo{
          border-radius: 50%;
          width:  var(--size);
          height: var(--size);
          margin: auto;
          background-color: var(--mainColor);
          position: relative;
          display: flex;
          align-items: center;
          justify-content: center;
          transition: all .5s;
        }
        .ToyarMomo.ball{
          overflow: hidden;
        }
        .Momo-left_Ear{
          position: absolute;
          height: calc(var(--size)/2);
          width:  calc(var(--size)/5);
          background-color: var(--secondColor);
          left: 0;
          border-radius: 44% 49% 22% 65% / 100% 100% 0% 0%;
          /* border-radius: 170% 170% 5% 5%;   */
          transform: rotate(-35deg);
          transform-origin: bottom;
          top: calc(var(--size) * -2.8/7);
        }
        .Momo-left_Ear::after{
          content: '';
          display: inline-block;
          width: calc(var(--size) * 0.27);
          height: calc(var(--size) * 1/3);
          border-radius: 44% 49% 22% 65% / 100% 100% 0% 0%;
          background-color: var(--mainColor);
          position: absolute;
          transform: rotate(180deg);
          bottom: calc(var(--size) * -1/6);
          left: -15%;
          clip-path: polygon(36% 0, 80% -5%, 93% 0,
          95% 48%, 97% 68%, 94% 83%, 93% 86%,
          90% 92%,
          88% 95%, 91% 90%, 87% 95%, 81% 98%, 79% 98%, 78% 93%, 77% 88%, 76% 83%, 73% 76%, 70% 71%, 67% 66%, 56% 50%,
          56% 49%, 39% 70%, 34% 81%, 30% 96%, 27% 100%, 19% 94%, 16% 91%,
          11% 78%, 9% 73%, 5% 49%, 0 30%);
        }

        .Momo-right_Ear{
          position: absolute;
          height: calc(var(--size)/2);
          width:  calc(var(--size)/5);
          left: 0;
          top: 0;
          border-radius: 44% 49% 22% 65% / 100% 100% 0% 0%;
          /* border-radius: 170% 170% 5% 5%;   */
          transform: rotate(35deg);
          top: calc(var(--size) * -2.8/7);
          left: calc(var(--size) * 0.81);
          transform-origin: bottom;
          background-color: var(--secondColor);
          z-index: -1;
        }
        .Momo-right_Ear::after{
          content: '';
          display: inline-block;
          width: calc(var(--size) * 0.27);
          height: calc(var(--size) * 1/3);
          border-radius: 44% 49% 22% 65% / 100% 100% 0% 0%;
          background-color: var(--mainColor);
          position: absolute;
          transform: rotate(180deg);
          bottom: calc(var(--size) * -1/6);
          left: -15%;
          /* clip-path: polygon(
          28% 0, 
          35% 10%, 
          70% 0, 
          90% 40%, 
          100% 50%, 
          90% 85%, 
          90% 90%, 
          83% 100%, 
          50% 50%, 
          20% 100%, 
          0 43%); */
          clip-path: polygon(36% 0, 80% -5%, 93% 0,
          95% 48%, 97% 68%, 94% 83%, 93% 86%,
          90% 92%,
          88% 95%, 91% 90%, 87% 95%, 81% 98%, 79% 98%, 78% 93%, 77% 88%, 76% 83%, 73% 76%, 70% 71%, 67% 66%, 56% 50%,
          56% 49%, 39% 70%, 34% 81%, 30% 96%, 27% 100%, 19% 94%, 16% 91%,
          11% 78%, 9% 73%, 5% 49%, 0 30%);
        }
        .MomoBody{
          position: absolute;
          width: calc( var(--size) * 2/3); 
          height: calc( var(--size) * 2/3);
          background-color: #000;
          transform: rotate(45deg);
          margin-top: -10px;
          /* border-radius: 58% 42% 81% 19% / 54% 20% 80% 46%; */
          border-radius: 55% 45% 77% 23% / 54% 22% 78% 46% ;
          border:calc(var(--size) * 0.045 ) solid #2f6ead5e;
          z-index: 15;
        }
        .inner{
          transform: rotate(-45deg);
          position: absolute;
          width: calc( var(--size) * 2/3);
          height: calc( var(--size) * 2/3);
          overflow: hidden
        }
        .MomoGy{
          width: 30px;
          height: 18px;
          /* background: #fff; */
          /* background-color: linear-gradient(#fff, #000); */
          /* background: linear-gradient(#fff, #111); */
          background: repeating-radial-gradient(
            white,
            #000 18px
        );
          position: absolute;
          border-radius: 50%;
          left: 11px;
          top: 18px;
          transform: rotate(-40deg);
        }
        .ear-light{
          animation: startlight 2s  forwards;
        }
        .ear-error{
          /* animation: startError 2s  forwards; */
          /* var(--se) */
          --secondColor:red
        }
        .Momo-left_Eye,.Momo-right_Eye{
          width: calc(var(--size) * 1.5/15);
          border: 0px solid #000;
          border-top: 0px solid #000;
          border-bottom: 0px solid #000;
          background-color: var(--secondColor);
          height: calc( var(--size) * 12/75);
          box-sizing: border-box;
          position: absolute;
          top: 37%;
          
        }
        .Momo-left_Eye{
          left: 20%;
        }
        .Momo-right_Eye{
          left: 70%;
        }
        .momo-blink{
          animation: blink var(--blinkTime) linear;
        }
        .momo-leftRotate{
          animation: lRotate var(--blinkTime) linear;
        }
        .momo-rightRotate{
          animation: rRotate var(--blinkTime) linear;
        }

        .loading{
          height:100%;
          line-height: calc(var(--size ) * 2/3);
          text-align: center;
          color: var(--secondColor);;
          position: absolute;
          width: 100%;
          font-weight: bold;
          letter-spacing: 1px;
          font-size:calc(var(--size ) * 1/10);
        }
        .open{
          width: calc(var(--size) / 10);
          height:calc(var(--size) / 10);
          background-image: url('./src/close.svg');
          position: absolute;
          bottom: calc(var(--size) / 15);
          background-repeat: no-repeat;
          background-position: center;
          background-size: calc(var(--size) / 12) calc(var(--size) / 12);
          /* border: 2px solid #666; */
          border-radius: 50%;
          /* box-shadow: 0 0 3px var(--secondColor); */
        }
        .open:hover{
          cursor: pointer;
          /* box-shadow: 0px 0px 5px var(--secondColor); */
        }
        /* .leftVg{
          border: 3px solid #000;
          width: 50px;
          height: 50px;
          position: absolute;
          left: 0;
          bottom: 0;
          border-radius: 30% 0 30% 0;
        }
        .rightVg{
          border: 3px solid #000;
          width: 50px;
          height: 50px;
          position: absolute;
          right: 0;
          bottom: 0;
          border-radius: 0 30% 0 30% ;
        } */
        .dialog{
          width: 230px;
          display: inline-block;
          /* background-color: red; */
          position: absolute;
          left: 130%;
          top: -50%;
          border: 3px solid transparent;
          border-image: url("./src/border.png");
          border-width: 23px 35px 33px 35px;
          border-image-slice: 51 38 20 132;
          box-sizing: border-box;
          color: #fff;
          word-break: break-all;
          transition: all 0.5s;
        }
        .dialog:empty{
          /* display: none; */
          height: 0;
          border-width: 0;
        }
  </style>
  <script>
    class ToyarMomo {
      constructor(size,{
        mainColor='#cbe8d7',
        secondColor='#02feff',
      }) {
        this.size=size
        this.mainColor = mainColor
        this.secondColor = secondColor
        this.init()
      }
      init() {
        this.appendStyles()
        this.createMomo()
        this.startEyeFollow()
      }
      createLeftEar(){
        const leftEar = document.createElement('div')
        leftEar.className = 'Momo-left_Ear ear-light'
        this.leftEar =leftEar
        return leftEar
      }
      createRightEar(){
        const rightEar = document.createElement('div')
        rightEar.className = 'Momo-right_Ear ear-light'
        this.rightEar =rightEar
        return rightEar
      }
      createLeftEye(){
        const leftEye = document.createElement('div');
        leftEye.className ='Momo-left_Eye'
        this.leftEye = leftEye
        return leftEye
      }
      createRightEye(){
        const rightEye = document.createElement('div');
        rightEye.className ='Momo-right_Eye'
        this.rightEye = rightEye
        return rightEye
      }
      createInner(){
        const inner = document.createElement('div');
        inner.className='inner'
        this.inner = inner
        const leftEye = this.createLeftEye()
        const rightEye = this.createRightEye()
        inner.append(leftEye)
        inner.append(rightEye)
        return inner
      }
      createBody(){
        const body = document.createElement('div');
        body.className = 'MomoBody'
        this.body = body
        const inner =this.createInner()
        body.append(inner)
        return body
      }
      createMomo() {
        let main = document.createElement('main');
        main.id='Momo'
        main.className = 'ToyarMomo'
        main.style.setProperty('--size',this.size+'px')
        main.style.setProperty('--mainColor',this.mainColor)
        main.style.setProperty('--secondColor',this.secondColor)

        this.main = main
        const body= this.createBody()
        const leftEar= this.createLeftEar()
        const rightEar= this.createRightEar()
        main.append(body)
        main.append(leftEar)
        main.append(rightEar)

        const open =document.createElement('div')
        open.className='open'
        main.append(open)

        
        const leftVg =document.createElement('div')
        leftVg.className='leftVg'
        main.append(leftVg)

        const rightVg =document.createElement('div')
        rightVg.className='rightVg'
        main.append(rightVg)

        const dialog =document.createElement('div')
        dialog.className='dialog'
        this.dialog =dialog
        main.append(dialog)

        document.body.append(main)
        this.loading()
      }
      
      appendStyles() {
        let staticStyle = `
        `
        let style = document.createElement('style')
        style.innerText = staticStyle
        document.body.appendChild(style)
      }
      loading(){
        const loading = document.createElement('div')
        loading.className='loading'
        loading.innerText='Loading...'
        this.inner.innerHTML=''
        this.inner.append(loading)
        let left =0
        this.main.classList.add('ball')
        let timer = setInterval(()=>{
          left-=5
          if(left <=-this.size* 2/3 ){
            left=this.size*2/3
          }
          loading.style.left= left +'px'
        },100)
        setTimeout(()=>{
          clearInterval(timer)
          this.main.classList.remove('ball')
          this.say('主人,我回来咯!')
          this.open()
        },3000)
      }
      open(){
        this.inner.innerHTML=''
        this.inner.append(this.leftEye)
        this.inner.append(this.rightEye)
        this.startStatus()
      }
      mouseMove(e){
        const multiple = 80;
          const transformElement=(x, y)=> {
            let box =  this.rightEye.getBoundingClientRect();
            let calcX = (y - box.y - (box.height / 2)) / multiple;
            let calcY = (x - box.x - (box.width / 2)) / multiple;
            this.rightEye.style.transform  =
            `translate(${calcY}px, ${calcX}px)`
            this.leftEye.style.transform  =
            `translate(${calcY}px, ${calcX}px)`
          }
        window.requestAnimationFrame(function(){
              transformElement(e.clientX, e.clientY);
        });
      }
      startEyeFollow(){
          document.getElementsByTagName("body")[0].addEventListener('mousemove', (e) => {
           this.mouseMove(e)
          });
      }
      startStatus() {
        setInterval(() => {
          let data =Math.random()
          if (data > 0 && data < 0.4) {
            this.blink()
          }
          if(data>0.1 && data < 0.6){
            this.rotateEar()
          }
          if(data>0.6){
            let info = "啦啦啦啦啦啦啦啦啦~"
            if(data>0.7){
            function pad(timeEl, total = 2, str = '0') {
                return timeEl.toString().padStart(total, str)
            }
              function formaData(timer) {
                  const year = timer.getFullYear()
                  const month = timer.getMonth() + 1 // 由于月份从0开始,因此需加1
                  const day = timer.getDate()
                  const hour = timer.getHours()
                  const minute = timer.getMinutes()
                  const second = timer.getSeconds()
                  return `${pad(year, 4)}-${pad(month)}-${pad(day)} ${pad(hour)}:${pad(minute)}:${pad(second)}`
              }
              console.log(formaData(new Date()));
              info = formaData(new Date())
            }
            this.say(info)
          }
        }, 1000)
      }
      say(info){
        this.dialog.innerText=info
        setTimeout(()=>{
          this.dialog.innerHTML =''
        },1000)
      }
      blink(){
        this.leftEye.classList.toggle('momo-blink')
        this.rightEye.classList.toggle('momo-blink')
      }
      rotateEar(){
        this.leftEar.classList.toggle('momo-leftRotate')
        this.rightEar.classList.toggle('momo-rightRotate')
      }
      error(){
        if(this.main.style.getPropertyValue('--secondColor') === 'red'){
          return this.main.style.setProperty('--secondColor',this.secondColor)
        }
        this.main.style.setProperty('--secondColor','red')
      }
    }
    let momo = new ToyarMomo(150,{
      // mainColor:'#cbe8d7',
      // secondColor:'#02feff',
      // mainColor:'red',
      // secondColor:'blue'
    })
  </script>
</body>

</html>

完善桌宠

后面使用electron真正将这个demo做成了一个桌宠 当春节前不想写业务的前端会做什么(二) - 掘金 (juejin.cn)