React Hooks 起手式,實現一個「高顏值實用」的色彩設計工具

無影er發表於2019-01-14

Build Status

本文意在記錄一次個人實踐,背景是最近公司新起了一個 React 技術棧的專案,因此沒有歷史包袱,可以嘗試使用框架的新特性 React hook 來實現 React 元件。

調色盤Pro

在考慮做什麼時候,正好看到 Ant-Design 的色彩設計,相信使用 React 的童鞋對 Ant-Design設計語言和風格是比較清楚的,本文實踐的 Demo 是使用 React hook 新特性編寫了一個幫助設計童鞋生成平滑色彩漸變的設計工具 Color-Design-Helper 。 生成規則是參考 Ant-Design色彩 ,有興趣的童鞋可以先行閱讀官方文件。

倉庫:https://github.com/zerolty/color-design-helper

體驗地址: http://zerolty.com/color-design-helper/

需求與實現

記得幾個月前閱讀過一篇介紹 Ant-Design 調色盤演進的文章《 Ant Design 色板生成演算法演進之路》,閱讀完之後,想對現有的調色盤進行增強,因此做這個工具實踐方向上的需求和實現是

  1. 自定義“色級”
  2. 自定義主色
  3. 實現平均法、tint-shade 法、和 HSV 遞進演算法(antd現在使用的)
  4. 使用 React Hooks 抽象value-setValue的邏輯
  5. 顏色轉化方法實現

自定義“色級”,更細的粒度,更多的可能

不知道這樣描述是否準確,Ant Design 的基礎色板共計 120 個顏色,包含 12 個主色以及衍生色。如果我使用平均法和 tint-shade 法,工具可以通過設計人員自己定製step得到更多級的過渡色,從而更好的滿足視覺需求。下圖是 Ant-Design 提供的色板生成工具和工具tint-shade 25色的對比。

React Hooks 起手式,實現一個「高顏值實用」的色彩設計工具

React Hooks 起手式,實現一個「高顏值實用」的色彩設計工具

自定義主色

這是 Ant-Design 提供的色板生成工具已經支援的了,除了官方推薦的共計 120 個顏色,你也可以自己定義主色。

image

三種演算法

平均法、tint-shade 法、和 HSV 遞進演算法(antd現在使用的)

平均法

顧名思義,我可以選擇兩種顏色,設定好步驟數,等量平均地從第一種顏色向第二種顏色過渡。 比如我們選取的顏色分別是#ffffff#000000,我們先將HEX轉化為RGB,分別是(255, 255, 255)和(0, 0, 0),在將0-255按照選擇的step依次平均獲得相應的色帶。

React Hooks 起手式,實現一個「高顏值實用」的色彩設計工具

tint-shade

這是第一版 Ant-Design 的實現,思路上是把純黑和純白和主色分別混合,生成一條常規的漸變色帶。比如我們的主色是#1890ff,“色級”是10,將其轉化為 RGB 後與#000000按照平均法混合,得到一條色帶,取 20%、40%、60%、80%、100%五種色和#fffffff按照平均法混合,得到一條色帶,取 20%、40%、60%、80%、100%五種色拼接組成我們的tint-shade色帶。

React Hooks 起手式,實現一個「高顏值實用」的色彩設計工具

import avgMix from './avg-color'; // 平均函式
function tintShade(color, granularity) {
    granularity = Number(granularity);
    const mid = Math.round(granularity/2); // 取中間的值
    const mixWithWhite = granularity % 2 === 0  // 奇偶情況
        ? avgMix([255, 255, 255], color, mid).slice(1, mid+1) 
        : avgMix([255, 255, 255], color, mid).slice(1, mid);
    const mixWithBlack = granularity % 2 === 0 
        ? avgMix(color, [0, 0, 0], mid).slice(1, mid+1)
        : avgMix(color, [0, 0, 0], mid).slice(0, mid);
   
    return [...mixWithWhite, ...mixWithBlack];  // 合併黑白混合
    
}

複製程式碼

