Vue3颜色选择器

最近项目内需要一个颜色选择器,需要Hex8位,所以根据网上资源还有等自己封装了一个颜色选择器组件,以便后续能够使用等。

效果图:

 

<!--
 * @Description: 
 * @Version: 1.0.0.1
 * @Autor: wufeng
 * @Date: 2023-05-18 15:25:21
 * @LastEditors: wufeng
 * @LastEditTime: 2023-05-18 15:26:12
-->
<template>
  <div class="color-select">
    <div
      class="saturation-value"
      ref="saturation_value"
      @mousedown="mousedownSV"
    >
      <div :style="`background-color: hsl(${hue}, 100%, 50%);`">
        <div class="point" :style="pointStyle"></div>
      </div>
      <div class="saturation-value-2"></div>
      <div class="saturation-value-3"></div>
    </div>
    <div class="color-select-middle">
      <div style="flex: auto">
        <div class="hue-slider" ref="hue_slider" @mousedown="mousedownHue">
          <div class="slider" :style="hueSliderStyle"></div>
        </div>
        <div
          class="alpha-slider"
          ref="alpha_slider"
          @mousedown="mousedownAlpha"
        >
          <div class="slider" :style="alphaSliderStyle"></div>
          <div
            :style="`background: linear-gradient(to right, rgba(0,0,0,0), ${colorObj.rgb});width: 100%;height: 100%`"
          ></div>
        </div>
      </div>
      <div class="color-diamond">
        <div
          :style="`background-color: ${colorObj.rgba};width: 100%;height: 100%;box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .15), inset 0 0 4px rgba(0, 0, 0, .25);`"
        ></div>
      </div>
    </div>
    <div class="color-value">
      <div class="hex">
        <label>
          <input :value="colorObj.hex8" @input="hexChange" spellcheck="false" />
        </label>
        <p>Hex</p>
      </div>
      <div class="rgba-r">
        <label>
          <input :value="red" @input="redChange" />
        </label>
        <p>R</p>
      </div>
      <div class="rgba-g">
        <label>
          <input :value="green" @input="greenChange" />
        </label>
        <p>G</p>
      </div>
      <div class="rgba-b">
        <label>
          <input :value="blue" @input="blueChange" />
        </label>
        <p>B</p>
      </div>
      <div class="rgba-a">
        <label>
          <input :value="alpha" @input="alphaChange" />
        </label>
        <p>A</p>
      </div>
    </div>
    <ul class="preset">
      <li
        v-for="item in preset"
        :key="item"
        :style="`background-color: ${item}`"
        @click="presetChange(item)"
      ></li>
    </ul>
  </div>
</template>

<script>
import { ref, computed, watch, onMounted } from "vue";

