根據背景色自適應文本顏色

針對企業服務來講,最終用戶每每須要更加細化的信息分類方式,而打標籤無疑是很是好的解決方案。git

若是標籤僅僅只提供幾種顏色可能沒法知足各個用戶的實際需求。那麼系統就須要爲用戶提供顏色選擇。事實上咱們徹底沒法預知用戶選擇了何種顏色,那麼若是當前用戶選擇了黑色做爲背景色,同時當前的字體顏色也是黑色,該標籤就沒法使用。若是配置背景色的同時還要求用戶配置文字顏色,那麼這個標籤功能未免有些雞肋。讓用戶以爲咱們的開發水平有問題。github

因此須要尋找一種解決方案來搞定這個問題。算法

問題解析

對於彩色轉灰度,有一個著名的公式。咱們能夠把十六進制的代碼分紅3個部分,以得到單獨的紅色,綠色和藍色的強度。用此算法逐個修改像素點的顏色能夠將當前的彩色圖片變爲灰色圖像。typescript

gray = r * 0.299 + g * 0.587 + b * 0.114

可是針對明亮和陰暗的顏色,通過公式的計算後必定會得到不一樣的數值,而針對當前不一樣值,咱們取反就能夠獲得當前的文本顏色。即:緩存

const textColor = (r * 0.299 + g * 0.587 + b * 0.114) > 186 ? '#000' : '#FFF'

固然了,186 並非一個肯定的數值,你能夠根據本身的需求調整一個新的數值。經過該算法,傳入不一樣的背景色,就能夠獲得白色和黑色,或者自定義出比較合適的文本顏色。字體

完善代碼

固然,雖然解決的方法很是簡單,可是中間仍是涉及了一些進制轉換問題,這裏簡單傳遞數值以下所示。code

/**
 * @param backgroundColor 字符串 傳入  #FFFBBC | FBC | FFBBCC 都可
 */
export function contrastTextColor(backgroundHexColor: string) {
  let hex = backgroundHexColor
  
  // 若是當前傳入的參數以 # 開頭,去除當前的
  if (hex.startsWith('#')) {
    hex = hex.substring(1);
  }
  // 若是當前傳入的是 3 位小數值,直接轉換爲 6 位進行處理
  if (hex.length === 3) {
    hex = [hex[0], hex[0], hex[1], hex[1], hex[2], hex[2]].join('')
  }

  if (hex.length !== 6) {
    throw new Error('Invalid background color.' + backgroundHexColor);
  }

  const r = parseInt(hex.slice(0, 2), 16)
  const g = parseInt(hex.slice(2, 4), 16)
  const b = parseInt(hex.slice(4, 6), 16)
  
  if ([r,g,b].some(x => Number.isNaN(x))) {
     throw new Error('Invalid background color.' + backgroundHexColor);
  }

  const textColor = (r * 0.299 + g * 0.587 + b * 0.114) > 186 ? '#000' : '#FFF'
  return textColor
}

咱們還能夠在其中添加 rgb 顏色,以及轉換邏輯。圖片

/**
 * @param backgroundColor 字符串
 */
export function contrastTextColor(backgroundHexColor: string) {
  // 均轉換爲 hex 格式, 能夠傳入 rgb(222,33,44)。
  // 若是當前字符串參數長度大於 7 rgb(,,) 最少爲 8 個字符,則認爲當前傳入的數值爲 rgb,進行轉換
  const backgroundHexColor = backgroundColor.length > 7 ? convertRGBToHex(backgroundColor) : backgroundColor
  
  // ... 後面代碼
}

/** 獲取背景色中的多個值,即 rgb(2,2,2) => [2,2,2] */
const rgbRegex = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/

/** 轉換 10 進製爲 16 進制, 
  * 計算完成後時字符串前面加 0,同時取後兩位數值。使得返回的數值必定是 兩位數
  * 如 E => 0E  |  FF => 0FF => FF
  */
const hex = (x: string) => ("0" + parseInt(x).toString(16)).slice(-2);

function convertRGBToHex(rgb: string): string {
  const bg = rgb.match(rgbRegex);
  
  if (!bg) {
    // 返回空字符串,在後面判斷長度爲 6 時候會報錯。不在此處進行操做
    return ''
  }
  
  return ("#" + hex(bg[1]) + hex(bg[2]) + hex(bg[3])).toUpperCase();
}

固然了,咱們也能夠在其中添加緩存代碼,以便於減小計算量。ip

// 使用 map 來緩存 
const colorByBgColor = new Map()
// 緩存錯誤字符串
const CACHE_ERROR = 'error'

export function contrastTextColor(backgroundColor: string) {
  // 獲取緩存
  const cacheColor = colorByBgColor.get(backgroundColor)
  if (cacheColor) {
    // 當前緩存錯誤,直接報錯
    if (cacheColor === CACHE_ERROR) {
      throw new Error('Invalid background color.' + backgroundColor);
    }
    return colorByBgColor.get(backgroundColor)
  }
  
  // ...
  if (hex.length !== 6) {
    // 直接緩存錯誤
    colorByBgColor.set(backgroundColor, CACHE_ERROR)
    throw new Error('Invalid background color.' + backgroundColor);
  }
  
  // ...
  
  if ([r,g,b].some(x => Number.isNaN(x))) {
    // 直接緩存錯誤
    colorByBgColor.set(backgroundColor, CACHE_ERROR)
    throw new Error('Invalid background color.' + backgroundColor);
  }

  const textColor = (r * 0.299 + g * 0.587 + b * 0.114) > 186 ? '#000' : '#FFF'
  // 緩存數據
  colorByBgColor.set(backgroundColor, textColor)
  return textColor
}

完整代碼能夠在代碼庫中 轉換問題顏色 中看到。開發

固然了,若是你不須要嚴格遵循 W3C 準則,當前代碼已經足夠使用。可是若是你須要嚴格遵循你能夠參考 http://stackoverflow.com/a/39... 以及 https://www.w3.org/TR/WCAG20/

鼓勵一下

若是你以爲這篇文章不錯,但願能夠給與我一些鼓勵,在個人 github 博客下幫忙 star 一下。
博客地址

參考資料

stackoverflow 問題

相關文章
相關標籤/搜索