背景
在开发开源库vue-design-editor过程中,需要一款设置画布背景色或其他元素颜色的颜色选择器, 在 github 中看大部分只支持 vue2, 不支持 vue3, 也不支持渐变色设置, 千辛万苦找到了一款优秀的开源颜色选择器库vue3-colorpicker, 本来心满意足的在项目中使用, 不过在开发过程中也发现了两大缺陷
缺陷一

不支持多点渐变
缺陷二
linear-gradient(0deg, rgba(241, 98, 126, 1) 0%, rgba(0, 0, 0, 1) 100%)
以上是返回的渐变数据,是一个只局限于 dom 元素的背景样式, 扩展性底, 如果想支持以下格式
            
            
              js
              
              
            
          
          colorStops: [
  // 定义渐变颜色的数组
  { offset: 0, color: "red" },
  { offset: 0.2, color: "orange" },
  { offset: 0.4, color: "yellow" },
  { offset: 0.6, color: "green" },
  { offset: 0.8, color: "blue" },
  { offset: 1, color: "purple" },
];
        需要正则匹配或使用gradient-parser解析, 复杂度上升
为了解决以上两个问题只能 pull 源代码进行魔改
为什么不fork
原仓库已不更新, 如果fork后在github上就搜索不到该仓库,为了更多人使用优化后的库,减少重复造轮子, 所以重新创建了单独仓库
仓库地址: github.com/haixin-fang...
多点渐变支持
数据结构优化
从渐变组件源代码可以看出, 只定义了开始和结束两个节点数据, 该数据结构不满足在原有基础上进行逻辑修改, 所以重构了渐变数据结构
原有结构
            
            
              js
              
              
            
          
          const gradientState = reactive({
  startColor,
  endColor,
  startColorStop: 0,
  endColorStop: 100,
  angle: 0,
  type: "linear",
  gradientColor: props.gradientColor,
});
        重写结构
            
            
              js
              
              
            
          
            const gradientState = reactive<any>({
    colors: [],
    angle: 0,
    type: "linear",
    gradientColor: props.gradientColor,
  });
        使用数组结构来存储渐变的多点数据
新增节点
color-scales的作用是该在具有定义的最小值和最大值的两个颜色端点之间以线性渐变形式返回值的颜色。
就不用新增一个节点就初始化一个固定色值, 过渡自然
也需要通过点击事件的位置计算出在渐变区间内的固定位置,作为断点值
            
            
              js
              
              
            
          
          const handlePotBar = (e: MouseEvent) => {
      if (refColorBar.value) {
        const barBounding = refColorBar.value.getBoundingClientRect();
        const barLeft = barBounding.left;
        const colorPotDist = e.pageX - barLeft;
        const value = cloneDeep(state.colors);
        // 渐变条stopColors;
        const rangColors = value
          .sort((a: any, b: any) => a.pst - b.pst)
          .map((item: any) => {
            return item.toHexString();
          });
        // 初始化色条Range,用来取渐变色值
        const colorScale = new ColorScale(0, barBounding.width, rangColors);
        const colorPotHex = colorScale.getColor(colorPotDist).toHexString();
        const colorPotPst = (100 * colorPotDist) / barBounding.width;
        const addPot = new Color(colorPotHex);
        addPot.pst = colorPotPst;
        state.colors.push(addPot);
        // 增加后默认选中
        state.selectIndex = state.colors.length - 1;
        emit("gradientChange", state.colors);
      }
};
        节点滑动交互
每个节点支持通过鼠标拖动位置
绑定mousedown事件
            
            
              html
              
              
            
          
                    <div
              class="vc-gradient__stop"
              v-for="(item, index) in colors"
              :key="index"
              :class="{
                'vc-gradient__stop--current': index == state.selectIndex,
              }"
              ref="startGradientRef"
              :style="{ left: `calc(${item.pst + '%'} - 8px)` }"
              @mousedown="sliderPotDown(index, $event)"
              @click="clickGColorPot(index)"
            >
              <span class="vc-gradient__stop--inner"></span>
          </div>
        滑动交互