export default {
  name: "MyColorSelect",
  props: {
    color: {
      type: Object || String,
      default() {
        return {
          r: 217,
          g: 128,
          b: 95,
          a: 1,
        };
      },
    },
    preset: {
      type: Array,
      default() {
        return [
          "#D0021B",
          "#F5A623",
          "#F8E71C",
          "#8B572A",
          "#7ED321",
          "#417505",
          "#BD10E0",
          "#9013FE",
          "#4A90E2",
          "#50E3C2",
          "#B8E986",
          "#000000",
          "#4A4A4A",
          "#9B9B9B",
          "#FFFFFF",
        ];
      },
    },
  },
  setup(props, context) {
    const refs = {
      saturation_value: ref(null),
      hue_slider: ref(null),
      alpha_slider: ref(null),
    };
    let pointStyle = ref("top: 25%;left: 80%;");
    let hueSliderStyle = ref("left: 0;");
    let alphaSliderStyle = ref("left: calc(100% - 6px);");

    let hue = ref(0);
    let saturation = ref(1);
    let value = ref(1);

    let red = ref(255);
    let green = ref(0);
    let blue = ref(0);

    let alpha = ref(1);

    onMounted(() => {
      let { r, g, b, a } = parseColor(props.color);
      red.value = r;
      green.value = g;
      blue.value = b;
      alpha.value = a;
    });

    watch([red, green, blue], (newValue) => {
      context.emit("update:color", {
        r: red.value,
        g: green.value,
        b: blue.value,
        a: alpha.value,
      });

      let { h, s, v } = rgb2hsv(red.value, green.value, blue.value);

      hue.value = h;
      saturation.value = s;
      value.value = v;

      // 移动背景板圆圈
      pointStyle.value = `top: ${100 - v * 100}%;left: ${s * 100}%;`;
      // 移动色调滑块
      hueSliderStyle.value = `left: ${(hue.value / 360) * 100}%;`;
    });

    watch(alpha, () => {
      context.emit("update:color", {
        r: red.value,
        g: green.value,
        b: blue.value,
        a: alpha.value,
      });
      // 移动透明度滑块
      alphaSliderStyle.value = `left: ${
        alpha.value >= 1 ? "calc(100% - 6px)" : alpha.value * 100 + "%"
      };`;
    });

    let colorObj = computed(() => {
      let r = red.value;
      let g = green.value;
      let b = blue.value;
      let a = alpha.value;
      let h = hue.value;
      let s = saturation.value;
      let v = value.value;
      return {
        rgb: `rgba(${r},${g},${b})`,
        rgba: `rgba(${r},${g},${b},${a})`,
        hex6: rgba2hex(r, g, b),
        hex8: rgba2hex(r, g, b, a),
        hsv: `hsv(${h},${s},${v})`,
        hsl: ``,
      };
    });

    // 输入框值变化,限制输入的值
    function hexChange(e) {
      let v = e.target.value;
      if (/^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(v)) {
        let { r, g, b, a } = hex2rgba(v);
        red.value = r;
        green.value = g;
        blue.value = b;
        alpha.value = a;
      }
    }

    function redChange(e) {
      let v = e.target.value;
      if (v !== "") {
        v > 255 && (red.value = 255);
        v < 0 && (red.value = 0);
        v >= 0 && v <= 255 && (red.value = parseInt(v));
      }
    }

    function greenChange(e) {
      let v = e.target.value;
      if (v !== "") {
        v > 255 && (green.value = 255);
        v < 0 && (green.value = 0);
        v >= 0 && v <= 255 && (green.value = parseInt(v));
      }
    }

    function blueChange(e) {
      let v = e.target.value;
      if (v !== "") {
        v > 255 && (blue.value = 255);
        v < 0 && (blue.value = 0);
        v >= 0 && v <= 255 && (blue.value = parseInt(v));
      }
    }

    function alphaChange(e) {
      let v = e.target.value;
      if (v !== "") {
        v = parseFloat(v);
        alpha.value = v;
        v > 1 && (alpha.value = 1);
        v < 0 && (alpha.value = 0);
        v >= 0 && v <= 1 && (alpha.value = v);
      }
    }

    // 点击预设方块事件
    function presetChange(item) {
      if (/^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(item)) {
        let { r, g, b, a } = hex2rgba(item);
        red.value = r;
        green.value = g;
        blue.value = b;
        alpha.value = a;
      }
    }

    // 饱和度和亮度
    function handleChangeSV(e) {
      let w = refs.saturation_value.value.clientWidth;
      let h = refs.saturation_value.value.clientHeight;
      let x =
        e.pageX - refs.saturation_value.value.getBoundingClientRect().left;
      let y = e.pageY - refs.saturation_value.value.getBoundingClientRect().top;
      x = x < w && x > 0 ? x : x > w ? w : 0;
      y = y < h && y > 0 ? y : y > h ? h : 0;
      // 计算饱和度和亮度
      saturation.value = Math.floor((x / w) * 100 + 0.5) / 100;
      value.value = Math.floor((1 - y / h) * 100 + 0.5) / 100;
      // hsv转化为rgb
      let { r, g, b } = hsv2rgb(hue.value, saturation.value, value.value);
      red.value = r;
      green.value = g;
      blue.value = b;
      // 移动背景板圆圈
      pointStyle.value = `top: ${y}px;left: ${x}px;`;
    }

    function mousedownSV(e) {
      // 鼠标按下计算饱和度和亮度并添加事件
      handleChangeSV(e);
      // 添加整个页面的鼠标事件
      window.addEventListener("mousemove", handleChangeSV);
      window.addEventListener("mouseup", mouseupSV);
    }

    function mouseupSV(e) {
      // 鼠标松开后移除事件
      window.removeEventListener("mousemove", handleChangeSV);
      window.removeEventListener("mouseup", mouseupSV);
    }

    // 色调
    function handleChangeHue(e) {
      let w = refs.hue_slider.value.clientWidth;
      let x =
        e.pageX - refs.saturation_value.value.getBoundingClientRect().left;
      x = x < w && x > 0 ? x : x > w ? w : 0;
      // 计算色调
      hue.value = Math.floor((x / w) * 360 + 0.5);
      // hsv转化为rgb
      let { r, g, b } = hsv2rgb(hue.value, saturation.value, value.value);
      red.value = r;
      green.value = g;
      blue.value = b;
      // 移动滑块
      hueSliderStyle.value = `left: ${x >= w - 6 ? w - 6 : x}px;`;
    }

    function mousedownHue(e) {
      handleChangeHue(e);
      window.addEventListener("mousemove", handleChangeHue);
      window.addEventListener("mouseup", mouseupHue);
    }

    function mouseupHue(e) {
      window.removeEventListener("mousemove", handleChangeHue);
      window.removeEventListener("mouseup", mouseupHue);
    }

    // 透明度
    function handleChangeAlpha(e) {
      let w = refs.alpha_slider.value.clientWidth;
      let x =
        e.pageX - refs.saturation_value.value.getBoundingClientRect().left;
      x = x < w && x > 0 ? x : x > w ? w : 0;
      // 计算透明度
      alpha.value = Math.floor((x / w) * 100 + 0.5) / 100;
      // 移动滑块
      alphaSliderStyle.value = `left: ${x >= w - 6 ? w - 6 : x}px;`;
    }

    function mousedownAlpha(e) {
      handleChangeAlpha(e);
      window.addEventListener("mousemove", handleChangeAlpha);
      window.addEventListener("mouseup", mouseupAlpha);
    }

    function mouseupAlpha(e) {
      window.removeEventListener("mousemove", handleChangeAlpha);
      window.removeEventListener("mouseup", mouseupAlpha);
    }

    /**
     * 解析输入的数据,只能解析hex颜色和rgb对象形式的数据
     * @param color
     */
    function parseColor(color) {
      if (color) {
        let r, g, b, a;
        if (typeof color === "string") {
          if (
            /^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8}|[0-9a-fA-F]{3}|[0-9a-fA-F]{4})$/.test(
              color
            )
          ) {
            return hex2rgba(color);
          }
        } else {
          r = color.r > 255 ? 255 : color.r < 0 ? 0 : color.r;
          g = color.g > 255 ? 255 : color.g < 0 ? 0 : color.g;
          b = color.b > 255 ? 255 : color.b < 0 ? 0 : color.b;
          a = color.a > 1 ? 1 : color.a < 0 ? 0 : color.a;
          return { r, g, b, a };
        }
      } else {
        return null;
      }
    }

    function hsv2rgb(h, s, v) {
      h === 360 && (h = 0);
      let i = Math.floor(h / 60) % 6;
      let f = h / 60 - i;
      let p = v * (1 - s);
      let q = v * (1 - s * f);
      let t = v * (1 - s * (1 - f));
      let r, g, b;
      if (i === 0) {
        r = v;
        g = t;
        b = p;
      } else if (i === 1) {
        r = q;
        g = v;
        b = p;
      } else if (i === 2) {
        r = p;
        g = v;
        b = t;
      } else if (i === 3) {
        r = p;
        g = q;
        b = v;
      } else if (i === 4) {
        r = t;
        g = p;
        b = v;
      } else if (i === 5) {
        r = v;
        g = p;
        b = q;
      }
      r = Math.floor(r * 255 + 0.5);
      g = Math.floor(g * 255 + 0.5);
      b = Math.floor(b * 255 + 0.5);
      return { r, g, b };
    }

    function rgb2hsv(r, g, b) {
      let r1 = r / 255;
      let g1 = g / 255;
      let b1 = b / 255;
      let cmax = Math.max(r1, g1, b1);
      let cmin = Math.min(r1, g1, b1);
      let d = cmax - cmin;
      let h, s, v;
      if (d === 0) {
        h = 0;
      } else if (cmax === r1) {
        h = ((60 * (g1 - b1)) / d + 360) % 360;
      } else if (cmax === g1) {
        h = 60 * ((b1 - r1) / d + 2);
      } else if (cmax === b1) {
        h = 60 * ((r1 - g1) / d + 4);
      }
      if (cmax === 0) {
        s = 0;
      } else {
        s = d / cmax;
      }
      v = cmax;
      h = Math.floor(h + 0.5);
      s = Math.floor(s * 100 + 0.5) / 100;
      v = Math.floor(v * 100 + 0.5) / 100;
      return { h, s, v };
    }

    function rgba2hex(r, g, b, a = 1) {
      r = parseInt(r);
      let r1 =
        r.toString(16).length !== 2 ? "0" + r.toString(16) : r.toString(16);
      g = parseInt(g);
      let g1 =
        g.toString(16).length !== 2 ? "0" + g.toString(16) : g.toString(16);
      b = parseInt(b);
      let b1 =
        b.toString(16).length !== 2 ? "0" + b.toString(16) : b.toString(16);
      a = parseFloat(a);
      let a1 = "";
      if (a !== 1) {
        let temp = Math.floor(256 * a);
        a1 =
          temp.toString(16).length !== 2
            ? "0" + temp.toString(16)
            : temp.toString(16);
      }
      return `#${r1}${g1}${b1}${a1}`.toUpperCase();
    }

    function hex2rgba(s) {
      if (/^#?[0-9a-fA-F]{3}$/.test(s)) {
        let b = s.substring(s.length - 1, s.length);
        let g = s.substring(s.length - 2, s.length - 1);
        let r = s.substring(s.length - 3, s.length - 2);
        return hex2rgba(`${r + r}${g + g}${b + b}`);
      }
      if (/^#?[0-9a-fA-F]{4}$/.test(s)) {
        let a = s.substring(s.length - 1, s.length);
        let b = s.substring(s.length - 2, s.length - 1);
        let g = s.substring(s.length - 3, s.length - 2);
        let r = s.substring(s.length - 4, s.length - 3);
        return hex2rgba(`${r + r}${g + g}${b + b}${a + a}`);
      }
      if (/^#?[0-9a-fA-F]{6}$/.test(s)) {
        let b = parseInt("0x" + s.substring(s.length - 2, s.length));
        let g = parseInt("0x" + s.substring(s.length - 4, s.length - 2));
        let r = parseInt("0x" + s.substring(s.length - 6, s.length - 4));
        return { r, g, b, a: 1 };
      }
      if (/^#?[0-9a-fA-F]{8}$/.test(s)) {
        let a = parseInt("0x" + s.substring(s.length - 2, s.length));
        a = a / 255;
        let b = parseInt("0x" + s.substring(s.length - 4, s.length - 2));
        let g = parseInt("0x" + s.substring(s.length - 6, s.length - 4));
        let r = parseInt("0x" + s.substring(s.length - 8, s.length - 6));
        return { r, g, b, a };
      }
    }

    return {
      ...refs,
      pointStyle,
      hueSliderStyle,
      alphaSliderStyle,
      hue,
      saturation,
      value,
      red,
      green,
      blue,
      alpha,
      colorObj,
      hexChange,
      redChange,
      greenChange,
      blueChange,
      alphaChange,
      presetChange,
      mousedownSV,
      mousedownHue,
      mousedownAlpha,
    };
  },
};
</script>