HSV 遞進演算法(antd現在使用的)

React Hooks 起手式,實現一個「高顏值實用」的色彩設計工具

選定主色後,我們使用 HSV 模型,將HEX格式的主色轉化成H、S、V。

const hsv = new TinyColor({ r: color[0], g: color[1], b: color[2] }).toHsv();
複製程式碼

第二步是生成我們色帶需要數量的“生成色”陣列,並將明暗線設定在60%的位置

for(let i = 1, len = granularity; i <= len; i++) {  // “生成色”數
        colors.push(
            colorPalette(color, i, Math.round((granularity)*0.6)) // 明暗線設定在60%的位置
        );
}

// HSV的優化
const isLight = index <= num; // 判斷明暗

複製程式碼

第三步 直接跟著程式碼看對H、S、V的處理,也可以直接看官方實現原始碼

const getHue = function (hsv, i, isLight) {
    let hue;
    if (hsv.h >= 60 && hsv.h <= 240) {
        // 冷色調
        // 減淡變亮 色相順時針旋轉 更暖
        // 加深變暗 色相逆時針旋轉 更冷
        hue = isLight ? hsv.h - hueStep * i : hsv.h + hueStep * i;
    } else {
     // 暖色調
    // 減淡變亮 色相逆時針旋轉 更暖
    // 加深變暗 色相順時針旋轉 更冷
        hue = isLight ? hsv.h + hueStep * i : hsv.h - hueStep * i;
    }
    if (hue < 0) {
        hue += 360;
    } else if (hue >= 360) {
        hue -= 360;
    }
    return Math.round(hue);
};
// 每個生成顏色的S(飽和)計算,飽和在一定範圍內折中,還原比較真實的視覺感受
const getSaturation = function (hsv, i, isLight) {
    let saturation;
    if (isLight) {
        // 減淡變亮 飽和度迅速降低
        saturation = Math.round(hsv.s * 100) - saturationStep * i;
    } else if (i === darkColorCount) {
       // 加深變暗-最暗 飽和度提高
        saturation = Math.round(hsv.s * 100) + saturationStep;
    } else {
        // 加深變暗 飽和度緩慢提高
        saturation = Math.round(hsv.s * 100) + saturationStep2 * i;
    }
    if (saturation > 100) {
        saturation = 100;
    }
    if (isLight && i === lightColorCount && saturation > 10) {
        saturation = 10;
    }
    if (saturation < 6) {
        saturation = 6;
    }
    return Math.round(saturation);
};
// 每個生成顏色的V計算,冷色調,變亮;暖色反之
const getValue = function (hsv, i, isLight) {
    if (isLight) {
        // 減淡變亮
        return Math.round(hsv.v * 100) + brightnessStep1 * i;
    }
    // 加深變暗幅度更大
    return Math.round(hsv.v * 100) - brightnessStep2 * i;
};

複製程式碼

React-Hooks

在編寫這個工具的時候,通過元件拆分是非常簡單的三部分,Step 顯示和色帶是兩個純的render,因此我們很容易想到把狀態都放在頂部元件。另外我們的顏色輸入框、range選擇器、演算法選擇器三個控制元件是有狀態的,valuesetValue需要從頂部元件下發下去,而在以往不使用 React Hooks 的主元件應該是這樣

<input type="color" value="state,xxx" onChange="onXXXChange" />
複製程式碼

這裡可以用 State Hooks 抽象 Input 元件的狀態和狀態控制邏輯,Hooks 是React 16.7.0-alpha.0嘗試使用的新特性,官方文件是最好的參考 React hook ,英文閱讀不適可以看秋風翻譯的React Hooks詳解翻譯。這個工具按照正規化只使用一個函式,沒有 class 和 state ,非常順滑。

使用 react-hook ,全域性有三個控制型元件使用 input ,我們也可以把 input 不要看成是原生元件而是一個render-props,useInputValue第一個引數是初始值,第二個引數setOr是供篩選使用暴露出的設定方法,可以在外層手動修改狀態,如果不加篩選,setDisabled, setValue將被傳到inputprops上, React 會丟擲Warning提示 Input 沒有定義對應的 Props 。