持久存储mousedown落下的位置, 通过监听mousemove事件实时获取鼠标移动的位置, 计算出和mousedown初始位置的距离实时改变节点数组的pst(节点位置)字段, vue通过数据双向绑定来修改节点dom的位置, 实现节点随鼠标滑动的交互
useEventListener 是vueuse提供的hooks, 轻松使用事件监听。在挂载时使用 addEventListener 注册,在卸载时自动使用 removeEventListener 。
            
            
              js
              
              
            
          
              const handleEleMouseMove = (e: MouseEvent) => {
      if (!isSelectBoxMouseDown) return;
      state.movePst.x = e.pageX - state.mouseStartPst.x;
      state.movePst.y = e.pageY - state.mouseStartPst.y;
      state.pageX = e.pageX;
      state.pageY = e.pageY;
      sliderMove();
    };
    const sliderMove = () => {
      if (refColorBar.value) {
        const barWidth = refColorBar.value.getBoundingClientRect().width;
        let distRatio =
          ((state.startMovePst * barWidth) / 100 + state.movePst.x) / barWidth;
        if (distRatio > 1) {
          distRatio = 1;
        } else if (distRatio < 0) {
          distRatio = 0;
        }
        state.colors[state.selectIndex].pst = Math.round(distRatio * 100);
        emit("gradientChange", state.colors);
      }
    };
    const handleEleMouseUp = () => {
      isSelectBoxMouseDown = false;
      sliderDone();
    };
    const sliderDone = () => {
      resetDraggle();
    };
    const resetDraggle = () => {
      isSelectBoxMouseDown = false;
      state.mouseStartPst = { x: 0, y: 0 };
      state.movePst.x = 0;
      state.movePst.y = 0;
    };
    const bindEventsDoc = () => {
      useEventListener(document.body, "mousemove", handleEleMouseMove);
      useEventListener(document.body, "mouseup", handleEleMouseUp);
    };
    const clickGColorPot = (index: Number) => {
      if (state.selectIndex === index) return;
      state.selectIndex = index;
    };
    const sliderStart = () => {
      state.startMovePst = state.colors[state.selectIndex].pst;
    };
    const sliderPotDown = (index: Number, $event: MouseEvent) => {
      bindEventsDoc();
      const e: MouseEvent = $event;
      clickGColorPot(index);
      isSelectBoxMouseDown = true;
      state.mouseStartPst.x = e.pageX;
      state.mouseStartPst.y = e.pageY;
      sliderStart();
    };
        发布npm
使用npm publish 发布到npm中, 这样在vue-design-editor项目中可以直接通过npm install方式进行安装
为什么包名是colorpickers, 因为我能想到的vue-color, vuecolorpicker等各种名称已被占用
文档更新
原有的文档是使用storybook开发的, 所以基于该文档,增加了两个配置和优化了事件回调, 原文档的事件回调数据未打印,造成事件回调数据不直观,影响体验
新增两个配置
- 渐变底层数据, 提高该库的扩展性
 - 渐变模块线性或经向按钮可自定义展示
 
            
            
              js
              
              
            
          
              gradientData: {
      control: "text",
      description: "Get gradient details",
    },
    gradientType: {
      type: "string",
      description: "linear | radial | both",
      control: { type: "select" },
      options: ["radial", "linear", "both"],
      table: {
        defaultValue: {
          summary: "both",
        },
      },
    },
        
            
            
              js
              
              
            
          
          import { action } from "@storybook/addon-actions";
{
  template:
      '<div class="demo">' +
      '<ColorPicker v-model:pureColor="pureColor" v-model:gradientColor="gradientColor" v-bind="args" @activeKeyChange="activeKeyChange" @gradientColorChange="gradientColorChange" @gradientDataChange="onChange" @pureColorChange="pureColorChange" />' +
      "</div>",
    methods: {
      onChange: action("gradientDataChange"),
      pureColorChange: action("pureColorChange"),
      gradientColorChange: action("gradientColorChange"),
      activeKeyChange: action("activeKeyChange"),
    }
}
        haixin-fang.github.io/colorpicker...
workflow
使用github的工作流功能, 每一次更新代码并提交到github中都会自动更新文档
            
            
              yaml
              
              
            
          
          name: GitHub Actions Build and Deploy
on:
  push:
    branches:
      - main
jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v1
        with:
          persist-credentials: false
      - name: Install and Build
        run: |
          npm install --force
          npm run-script build-storybook
      - name: Deploy
        uses: JamesIves/github-pages-deploy-action@3.7.1
        with:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          BRANCH: gh-pages
          FOLDER: storybook-static
          CLEAN: true
        新特性总结
- 支持多点渐变
 - 渐变绑定的数据是linear-gradient字符串,不满足个性化需求,提供了渐变底层数据,可以使用gradientData数据双向绑定或者gradientDataChange方法获取
 - 支持线性或经向渐变按钮自定义展示,通过 gradientType: "linear | radial | both" 控制
 - 修复透明度输入框有小数问题
 - 文档支持事件打印,可直观看数据结构和事件方法
 - 可长期维护
 
安装与使用
在vue-design-editor项目中使用以下命令进行安装
            
            
              csharp
              
              
            
          
          yarn add colorpickers
        OR
npm install colorpickers
        使用
            
            
              vue
              
              
            
          
          <template>
  <!-- :gradientType="'linear'" -->
  <color-picker
    :pureColor="pureColor"
    :gradientColor="gradientColor"
    :isWidget="true"
    :useType="useType"
    @pureColorChange="onPureColor"
    @gradientDataChange="gradientColorChange"
  />
</template>
<script setup>
import { ColorPicker } from "colorpickers";
import "colorpickers/style.css";
import { defineProps, defineEmits } from "vue";
const emit = defineEmits(["select"]);
defineProps({
  useType: { type: String, default: "both" },
  pureColor: {
    type: String,
    default: "red",
  },
  gradientColor: {
    type: String,
    default: "",
  },
});
function onPureColor(color) {
  emit("select", {
    fill: color,
  });
}
function gradientColorChange(color) {
  emit("select", {
    gradient: color,
  });
}
</script>
        最终效果

比搞定设计多了经向渐变背景色设置
简介
vue-design-editor 是仿搞定设计的一款开源图片编辑器, 支持多种格式的导入,包括png、jpg、gif、mp4, 也可以一键psd转模板(后续开发)
上个开源库是 starfish-vue3-lowcode