雲間玉兔,自出機抒,從零開始製作Web外掛網頁特效小兔子元件(小掛件widget),基於原生CSS/NPM

劉悅的技術部落格發表於2023-01-12

著意登樓瞻玉兔,何人張幕遮銀闕?又到了一年一度的網頁小掛件環節,以往我們都是整合別人開源的元件,但所謂熟讀唐詩三百首,不會做詩也會吟,熟讀了別人的東西,做幾首打油詩也是可以的,但若不能自出機抒,卻也成不了大事,所以本次我們從零開始製作屬於自己的網頁小掛件,博君一曬。

玉兔主題元素繪製

成本最低的繪製方式是使用純CSS,不依賴任何圖片和三方庫,首先建立繪製容器:

<div id="rabbit_box">  
    
  
  
</div>

由於是小掛件,我們首先將容器固定在右下角:

#rabbit_box{  
  
    position: fixed;  
    bottom: var(--pos,5%);   
    right: 35px;   
    z-index: 99;   
    border: none;   
    outline: none;   
    filter: drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));  
  
  }

這裡加了一層filter濾鏡,可以讓玉兔更加立體。

考慮到複用性和可移植性,將主題元素動態新增到容器中:

function rabbit_init(){  
  
    var container = document.getElementById("rabbit_box");  
  
    container.innerHTML = '<div class="rabbit"><div class="rabbit__leg rabbit__leg--one"></div><div class="rabbit__leg rabbit__leg--two"></div><div class="rabbit__tail"></div><div class="rabbit__body"></div><div class="rabbit__leg rabbit__leg--three"></div><div class="rabbit__leg rabbit__leg--four"></div><div class="rabbit__ear rabbit__ear--right"></div><div class="rabbit__head"></div><div class="rabbit__ear rabbit__ear--left"></div></div>';  
  
     
  
}  
  
rabbit_init()

這裡玉兔元素由八個小元件構成,分別是頭部,四肢,四爪,兩隻耳朵,眼睛,尾巴以及嘴。

隨後設定CSS樣式:

.rabbit .rabbit__body {  
    width: 4em;  
    height: 5.6em;  
    background: #F4F4F4;  
    border-radius: 50% 50% 50% 50%/60% 60% 40% 40%;  
    transform: rotate(-40deg);  
    box-shadow: inset -2.3em -2.7em 0 0 var(--theme-color,#D2DAEE);  
  }

兔子身體元素透過border-radius來獲得圓潤的曲線,同時使用transform旋轉元素得到一個適合的角度。最後透過box-shadow屬性來設定顏色,顏色可以自定義,如果沒有自定義則使用預設值#D2DAEE,注意旋轉角度需要指定單位:deg。

接著繪製頭部:

.rabbit .rabbit__head {  
    position: absolute;  
    width: 4em;  
    height: 4.6em;  
    top: -2.5em;  
    left: -2em;  
    background: var(--theme-color,#e1e6f4);  
    border-radius: 50% 50% 50% 50%/65% 60% 40% 35%;  
    transform: rotate(-120deg);  
    overflow: hidden;  
  }  
  .rabbit .rabbit__head:before {  
    content: "";  
    position: absolute;  
    width: 0.65em;  
    height: 0.5em;  
    top: -0.1em;  
    left: 1.8em;  
    background: #F97996;  
    border-radius: 50% 50% 50% 50%/30% 30% 70% 70%;  
    transform: rotate(130deg);  
  }  
  .rabbit .rabbit__head:after {  
    content: "";  
    position: absolute;  
    width: 1em;  
    height: 1em;  
    top: 1.5em;  
    left: 1.6em;  
    background: #F4F4F4;  
    border-radius: 50%;  
    box-shadow: inset 0.1em 0.15em 0 0.37em #e2262e;  
  }

這裡透過::before 和 ::after 偽元素在兔子頭部元素的前面或後面插入內容,頭部前面繪製兔嘴,後面則插入兔子眼睛,之所以這樣控制,是因為可以靈活的使用box-shadow填充顏色。

接著繪製耳朵:

.rabbit .rabbit__ear {  
    position: absolute;  
    border-radius: 50% 50% 50% 50%/40% 40% 60% 60%;  
    transform-origin: 50% 100%;  
  }  
  .rabbit .rabbit__ear--left {  
    width: 2.2em;  
    height: 4.7em;  
    top: -5.7em;  
    left: -0.2em;  
    background: #F3E3DE;  
    transform: rotate(60deg);  
    box-shadow: inset 0.3em -0.4em 0 -0.1em var(--theme-color,#c7d1ea);  
    -webkit-animation: ear-left 3s infinite ease-out;  
            animation: ear-left 3s infinite ease-out;  
  }  
  .rabbit .rabbit__ear--right {  
    width: 2em;  
    height: 4.7em;  
    top: -5.5em;  
    left: -0.7em;  
    background: var(--theme-color,#D2DAEE);  
    transform: rotate(20deg);  
    -webkit-animation: ear-right 3s infinite ease-out;  
            animation: ear-right 3s infinite ease-out;  
  }

@-webkit-keyframes ear-left {  
    0%, 20%, 100% {  
      transform: rotate(40deg);  
    }  
    10%, 30%, 80% {  
      transform: rotate(45deg);  
    }  
    90% {  
      transform: rotate(50deg);  
    }  
  }  
    
  @keyframes ear-left {  
    0%, 20%, 100% {  
      transform: rotate(40deg);  
    }  
    10%, 30%, 80% {  
      transform: rotate(45deg);  
    }  
    90% {  
      transform: rotate(50deg);  
    }  
  }  
  @-webkit-keyframes ear-right {  
    0%, 20%, 100% {  
      transform: rotate(10deg);  
    }  
    10%, 30%, 80% {  
      transform: rotate(5deg);  
    }  
    90% {  
      transform: rotate(0deg);  
    }  
  }  
  @keyframes ear-right {  
    0%, 20%, 100% {  
      transform: rotate(10deg);  
    }  
    10%, 30%, 80% {  
      transform: rotate(5deg);  
    }  
    90% {  
      transform: rotate(0deg);  
    }  
  } 

這裡透過-webkit-animation屬性讓兔子左右耳在3秒內進行來回擺動,達到一種動態效果,注意左耳內側顏色固定為:#F3E3DE,同時動畫會影響元素的佈局,需要注意元素的寬高。

最後就是四肢和尾巴:

.rabbit .rabbit__leg {  
    position: absolute;  
  }  
  .rabbit .rabbit__leg--one {  
    width: 0.8em;  
    height: 3em;  
    top: 2.3em;  
    left: 0.2em;  
    background: var(--theme-color,#c7d1ea);  
    border-radius: 50% 50% 50% 50%/30% 30% 70% 70%;  
    transform-origin: 50% 0%;  
    transform: rotate(15deg);  
  }  
  .rabbit .rabbit__leg--one:before {  
    content: "";  
    position: absolute;  
    width: 0.8em;  
    height: 0.5em;  
    top: 2.6em;  
    left: -0.2em;  
    background: #f3f6ff;  
    border-radius: 50% 50% 50% 50%/70% 70% 30% 30%;  
    transform: rotate(-10deg);  
  }  
  .rabbit .rabbit__leg--three {  
    width: 0.9em;  
    height: 3em;  
    top: 2.4em;  
    left: 0.7em;  
    background: var(--theme-color,#e1e6f4);  
    border-radius: 50% 50% 50% 50%/30% 30% 70% 70%;  
    transform-origin: 50% 0%;  
    transform: rotate(10deg);  
  }  
  .rabbit .rabbit__leg--three:before {  
    content: "";  
    position: absolute;  
    width: 0.8em;  
    height: 0.5em;  
    top: 2.6em;  
    left: -0.2em;  
    background: #f3f6ff;  
    border-radius: 50% 50% 50% 50%/70% 70% 30% 30%;  
    transform: rotate(-10deg);  
  }  
  .rabbit .rabbit__leg--two {  
    width: 2.6em;  
    height: 3.6em;  
    top: 1.7em;  
    left: 1.6em;  
    background: #c7d1ea;  
    border-radius: 50% 50% 50% 50%/50% 50% 50% 50%;  
    transform-origin: 50% 0%;  
    transform: rotate(10deg);  
  }  
  .rabbit .rabbit__leg--two:before {  
    content: "";  
    position: absolute;  
    width: 1.6em;  
    height: 0.8em;  
    top: 3.05em;  
    left: 0em;  
    background: #f3f6ff;  
    border-radius: 50% 50% 50% 50%/70% 70% 30% 30%;  
    transform: rotate(-10deg);  
  }  
  .rabbit .rabbit__leg--four {  
    width: 2.6em;  
    height: 3.6em;  
    top: 1.8em;  
    left: 2.1em;  
    background: var(--theme-color,#e1e6f4);  
    border-radius: 50% 50% 50% 50%/50% 50% 50% 50%;  
    transform-origin: 50% 0%;  
    transform: rotate(10deg);  
  }  
  .rabbit .rabbit__leg--four:before {  
    content: "";  
    position: absolute;  
    width: 1.6em;  
    height: 0.8em;  
    top: 3.05em;  
    left: 0em;  
    background: #f3f6ff;  
    border-radius: 50% 50% 50% 50%/70% 70% 30% 30%;  
    transform: rotate(-10deg);  
  }  
  .rabbit .rabbit__tail {  
    position: absolute;  
    width: 0.9em;  
    height: 0.9em;  
    top: 3.7em;  
    left: 4em;  
    background: var(--theme-color,#D2DAEE);  
    transform: rotate(25deg);  
  }  
  .rabbit .rabbit__tail:after, .rabbit .rabbit__tail:before {  
    content: "";  
    position: absolute;  
    width: 100%;  
    height: 100%;  
    background: var(--theme-color,#D2DAEE);  
    border-radius: 50%;  
  }  
  .rabbit .rabbit__tail:before {  
    top: 0;  
    left: -50%;  
  }  
  .rabbit .rabbit__tail:after {  
    top: 50%;  
    left: 0;  
  }

這裡四肢和四爪的顏色應該有差異,四肢顏色可以自定義,四爪固定為白色,以達到“四蹄踏雪”的效果。

接著改造初始化函式,使其可以動態更改顏色:

function rabbit_init(color=null,pos=null){  
  
    var container = document.getElementById("rabbit_box");  
  
    container.innerHTML = '<div class="rabbit"><div class="rabbit__leg rabbit__leg--one"></div><div class="rabbit__leg rabbit__leg--two"></div><div class="rabbit__tail"></div><div class="rabbit__body"></div><div class="rabbit__leg rabbit__leg--three"></div><div class="rabbit__leg rabbit__leg--four"></div><div class="rabbit__ear rabbit__ear--right"></div><div class="rabbit__head"></div><div class="rabbit__ear rabbit__ear--left"></div></div>';  
  
    if(color != null){  
  
        document.documentElement.style.setProperty("--theme-color",color);  
  
    }  
    if(pos != null){  
  
        document.documentElement.style.setProperty("--pos",pos);  
  
    }  
  
}  
  
rabbit_init("pink")

最終效果:

開源釋出

現在我們將這個開源特效打包上線,首先建立專案目錄:

mkdir rabbit

隨後將特效的樣式CSS程式碼以及JS程式碼分別抽離出來:rabbit.css:

.rabbit {  
    position: relative;  
  }  
  .rabbit .rabbit__body {  
    width: 4em;  
    height: 5.6em;  
    background: #F4F4F4;  
    border-radius: 50% 50% 50% 50%/60% 60% 40% 40%;  
    transform: rotate(-40deg);  
    box-shadow: inset -2.3em -2.7em 0 0 var(--theme-color,#D2DAEE);  
  }  
  .rabbit .rabbit__head {  
    position: absolute;  
    width: 4em;  
    height: 4.6em;  
    top: -2.5em;  
    left: -2em;  
    background: var(--theme-color,#e1e6f4);  
    border-radius: 50% 50% 50% 50%/65% 60% 40% 35%;  
    transform: rotate(-120deg);  
    overflow: hidden;  
  }  
  .rabbit .rabbit__head:before {  
    content: "";  
    position: absolute;  
    width: 0.65em;  
    height: 0.5em;  
    top: -0.1em;  
    left: 1.8em;  
    background: #F97996;  
    border-radius: 50% 50% 50% 50%/30% 30% 70% 70%;  
    transform: rotate(130deg);  
  }  
  .rabbit .rabbit__head:after {  
    content: "";  
    position: absolute;  
    width: 1em;  
    height: 1em;  
    top: 1.5em;  
    left: 1.6em;  
    background: #F4F4F4;  
    border-radius: 50%;  
    box-shadow: inset 0.1em 0.15em 0 0.37em #e2262e;  
  }  
  .rabbit .rabbit__ear {  
    position: absolute;  
    border-radius: 50% 50% 50% 50%/40% 40% 60% 60%;  
    transform-origin: 50% 100%;  
  }  
  .rabbit .rabbit__ear--left {  
    width: 2.2em;  
    height: 4.7em;  
    top: -5.7em;  
    left: -0.2em;  
    background: #F3E3DE;  
    transform: rotate(60deg);  
    box-shadow: inset 0.3em -0.4em 0 -0.1em var(--theme-color,#c7d1ea);  
    -webkit-animation: ear-left 3s infinite ease-out;  
            animation: ear-left 3s infinite ease-out;  
  }  
  .rabbit .rabbit__ear--right {  
    width: 2em;  
    height: 4.7em;  
    top: -5.5em;  
    left: -0.7em;  
    background: var(--theme-color,#D2DAEE);  
    transform: rotate(20deg);  
    -webkit-animation: ear-right 3s infinite ease-out;  
            animation: ear-right 3s infinite ease-out;  
  }  
  .rabbit .rabbit__leg {  
    position: absolute;  
  }  
  .rabbit .rabbit__leg--one {  
    width: 0.8em;  
    height: 3em;  
    top: 2.3em;  
    left: 0.2em;  
    background: var(--theme-color,#c7d1ea);  
    border-radius: 50% 50% 50% 50%/30% 30% 70% 70%;  
    transform-origin: 50% 0%;  
    transform: rotate(15deg);  
  }  
  .rabbit .rabbit__leg--one:before {  
    content: "";  
    position: absolute;  
    width: 0.8em;  
    height: 0.5em;  
    top: 2.6em;  
    left: -0.2em;  
    background: #f3f6ff;  
    border-radius: 50% 50% 50% 50%/70% 70% 30% 30%;  
    transform: rotate(-10deg);  
  }  
  .rabbit .rabbit__leg--three {  
    width: 0.9em;  
    height: 3em;  
    top: 2.4em;  
    left: 0.7em;  
    background: var(--theme-color,#e1e6f4);  
    border-radius: 50% 50% 50% 50%/30% 30% 70% 70%;  
    transform-origin: 50% 0%;  
    transform: rotate(10deg);  
  }  
  .rabbit .rabbit__leg--three:before {  
    content: "";  
    position: absolute;  
    width: 0.8em;  
    height: 0.5em;  
    top: 2.6em;  
    left: -0.2em;  
    background: #f3f6ff;  
    border-radius: 50% 50% 50% 50%/70% 70% 30% 30%;  
    transform: rotate(-10deg);  
  }  
  .rabbit .rabbit__leg--two {  
    width: 2.6em;  
    height: 3.6em;  
    top: 1.7em;  
    left: 1.6em;  
    background: #c7d1ea;  
    border-radius: 50% 50% 50% 50%/50% 50% 50% 50%;  
    transform-origin: 50% 0%;  
    transform: rotate(10deg);  
  }  
  .rabbit .rabbit__leg--two:before {  
    content: "";  
    position: absolute;  
    width: 1.6em;  
    height: 0.8em;  
    top: 3.05em;  
    left: 0em;  
    background: #f3f6ff;  
    border-radius: 50% 50% 50% 50%/70% 70% 30% 30%;  
    transform: rotate(-10deg);  
  }  
  .rabbit .rabbit__leg--four {  
    width: 2.6em;  
    height: 3.6em;  
    top: 1.8em;  
    left: 2.1em;  
    background: var(--theme-color,#e1e6f4);  
    border-radius: 50% 50% 50% 50%/50% 50% 50% 50%;  
    transform-origin: 50% 0%;  
    transform: rotate(10deg);  
  }  
  .rabbit .rabbit__leg--four:before {  
    content: "";  
    position: absolute;  
    width: 1.6em;  
    height: 0.8em;  
    top: 3.05em;  
    left: 0em;  
    background: #f3f6ff;  
    border-radius: 50% 50% 50% 50%/70% 70% 30% 30%;  
    transform: rotate(-10deg);  
  }  
  .rabbit .rabbit__tail {  
    position: absolute;  
    width: 0.9em;  
    height: 0.9em;  
    top: 3.7em;  
    left: 4em;  
    background: var(--theme-color,#D2DAEE);  
    transform: rotate(25deg);  
  }  
  .rabbit .rabbit__tail:after, .rabbit .rabbit__tail:before {  
    content: "";  
    position: absolute;  
    width: 100%;  
    height: 100%;  
    background: var(--theme-color,#D2DAEE);  
    border-radius: 50%;  
  }  
  .rabbit .rabbit__tail:before {  
    top: 0;  
    left: -50%;  
  }  
  .rabbit .rabbit__tail:after {  
    top: 50%;  
    left: 0;  
  }  
    
  @-webkit-keyframes ear-left {  
    0%, 20%, 100% {  
      transform: rotate(40deg);  
    }  
    10%, 30%, 80% {  
      transform: rotate(45deg);  
    }  
    90% {  
      transform: rotate(50deg);  
    }  
  }  
    
  @keyframes ear-left {  
    0%, 20%, 100% {  
      transform: rotate(40deg);  
    }  
    10%, 30%, 80% {  
      transform: rotate(45deg);  
    }  
    90% {  
      transform: rotate(50deg);  
    }  
  }  
  @-webkit-keyframes ear-right {  
    0%, 20%, 100% {  
      transform: rotate(10deg);  
    }  
    10%, 30%, 80% {  
      transform: rotate(5deg);  
    }  
    90% {  
      transform: rotate(0deg);  
    }  
  }  
  @keyframes ear-right {  
    0%, 20%, 100% {  
      transform: rotate(10deg);  
    }  
    10%, 30%, 80% {  
      transform: rotate(5deg);  
    }  
    90% {  
      transform: rotate(0deg);  
    }  
  }  
  
  #rabbit_box{  
  
    position: fixed;  
    bottom: var(--pos,5%);   
    right: 35px;   
    z-index: 99;   
    border: none;   
    outline: none;   
    filter: drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));  
  
  }

rabbit.js程式碼:

(function (name, context, fn) {  
    if (typeof module != 'undefined' && module.exports) {  
        // Node 環境  
        module.exports = fn();  
    } else if (typeof context['define'] == 'function' && (context['define']['amd'] || context['define']['cmd'])) {  
        // Require.js 或 Sea.js 環境  
        define(fn);  
    } else {  
        // client 環境  
        context[name] = fn();  
    }  
})('rabbit_init', this, function () {  
    return function (color=null,pos=null) {  
          
  
        var container = document.getElementById("rabbit_box");  
  
    container.innerHTML = '<div class="rabbit"><div class="rabbit__leg rabbit__leg--one"></div><div class="rabbit__leg rabbit__leg--two"></div><div class="rabbit__tail"></div><div class="rabbit__body"></div><div class="rabbit__leg rabbit__leg--three"></div><div class="rabbit__leg rabbit__leg--four"></div><div class="rabbit__ear rabbit__ear--right"></div><div class="rabbit__head"></div><div class="rabbit__ear rabbit__ear--left"></div></div>';  
  
    if(color != null){  
  
        document.documentElement.style.setProperty("--theme-color",color);  
  
    }  
    if(pos != null){  
  
        document.documentElement.style.setProperty("--pos",pos);  
  
    }  
  
  
    }  
});

儲存在專案的lib目錄。

首先將專案提交到Github: https://github.com/zcxey2911/rabbit

隨後執行命令填寫NPM配置:

npm init

entry point 配置項填寫你的入口檔案:

entry point: ./lib/rabbit.js

登入NPM賬號,隨後釋出:

npm login  
npm publish

登入之前,最好將切換回預設源,否則無法登入:

npm config set registry=https://registry.npmjs.com

釋出成功後,檢視釋出內容:https://www.npmjs.com/package/rabbit-widget

開源庫引入和使用

首先需要引入模組,可以使用 CDN 直接引入或者透過 NPM 包的形式安裝。

直接引入:

<!-- https://cdn.jsdelivr.net/gh/zcxey2911/rabbit@v1.0.0/lib/rabbit.css -->  
<!-- https://cdn.jsdelivr.net/gh/zcxey2911/rabbit@v1.0.0/lib/rabbit.js -->  
  
<link  
  rel="stylesheet"  
  href="https://cdn.jsdelivr.net/gh/zcxey2911/rabbit@v1.0.0/lib/rabbit.css"  
/>  
  
<div id="rabbit_box">  
</div>  
  
<script>  
  
function init_rabbit(){  
  
 rabbit_init("pink","20%");  // 粉色 高度20%  
  
 //rabbit_init(); //預設顏色 預設位置  
  
}  
  
</script>  
  
  
<script  
  async  
  onload="init_rabbit()"  
  src="https://cdn.jsdelivr.net/gh/zcxey2911/rabbit@v1.0.0/lib/rabbit.js"  
></script>

NPM 包的形式安裝:

// npm install --save rabbit-widget  
import 'rabbit-widget/lib/rabbit.css';  
  
var rabbit_init = require('rabbit-widget');  
  
rabbit_init();

如果使用NPM匯入模組的形式引入,請確保頁面載入完畢之後執行再執行rabbit_init();,否則會報錯:Uncaught TypeError: Cannot set properties of null (setting 'innerHTML')。

這裡以Vue.js3.0元件為例子:

<template>  
  <a-layout class="layout">  
    <a-layout-header>  
      <div class="logo" />  
  
      <ad_header />  
        
  
  
    </a-layout-header>  
    <a-layout-content style="padding: 0 50px">  
      <a-breadcrumb style="margin: 16px 0">  
        <a-breadcrumb-item>廣告平臺</a-breadcrumb-item>  
        <a-breadcrumb-item>首頁</a-breadcrumb-item>  
  
      </a-breadcrumb>  
      <div :style="{ background: '#fff', padding: '24px', minHeight: '280px' }">  
  
  
        這裡是首頁  
  
  
        <div id="rabbit_box"></div>  
  
  
    </div>  
    </a-layout-content>  
    <a-layout-footer style="text-align: center">  
      線上廣告平臺  
    </a-layout-footer>  
  </a-layout>  
</template>  
  
<script>  
  
import ad_header from './ad_header';  
  
import 'rabbit-widget/lib/rabbit.css';  
  
var rabbit_init = require('rabbit-widget');  
  
  
export default {  
 data() {  
    return {  
  
       
  
    }  
  },  
  //宣告子元件  
  components:{  
  
    'ad_header':ad_header  
  
  
  },  
  methods:{  
  
     
  
  
  },  
  created(){  
  
  
    this.$nextTick(() => {  
    console.log("頁面載入完啦~")  
  
    rabbit_init();  
})  
  
  
  }  
  
}  
</script>  
<style>  
.site-layout-content {  
  min-height: 280px;  
  padding: 24px;  
  background: #fff;  
}  
#components-layout-demo-top .logo {  
  float: left;  
  width: 120px;  
  height: 31px;  
  margin: 16px 24px 16px 0;  
  background: rgba(255, 255, 255, 0.3);  
}  
.ant-row-rtl #components-layout-demo-top .logo {  
  float: right;  
  margin: 16px 0 16px 24px;  
}  
  
[data-theme='dark'] .site-layout-content {  
  background: #141414;  
}  
</style>

專案中引入效果:

結語

奉上專案程式碼,與眾親同饗:https://github.com/zcxey2911/rabbit https://www.npmjs.com/package/rabbit-widget ,最後祝各位鄉親祥瑞玉兔,人機平安,願諸君2023年武運昌隆,前端一統。

相關文章