<style scoped>
.color-select {
  position: relative;
  user-select: none;
  width: 100%;
  background: #fff;
  padding: 10px;
  /*border: 1px solid #ccc;*/
  /*border-radius: 10px;*/
}

/* 饱和度和亮度 */
.saturation-value {
  cursor: pointer;
  width: 100%;
  height: 200px;
  position: relative;
  margin-bottom: 10px;
  box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1);
}

.saturation-value > div {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

/* 圆圈 */
.point {
  box-sizing: border-box;
  width: 6px;
  height: 6px;
  background-color: transparent;
  border: 2px solid #ccc;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  position: absolute;
  z-index: 9;
}

.saturation-value-2 {
  background: linear-gradient(to right, white, #ffffff00);
}

.saturation-value-3 {
  background: linear-gradient(to top, black, #ffffff00);
}

/* 色调 透明度 */
.color-select-middle {
  width: 100%;
  display: flex;
  margin-bottom: 10px;
}

/* 色调滑块条 */
.hue-slider {
  position: relative;
  margin-bottom: 6px;
  height: 10px;
  background: linear-gradient(
    90deg,
    red 0,
    #ff0 17%,
    #0f0 33%,
    #0ff 50%,
    #00f 67%,
    #f0f 83%,
    red
  );
  box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1);
}

/* 透明度滑块条 */
.alpha-slider {
  position: relative;
  height: 10px;
  box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1);
  background: #fff
    url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAWElEQVRIiWM8fubkfwYygKWJOSM5+mCAhRLNoxaPWjxq8ajFoxbTyeL/DAfJ0Xjs3Cl7Siwmu4Yht1aDgZEYx6MWj1o8avGoxaMWD3qLya5X//4nqx6HAQC7RBGFzolqTAAAAABJRU5ErkJggg==");
  background-size: 10px 10px;
}

/* 滑块 */
.slider {
  position: absolute;
  box-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
  box-sizing: border-box;
  width: 6px;
  height: 100%;
  background-color: #fff;
}

/* 颜色方块 */
.color-diamond {
  position: relative;
  margin-left: 5px;
  width: 26px;
  height: 26px;
  border-radius: 3px;
  overflow: hidden;
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAWElEQVRIiWM8fubkfwYygKWJOSM5+mCAhRLNoxaPWjxq8ajFoxbTyeL/DAfJ0Xjs3Cl7Siwmu4Yht1aDgZEYx6MWj1o8avGoxaMWD3qLya5X//4nqx6HAQC7RBGFzolqTAAAAABJRU5ErkJggg==");
  background-size: 10px 10px;
}

/* 颜色的值 hex rgba */
.color-value {
  width: 100%;
  display: flex;
  justify-content: space-between;
}

.color-value div {
  padding: 0 3px;
  text-align: center;
}

.color-value input {
  font-size: 12px;
  box-sizing: border-box;
  width: 34px;
  height: 24px;
  padding: 0;
  margin: 0;
  outline: none;
  text-align: center;
  border-radius: 3px;
  border: 1px solid #ccc;
}

.color-value p {
  font-size: 12px;
  margin: 3px 0 0;
}

.color-value .rgba-a {
  padding-right: 0;
}

.color-value .hex {
  flex: 1;
  padding-left: 0;
}

.color-value .hex input {
  width: 100%;
  height: 24px;
}

/* 预设颜色  */
.preset {
  width: 100%;
  padding: 0;
  margin: 10px 0 0;
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-start;
}

.preset li {
  width: 20px;
  height: 20px;
  margin-right: 6px;
  margin-bottom: 6px;
  border: 1px solid #ccc;
  border-radius: 6px;
}
</style>

父组件使用示例:

<MyColorSelect
          v-if="myColorSelectVisible"
          style="position: absolute; top: 0; left: 290px"
          v-model="presentBgPlacard.bgColor"
          :color="presentColor"
          @update:color="handleColorUpdate"
        ></MyColorSelect>

注意,父组件需要传值color,子组件通过@update:color传值。传值需要传RGBA格式

附上HEX转RGBA 以及 RBGA转HEX方法

function hexToRgba(hex) {
  var bigint = parseInt(hex.slice(1), 16);
  var r = (bigint >> 24) & 255;
  var g = (bigint >> 16) & 255;
  var b = (bigint >> 8) & 255;
  var a = (bigint & 255) / 255; // 将透明度值映射到 0 到 1 的范围

  return {
    r: r,
    g: g,
    b: b,
    a: a,
  };
}
function rgbaToHex(r, g, b, a) {
  r = Math.round(r);
  g = Math.round(g);
  b = Math.round(b);
  a = Math.round(a * 255);

  var hexR = r.toString(16).padStart(2, "0");
  var hexG = g.toString(16).padStart(2, "0");
  var hexB = b.toString(16).padStart(2, "0");
  var hexA = a.toString(16).padStart(2, "0");

  return "#" + hexR + hexG + hexB + hexA;
}

以上,完毕

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值