// define hook function
import { useState, useCallback } from "react";

export default function useInputValue(initiateValue, setOr) {
    const [value, setValue] = useState(initiateValue);
    const [disabled, setDisabled] =  useState(false);
    let onChange = useCallback(e => setValue(e.target.value), []);
    if(setOr) return { value, onChange, disabled, setDisabled, setValue };
    return { value, onChange, disabled };
    
}

// usage
const startInputHook = useInputValue('#ffffff', true);
const endInputHook = useInputValue('#1890ff');
const stepInputHook = useInputValue(1);
const radiosHook = useInputValue('avg');
...
<input type="color" {...filterStartInputHook} disabled={startInputHook.disabled} />
<input type="text" {...filterStartInputHook} disabled={startInputHook.disabled}/>
<input type="range" min={MIN} max={MAX} {...stepInputHook} />

複製程式碼

顏色轉化

先明確我們用到的三種顏色格式

  • HEX: 我們常見的#000000這類,rgb 的16進位制組合表達
  • RGB:0-255組成的三原色引數
  • HSV:HSL 和 HSV 都是一種將RGB色彩模型中的點在圓柱座標系中的表示法。這兩種表示法試圖做到比基於笛卡爾座標系的幾何結構RGB更加直觀。色相(H)是色彩的基本屬性,就是平常所說的顏色名稱,如紅色、黃色等。飽和度(S)是指色彩的純度,越高色彩越純,低則逐漸變灰,取0-100%的數值。明度(V),亮度(L),取0-100%。

RGB 與 HEX、HSV之間的轉化

RGB 與 HEX 之間轉化使用toString(16)parseInt(x, 16)即可

// HEX to RGB
const hexArray = hex => /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); // 正則取出rgb的十六進位制
const rgbObjectFromHex = hex => {
    var result = hexArray(hex)
    return result ? {
        r: parseInt(result[1], 16),
        g: parseInt(result[2], 16),
        b: parseInt(result[3], 16),
    } : null;
}
const rgbArrayFromHex = hex => {
    const rgb = rgbObjectFromHex(hex);
    return [rgb.r, rgb.g, rgb.b];  // rgb的陣列
}

// RGB to HEX
function rgbChannelToHex (channel) {
    const hex = channel.toString(16);
    return hex.length === 1 ? `0${hex}` : hex;
}

function rgbToHex (r, g, b) {
    return `#${rgbChannelToHex(r)}${rgbChannelToHex(g)}${rgbChannelToHex(b)}`;
}

function rgbArrayToHex (color) {
    return rgbToHex(color[0], color[1], color[2]);
}

複製程式碼

RGB 與 HSV 的轉化可以參考公式演算法,原始碼可見:HSV-RGB

React Hooks 起手式,實現一個「高顏值實用」的色彩設計工具 rgb to hsv React Hooks 起手式,實現一個「高顏值實用」的色彩設計工具

我在實踐後,發現一些浮點情況邊界處理的不好,之後使用這個顏色轉化的庫來實踐,省去部分成本tinycolor

反差色文字顏色

暗底白字與明底黑字實現是通過計算 RGB 判斷明暗,從而顯示白色或黑色

export default function brightness(c) {
    const color = ((c[0] * 299 + c[1] * 587 + c[2] * 114) / 1000) < 154 ? '#ffffff' : '#000000';
    return color;
}
複製程式碼

寫在最後

歡迎小夥伴們,點評和指出不足一起探討問題,另外我和@藍色的秋風大佬正在共建一個偏 handsome 的小工具最佳實踐,如果你是熱愛技術、並有想法參與技術共建歡迎和我們聯絡。

React Hooks 起手式,實現一個「高顏值實用」的色彩設計工具

打賞

React Hooks 起手式,實現一個「高顏值實用」的色彩設計工具

友情連結

無影er 秋風 阿米狗

相關文章