首頁 > 軟體

buildAdmin開源專案引入四種圖示方式詳解

2023-02-03 18:01:13

正文

在專案開發中,我們經常使用可能都是UI元件庫裡的圖示,當然由於業務需要,可能當前圖示庫沒有我們需要的圖示這時候就需要引入其它圖示庫的圖示,比如iconfont、FontAweSome、本地圖示庫。在瞭解引入這些圖示庫之前,我們先學習一下各種圖示庫的引入使用:

Element-Plus:由於elemen官方已經把圖示封裝成了元件,所以當我們引入圖示的時候,需要全域性宣告元件。

import * as Icons from '@element-plus/icons';
const app = createApp(App);
// 全域性註冊圖示,犧牲一點效能
for (let i in Icons) {
// 官方圖示名稱首字母都是大寫,所以轉為小寫,並命名元件未el-icon-圖示名
app.component(`el-icon-${toLine(i)}`, (Icons as any)[i]);
}
// 元件中使用圖示
<el-icon-user />

Iconfont:阿里巴巴圖示庫,通過建立一個專案目錄,然後把我們需要的圖示新增進去,然後在專案中引入圖示目錄的cdn(三種方式之一:css程式碼、css連結、js連結)就可以使用了,如果沒有特殊處理一般是在專案的index.html中引入相關連結,css程式碼可以在根目錄的樣式檔案中引入。然後就可以在專案中使用:

<i class="iconfont icon-user"></i>

FontAwesome:一個比較好用的字型圖示庫,可以直接通過cdn引入,也可以通過安裝package包引入,然後就可以使用了:

英文官網:fontawesome.com/search

中文網:fontawesome.com.cn/

<i class="fa fa-user"></i>

參照本地圖示:一般使用svg格式圖示,因為svg效能好,相對於其它格式,它體積更小,可以任意放大圖形顯示,不以犧牲圖示質量為代價,專案中是不能直接載入svg格式,需要額外外掛實現(後面會詳細介紹)。

<svg class="svg-icon icon" style="width: 1em;height: 1em;color: black;">
<use href="#local-vue" rel="external nofollow"  rel="external nofollow"  />
</svg>

為了方便維護以及擴充套件,我們可以把四種圖示封裝為統一元件使用,在封裝前我們需要明確三點:

  • 獲取所有圖示。
  • 實現圖示可複製,複製就可用的原則。
  • 圖示使用統一元件。

在學習各類的圖示庫之前我們先了解一下如何封裝一下圖示共用元件,它向外暴露的名稱是Icon:

實現元件健壯性、易維護:支援圖示名稱(name)、圖示顏色(color)、圖示大小(size)三要素的自定義。四種圖示格式引入為element-plus(el-icon-iconName)、iconfont(iconfont iconName)、fontawesome(fa fa-iconName)、本地圖示(local-iconName),iconName是圖示名稱。

props: {
name: {
  type: String,
  required: true,
},
size: {
  type: String,
  default: '30px',
},
color: {
  type: String,
  default: '#00000',
},
},
// 處理樣式,去掉多餘px命名
const iconStyle = computed((): CSSProperties => {
  const { size, color } = props;
  let s = `${size.replace('px', '')}px`;
  return {
    fontSize: s,
    color: color,
  };
});

相容上面四種圖示實現:通過Vue3的setup的返回值中使用渲染函數實現,不需要在template中定義標籤使用,分三種情況。

createVNode函數:建立虛擬節點,從左到右有三個引數:html標籤名稱或元件(String)、標籤屬性(Object)、巢狀標籤定義(Array)。

對於element-plus圖示的渲染:官方是通過el-icon標籤內直接使用圖示元件,所以建立虛擬節點標籤就是el-icon,由於圖示元件是巢狀的,所以需要用到第三引數。

setup(props) {
    // 當前引入的是element-plus圖示
    if (props.name.indexOf('el-icon-') === 0) {
      return () =>
        createVNode(
          'el-icon',
          { class: 'icon el-icon', style: iconStyle.value },
          [createVNode(resolveComponent(props.name))]
        );
    }
 }

對於iconfont、fontawesome圖示的渲染:由於使用這兩種的圖示的標籤都是i,它們唯一不同就是圖示名稱的命名所以可以共用同一個渲染函數。

setup(props){
 // 當前引入的是iconfont或fontawesome圖示
 if (props.name.indexOf('local-') === 0 || isExternal(props.name)) {
 return () =>
    createVNode('i', {
      class: [props.name, 'icon'],
      style: iconStyle.value,
    });
  }
}

對於本地svg圖示的渲染:直接引入本地封裝的svg元件,把這個元件當作渲染標籤,這裡圖示命名以local-iconName格式引入的。

setup(props){
 // 當前引入的是本地svg圖示
 if (props.name.indexOf('local-') === 0 || isExternal(props.name)) {
  return () =>
    createVNode(svg, {
      name: props.name,
      size: props.size,
      color: props.color,
    });
  }
}

最終就可以通過這樣使用圖示:

<Icon name="" color="" size=""/>

其實上面就已經實現了四種圖示的型別統一封裝,一致使用。接下來為了更方便獲取圖示,我們把所有圖示封裝起來就可以直接cv使用了。

點選實現cv方式:通過點選圖示傳入我們想要複製的內容,一般都是整個元件的字串。

