05 - Vue3 UI Framework - Button 元件

Jeremy.Wu發表於2021-12-13

官網基本做好了,接下來開始做核心元件

返回閱讀列表點選 這裡

目錄準備

在專案 src 目錄下建立 lib 資料夾,用來存放所有的核心元件吧。然後再在 lib 資料夾下建立 Button.vue 檔案。

您也可以進行結構化設計,比如,這裡就不進行了。

|-lib
  |-Button
  	|- Button.vue
  	|- Button.ts
  	|_ Button.scss

需求分析

慣例先行需求分析

  1. 多種類基礎 Button,包含警告、成功、危險等
  2. 允許設定 Button 為禁用狀態
  3. 不止有傳統 Button,還可以有文字或連結形式
  4. 當處於載入中,Button 應當顯示
  5. 有不同的尺寸可供選擇
  6. 應當允許更換顏色
  7. 當滑鼠放置於 Button 上、滑鼠按下未鬆開、處於載入中等狀態時,應當變更背景色
  8. 允許使用者自定義 Button 上顯示的文字

那麼可以整理出以下參數列格

引數 含義 型別 可選值 預設值
level 預設型別 string default / plain / primary / success / info / warning / danger default
disabled 是否禁用 boolean false / true false
theme 式樣 string button / link / text button
loding 是否載入中 boolean false / true false
size 尺寸 string middle / small / large middle
color 顏色 string 任意合法顏色值 #f3678e

第 7 條,可以通過設定一個遮罩層來實現,只要遮罩層變色,背景色也等效變色

第 8 條,可以通過插槽實現,注意 vue3 不建議使用具名插槽

骨架

容易得到如下骨架

<template>
  <button
    class="jeremy-button"
    :theme="theme"
    :level="level"
    :size="size"
    :style="{ '--color': color }"
    :disabled="disabled"
    :loading="loading"
  >
    <div class="jeremy-button-mask"></div>
    <span class="jeremy-button-loadingIndicator" v-if="loading"></span>
    <slot></slot>
  </button>
</template>

首先,本質應當是一個 button 元素,在此基礎上,將引數列表中整理出來的每個引數,都使用 v-bind 繫結到 button

注意,此處繫結 color,必須是如上例一樣,繫結到 --color 屬性上,才可以在 css 中使用 css3 語法 var() 讀取,在 css 小節會再解釋,此處略

之後,在 button

  1. 放置一個遮罩層,用於變色
  2. 放置一個”載入中”的動畫,用於在載入中狀態下顯示
  3. 放置一個預設插槽,用於傳遞使用者自定義的文字

然後為上述元素配置各自的 class 名稱,骨架就完成了。

功能

顯然,引數列表中整理出來的內容,一定來自引用該元件的地方的傳入,先根據引數列表,寫好 ts 宣告:

declare const props: {
  theme?: "button" | "link" | "text";
  level?:
    | "default"
    | "plain"
    | "primary"
    | "success"
    | "info"
    | "warning"
    | "danger";
  size?: "middle" | "small" | "large";
  color: string;
  disabled: boolean;
  loading: boolean;
};

然後在 export default 中,寫入我們的引數

export default {
  install: function (Vue) {
    Vue.component(this.name, this);
  },
  name: "JeremyButton",
  props: {
    theme: {
      type: String,
      default: "button",
    },
    level: {
      type: String,
      default: "default",
    },
    size: {
      type: String,
      default: "middle",
    },
    color: {
      type: String,
      default: "#8c6fef",
    },
    disabled: {
      type: Boolean,
      default: false,
    },
    loading: {
      type: Boolean,
      default: false,
    },
  },
};

對於事件繫結,因為我們設計的元件只有一個唯一的根元素,所以對於外部傳遞過來的事件,會自動繫結到元件的根元素上面。

樣式表

注意 :UI 庫的樣式表一般不要加 scoped 修飾符,為了儘可能減少對使用者樣式表的影響,方便使用者 DIY

特別注意 : button 元素會有預設黑色外邊框,不屬於 border,必須通過 outline: none; 才能消除

然後,我們使用 css3var() 語法,取得我們通過 ts 繫結到 style 上的 --color 屬性

為什麼是 --color 而不是 color ?因為 var() 語法要求這個引數必須是 -- 開頭,才可以正常訪問到

對於遮罩層,採用淡出到白色即可實現,原理此處不解釋了

最後,對於多種不同的 button,可以使用 scss 提供的 mixin / include 語法來實現,完整程式碼如下:

$theme-color: var(--color);
$base-mask: fade-out(#fff, 0.7);
$active-mask: fade-out(#fff, 0.5);
$h: 32px;
$radius: 4px;

@keyframes jeremy-spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}
.jeremy-button {
  position: relative;
  display: inline-block;
  padding: 10px 16px;
  color: white;
  border-radius: $radius;
  border: none;
  font-size: 16px;
  cursor: pointer;
  white-space: nowrap;
  transition: background-color 250ms;
  outline: none;
  :focus {
    outline: none;
  }
  > .jeremy-button-mask {
    position: absolute;
    display: inline-block;
    height: 100%;
    width: 100%;
    left: 0;
    top: 0;
    border-radius: $radius;
    &:hover {
      background: $base-mask;
    }
  }
  &[loading="true"],
  &[disabled] {
    cursor: not-allowed;
    > .jeremy-button-mask {
      pointer-events: none;
    }
  }
  > .jeremy-button-loadingIndicator {
    width: 14px;
    height: 14px;
    display: inline-block;
    margin-right: 4px;
    border-radius: 8px;
    border-style: solid;
    border-width: 2px;
    animation: jeremy-spin 1s infinite linear;
  }
}

@mixin layout($color) {
  $loading-color: fade-out(black, 0.7);

  background: $color;

  &:active {
    > .jeremy-button-mask {
      background: $active-mask;
    }
  }
  > .jeremy-button-loadingIndicator {
    border-color: $loading-color $loading-color $loading-color transparent;
  }
  &[loading="true"],
  &[disabled] {
    > .jeremy-button-mask {
      background: $base-mask;
    }
  }
}
.jeremy-button[theme="button"] {
  $color: $theme-color;

  @include layout($color);
}
.jeremy-button:not([theme="button"]) {
  padding: 0;
  background: white;
  color: black;
  &:hover {
    color: $theme-color;
  }
}
.jeremy-button[theme="link"] {
  text-decoration: underline;
}
.jeremy-button[level="plain"] {
  $base-color: $theme-color;
  @include layout(white);
  color: black;
  > .jeremy-button-mask {
    border: 1px solid rgb(187, 187, 187);
  }

  &:not([loading="true"]):not([disabled]) {
    &:hover {
      > .jeremy-button-mask {
        border: 1px solid $base-color;
      }
      color: $base-color;
    }
  }
}
.jeremy-button[level="primary"] {
  $color: #29adfa;

  @include layout($color);
}
.jeremy-button[level="success"] {
  $color: rgb(103, 194, 58);

  @include layout($color);
}
.jeremy-button[level="info"] {
  $color: #808080;

  @include layout($color);
}
.jeremy-button[level="warning"] {
  $color: rgb(230, 162, 60);

  @include layout($color);
}
.jeremy-button[level="danger"] {
  $color: rgb(245, 108, 108);

  @include layout($color);
}
.jeremy-button[size="large"] {
  padding: 14px 24px;
}
.jeremy-button[size="small"] {
  padding: 6px 10px;
}

以上,button 元件就完成了! :happy:

測試一下

image-20211213131430347

感謝閱讀 ☕

相關文章