export const useCopy = (text: string) => {
  let input = document.createElement('input'); // 建立輸入框
  input.value = text; // 給輸入框value賦值
  document.body.appendChild(input); // 追加到body裡面去
  input.select(); // 選擇輸入框的操作
  document.execCommand('Copy'); // 執行復制操作
  document.body.removeChild(input); // 刪除加入的輸入框
  ElMessage.success('複製成功!');
};

引入Element-Plus圖示庫

import * as elIcons from '@element-plus/icons-vue';
// 獲取所有Element-Plus圖示元件名稱,如搜尋圖示Search
export function getElementPlusIconfontNames() {
  return new Promise<string[]>((resolve, reject) => {
    nextTick(() => {
      const iconfonts = [];
      const icons = elIcons as any;
      // 遍歷新增icons元件名稱
      for (const i in icons) {
        iconfonts.push(icons[i].name);
      }
      if (iconfonts.length > 0) {
        resolve(iconfonts);
      } else {
        reject('No ElementPlus Icons');
      }
    });
  });
}

引入Iconfont圖示庫

先載入圖示樣式表:

const cssUrls: Array<string> = [
  '//at.alicdn.com/t/c/font_3846007_vf3shrhbpya.css', // 阿里圖示庫cs,每新增一次圖示都需要更換
  '//cdn.bootcdn.net/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css', // font-awesome的css
];
export default function init() {
  // 遍歷載入圖示連結樣式
  if (cssUrls.length > 0) {
    cssUrls.map((v) => {
      loadCss(v);
    });
  }
}
// 通過建立link標籤引入樣式連結
export function loadCss(url: string): void {
  const link = document.createElement('link'); // 建立link標籤
  link.rel = 'stylesheet';
  link.href = url;
  // 是否採用跨域的方式載入。它可以取兩個值
  // anonymous(跨域請求時,不傳送使用者憑證,主要是 Cookie)
  // use-credentials(跨域時傳送使用者憑證)。
  link.crossOrigin = 'anonmous';
  document.getElementsByTagName('head')[0].appendChild(link);
}

獲取當前頁面中從指定域名載入到的樣式表內容:在呼叫這個函數之前必須要先引入樣式。

// 獲取樣式表內容
function getStylesFromDomain(domain: string) {
  const sheets = [];
  const styles: StyleSheetList = document.styleSheets;
  for (const key in styles) {
    if (styles[key].href && (styles[key].href as string).indexOf(domain) > -1) {
      sheets.push(styles[key]);
    }
  }
  return sheets;
}

呼叫這個函數之後就可以獲取到當前圖示庫相關樣式表內容,我們的目的就是拿到圖示名稱,可以提取rules陣列內的樣式名稱就可以拿到所有圖示名稱了。

// 獲取所有iconfont圖示庫圖示名稱
export function getIconfontNames() {
  init();
  return new Promise<string[]>((resolve, reject) => {
    nextTick(() => {
      const iconfonts = [];
      const sheets = getStylesFromDomain('at.alicdn.com');
      for (const key in sheets) {
        const rules: any = sheets[key].cssRules;
        for (const k in rules) {
          // .表示匹配除換行符 n 之外的任何單字元
          // *表示單個字元匹配任意次
          if (
            rules[k].selectorText &&
            /^.icon-(.*)::before$/g.test(rules[k].selectorText)
          ) {
            // 去掉樣式的.符號以及::before
            iconfonts.push(
              `${rules[k].selectorText
                .substring(1, rules[k].selectorText.length)
                .replace(/::before/gi, '')}`
            );
          }
        }
      }
      if (iconfonts.length > 0) {
        resolve(iconfonts);
      } else {
        reject('No Iconfont style sheet');
      }
    });
  });
}

引入FontAwesome圖示庫

先載入圖示樣式表:也就是直接呼叫init函數,載入相應的樣式連結。

獲取當前頁面中從指定域名載入到的樣式表內容:如下圖所示。

export function getAwesomeIconfontName() {
  init();
  return new Promise<string[]>((resolve, reject) => {
    nextTick(() => {
      const iconfonts = [];
      // 獲取所有圖示名稱
      const sheets = getStylesFromDomain(
        'cdn.bootcdn.net/ajax/libs/font-awesome/'
      );
      for (const key in sheets) {
        const rules: any = sheets[key].cssRules;
        // 處理方法與iconfont一致,只不過名稱不一樣
        for (const k in rules) {
          if (
            rules[k].selectorText &&
            /^.fa-(.*)::before$/g.test(rules[k].selectorText)
          ) {
            if (rules[k].selectorText.indexOf(', ') > -1) {
              // selectorText裡有多個圖示,只提取第一個
              const iconNames = rules[k].selectorText.split(', ');
              iconfonts.push(
                `${iconNames[0]
                  .substring(1, iconNames[0].length)
                  .replace(/::before/gi, '')}`
              );
            } else {
              iconfonts.push(
                `${rules[k].selectorText
                  .substring(1, rules[k].selectorText.length)
                  .replace(/::before/gi, '')}`
              );
            }
          }
        }
      }
      if (iconfonts.length > 0) {
        resolve(iconfonts);
      } else {
        reject('No AwesomeIcon style sheet');
      }
    });
  });
}

引入本地svg圖示

在引入本地svg圖示之前,我們先了解一下svg標籤的相關知識:


IT145.com E-mail:sddin#qq.com