🎨手撸一个颜色选择器耍耍-Javascript

620 阅读13分钟

写在开头

哈喽,各位好吖!👻

当前,2024年08月08日,是个不错的日子,八八八你发我也发。😂

近来有些感冒😷,状态不佳,过得有点浑浑噩噩!自己上药店买药吃,每次上药店买一两盒药(999和咳嗽糖浆)就要近百,真是挺贵!难怪都说穷人生不起病,这确实呀。

人生箴言"小病就治,大病就嘎"。😋

还有,最近是奥运期间,不知道你是否有关注?😀

都说近这些年,好像全世界对奥运会都不怎么关心了,申办的城市也越来越少了。奥运会下届是在美国洛杉矶,而下下届是在"唯一"申办的澳大利亚布里斯班城市,唯一申办❗😐

不管如何,还是希望未来能继续办下去吧。反正俺是挺喜欢看的,咱最喜欢看游泳、跳水、乒乓球等赛事,感觉奥运赛事带来的体验,远远超过竞技本身。

好,回到正题,这次要分享的是颜色选择器相关的内容,具体效果如下,请诸君按需食用。

2024809-9.gif

(动态被压缩了,颜色区域有点裂痕,真实页面是不会的🥶)

简介

颜色选择器,一个用来选择颜色的小工具,现在的浏览器也都有内置颜色选择器,如:

<input type="color" />

样子长这样:

image.png

不错,简单、实用,可惜的是,样子不能自定义,这就很难受了。😅

为此,手动来学会自己搞一个还是很有必要的,接下来就让咱们一步一步来完成它,冲。

基本布局

咱们最终目标是开头看到的那个动图效果,其中最核心的就是选择区那部分,先来进行布局:

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="./style.css">
</head>
<body>
  <div class="container">
    <div class="draggable"></div>
  </div>
</body>
</html>

创建 style.css 文件:

body {
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}
.container {
  border: 1px solid #cbd5e1;
  border-radius: 4px;
  position: relative;
  width: 250px;
  height: 200px;
  box-sizing: border-box;
  margin-bottom: 20px;
  /* 设置背景,渐变了一下 */
  background-image: linear-gradient(to bottom, transparent, black), linear-gradient(to right, white, transparent);
  background-color: hsl(0, 100%, 50%);
}
.draggable {
  width: 20px;
  height: 20px;
  border: 2px solid #fff;
  border-radius: 50%;
  position: absolute;
  top: -10px;
  left: -10px;
  transform: translate(0, 0);
  cursor: move;
  user-select: none;
  box-sizing: border-box;
  background-color: #000;
}

效果:

image.png

创建拖动元素

有了布局后,第二步,咱们要让小黑点变成可拖动的元素。

拖动,又是拖动呢,最近写的文章好像老是和拖动搭边,曾经小编一听到拖动就感觉好麻烦,要处理多个事件,还有各种位置相关的属性,老是傻傻分不清。。。🙊

还是老样子,监听鼠标三兄弟事件(mousedown/mousemove/mouseup),如下:

<script>
  document.addEventListener('DOMContentLoaded', () => {
    const containerDOM = document.querySelector('.container');
    const draggableDOM = document.querySelector('.draggable');

    draggableDOM.addEventListener("mousedown", handleMouseDown);

    const { width: containerWidth, height: containerHeight } = containerDOM.getBoundingClientRect();
    let dx = 0;
    let dy = 0;

    function handleMouseDown(e) {
      const startPos = {
        x: e.clientX - dx,
        y: e.clientY - dy,
      };
      function handleMouseMove(e) {
        let dxTemp = e.clientX - startPos.x;
        let dyTemp = e.clientY - startPos.y;
        dx = limitRange(dxTemp, 0, containerWidth);
        dy = limitRange(dyTemp, 0, containerHeight);
        // 进行移动
        draggableDOM.style.transform = `translate3d(${dx}px, ${dy}px, 0)`;
      }
      function handleMouseUp() {
        document.removeEventListener("mousemove", handleMouseMove);
        document.removeEventListener("mouseup", handleMouseUp);
      };
      document.addEventListener("mousemove", handleMouseMove);
      document.addEventListener("mouseup", handleMouseUp);
    }

    /** @name 限制一个数值在某个范围内 **/
    function limitRange(val, min, max) {
      return Math.max(min, Math.min(max, val));
    }
  });
</script>

效果:

2024808-2.gif

很简单吧,计算了一下鼠标的拖动距离(dx/dy),然后通过 translate 让小黑点元素变得可拖动。当然,这里你也能选择用 top/left 属性,或者是 margin 属性都行。😉

在使元素可拖动之后,接下来咱们要讨论颜色的选择功能。但在实现颜色选择之前,让我们先来简单了解一下"颜色模型"。

颜色模型

颜色模型,是用来表示颜色的数学模型。

常见的颜色模型有三种 RGBHSLHSV ,而在前端仅支持前两者,不支持 HSV 模型。

RGB 是应用最广泛的颜色模型。

模型的应用形式:

  • RGB 模型:rgbrgbahex
  • HSL 模型:hslhsla
  • HSV 模型:hsvhsva

含义:

  • r:红-red,值范围0~255。
  • g:绿-green,值范围0~255。
  • b:蓝-blue,值范围0~255。
  • a:透明度-alpha,值范围0~1。
  • h:色调、色相-hue,值范围0~360。
  • s:饱和度-saturation,值范围0~100%。
  • l:亮度-lightness,值范围0~100%。
  • v:明度-value,值范围0~100%。

具体前端应用情况:

  • rgb:代表红、绿、蓝值, 如 rgb(0, 0, 0)
  • rgba:增加透明度,如 rgb(0, 0, 0, 1)
  • hexRGB 的16进制的表现形式,如 #000#000000#000000bb
  • hsl:代表色调、饱和度、亮度,如 hsl(0, 100%, 50%)
  • hsla:增加透明度,如 hsl(0, 100%, 50%, 1)
  • hsv:代表色调、饱和度、明度,前端不支持。
  • hsva:增加透明度,前端不支持。

大概了解一下就行,这些形式都能互相转换来转换去,转换方法也都是现成可靠的,咱们只要记住常用的 rgbhex 这两种形式就够了。😉

HSL和HSV色彩空间

image.png

颜色的选择

在上面,咱们实现了用户拖动选择颜色的交互,拥有了 dxdy 变量,它们是相对于容器的偏移量。

image.png

咱们要如何把这两个偏移量转换成需要的颜色值呢❓

那必然是需要一些计算滴😋,先来瞅瞅具体代码实现吧,如下:

<script src="./utils.js" ></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
  // ...
  
  function handleMouseDown(e) {
    // ...
    function handleMouseMove(e) {
      // ...
      // 转换成颜色的变化
      handleChangeSaturationValue();
      draggableDOM.style.transform = `translate3d(${dx}px, ${dy}px, 0)`;
    }
    // ...
  }
  
  /** @name 初始化一个颜色,黑色 **/
  const initColor = {
    r: 0,
    g: 0,
    b: 0,
    a: 1,
  };
  /** @name 最终选择的颜色 **/
  let color = {
    ...initColor,
    ...rgbToHsv(initColor),
  }
  /** @name 拖动选择颜色时,饱和度与明度的变化 **/
  function handleChangeSaturationValue() {
    // 计算饱和度
    const s = 100 * dx / containerWidth;
    // 计算明度
    const v = 100 * (1 - (dy / containerHeight));
    // 转换成rgb
    const rgb = hsvToRgb({ h: color.h, s, v });
    const colorResult = {
      ...color,
      ...rgb,
      s,
      v,
    };
    color = colorResult;
    console.log(color)
  };
});
<script>

创建 utils.js 文件:

function rgbToHsv({ r, g, b }) {
  const max = Math.max(r, g, b);
  const d = max - Math.min(r, g, b);
  if (d === 0) {
    return {
      h: 0,
      s: max ? d / max : 0,
      v: max,
    };
  }
  let h = 0;
  switch (max) {
    case r:
      h = (g - b) / d + (g < b ? 6 : 0);
      break;
    case g:
      h = 2 + (b - r) / d;
      break;
    case b:
      h = 4 + (r - g) / d;
      break;
  }
  return {
    h: h * 60,
    s: max ? d / max : 0,
    v: max,
  };
}

效果:

image.png

可以看到当拖动时,颜色具体数值是计算出来了,关键代码是这两行:

const s = 100 * dx / containerWidth;
const v = 100 * (1 - (dy / containerHeight));

这里的 sv 表示饱和度和明度(或者某种颜色分量,亮度)的。

饱和度(Saturation)和明度/亮度(Value 或 Lightness)是 HSV/HSL 颜色模型中的两个组成部分,其中:

  • 饱和度(S)表示颜色的纯度,范围从0%(灰色)到100%(完全饱和的颜色)。
  • 明度(V)或亮度(L)表示颜色的明暗程度,范围也是从0%(黑色)到100%(白色)。
image.png

饱和度(S)的计算:

  • dx / containerWidth 计算了水平偏移量占容器宽度百分比。
  • 乘以100将这个百分比转换为0到100的饱和度范围。
  • 结果是,当用户在颜色选择器水平方向上从左(0%)移动到右(100%),饱和度值s也会从0增加到100。

明度(V)的计算:

  • dy / containerHeight 计算了垂直偏移量占容器高度百分比。
  • 1 - (dy / containerHeight) 反转了垂直偏移量,这样当用户从顶部(暗,即低亮度)移动到底部(亮,即高明度)时,亮度值会增加。
  • 乘以100同样将这个百分比转换为0到100的亮度范围。
  • 结果是,当用户在颜色选择器垂直方向上从顶部(0%)移动到底部(100%),亮度值v也会从100减少到0。

计算亮度 lconst l = 100 * (1 - (dy / containerHeight));,与亮度 v 是一样的。

有了饱和度 s 与 明度 v ,咱们就可以将 HSV 型转换成 RGB 颜色模型了。🙊

不对吧,不是还有色调 h 还没有算吗❓

先不管它,先使用初始化的默认值就行。因为在实际应用中,色调 h 通常是通过另一个独立控制(比如一个色轮或者一个水平条)来选择的。😕

现在我们只是 console 颜色数值,为了更好的展示效果,我们来将其进行格式化一下,并实时展示到页面上。

结构:

<div class="color-info">
  <div id="rgb"></div>
  <div id="rgba"></div>
  <div id="hex"></div>
  <div id="hsl"></div>
  <div id="hsv"></div>
</div>

样式:

.color-info {
  width: 250px;
  border: 1px solid #cbd5e1;
  border-bottom: none;
  border-radius: 4px;
}
.color-info > div {
  width: 100%;
  height: 40px;
  display: flex;
  justify-content: center;
  align-items: center;
  border-bottom: 1px solid #cbd5e1;
}

逻辑实现:

// ...

// 获取颜色值展示的元素
const rgbDOM = document.getElementById('rgb');
const rgbaDOM = document.getElementById('rgba');
const hexDOM = document.getElementById('hex');
const hslDOM = document.getElementById('hsl');
const hsvDOM = document.getElementById('hsv');

// ...

function handleChangeSaturationValue() {
  // ...
  // console.log(color);
  // 展示颜色值到页面
  colorFormat = transformColorFormat(colorResult);
};

/** @name 转换颜色格式 **/
let colorFormat = {
  ...transformColorFormat(color),
};

/** @name 获取颜色的各种格式 **/
function transformColorFormat(color) {
  // 颜色值保持整数
  const r = Math.round(color.r);
  const g = Math.round(color.g);
  const b = Math.round(color.b);
  const h = Math.round(color.h);
  const s = Math.round(color.s);
  const v = Math.round(color.v);
  // 由于透明度范围是0~1,为了获得更精确的结果,将其乘以1000,然后除以1000
  const a = Math.round(color.a * 1000) / 1000;
  // 各种颜色格式
  const rgbColor = `rgb(${r} ${g} ${b})`;
  const rgbaColor = `rgba(${r} ${g} ${b} / ${a})`;
  const hexColor = rgbToHex({ r, g, b, a });
  const hslColor = `hsl(${h} 100% 50%)`;
  const hsvColor = `${h} ${s}% ${v}% ${a}`;
  // 展示到页面
  rgbElement.innerText = rgbColor;
  rgbaElement.innerText = rgbaColor;
  hexElement.innerText = hexColor;
  hslElement.innerText = hslColor;
  hsvElement.innerText = hsvColor;

  return {
    rgbColor,
    rgbaColor,
    hexColor,
    hslColor,
    hsvColor,
  }
}

增加工具函数,utils.js 文件:

function rgbToHex({ r, g, b, a }) {
  const [rr, gg, bb, aa] = [r, g, b, Math.round(a * 255)].map((v) =>
        v.toString(16).padStart(2, "0")
  );
  return ["#", rr, gg, bb, aa === "ff" ? "" : aa].join("");
}

效果:

2024808-3.gif

拖动事件封装

完成颜色选择后,接下来咱们要来实现色调选择器,它是一个水平条,允许左右拖动进行选择。

image.png

那么,它也需要监听鼠标三兄弟事件(mousedown/mousemove/mouseup)。

后面,咱们还有一个透明度选择器,也是一样的拖动操作。😳

如果每次都监听这三个事件处理拖动逻辑未免有点太麻烦了,我们需要把这个过程给它封装一下,让它更方便一些。

且看,创建 draggable.js 文件:

class Draggable {
  constructor(el) {
    this.el = el;
    this.parentEl = null;
    this.parentRect = {};
    this.dx = 0;
    this.dy = 0;
    this.isDragging = false;
    this.init();
  }
  init() {
    this.parentEl = this.el.parentNode;
    // 给父元素添加点击事件
    this.parentEl.addEventListener('click', this.handleClick.bind(this));
    this.parentRect = this.parentEl.getBoundingClientRect();
    this.el.addEventListener('mousedown', this.handleMouseDown.bind(this));
  }
  handleClick(e) {
    // 仅本身触发
    if (e.target === e.currentTarget) {
      const dx = e.offsetX;
      const dy = e.offsetY;
      // 在拖动过程可以自定义做一些事情
      if (typeof this.onClick === 'function') {
        this.dx = dx;
        this.dy = dy;
        this.onClick({
          dx: this.dx,
          dy: this.dy
        });
      }
    }
  }
  handleMouseDown(e) {
    // 阻止冒泡
    e.stopPropagation();
    this.isDragging = true;
    this.startPos = {
      x: e.clientX - this.dx,
      y: e.clientY - this.dy,
    };
    document.addEventListener('mousemove', this.handleMouseMove.bind(this));
    document.addEventListener('mouseup', this.handleMouseUp.bind(this));
  }
  handleMouseMove(e) {
    e.stopPropagation();
    if (this.isDragging) {
      const dxTemp = e.clientX - this.startPos.x;
      const dyTemp = e.clientY - this.startPos.y;
      const { width: parentWidth, height: parentHeight } = this.parentRect;
      // 在拖动过程可以自定义做一些事情
      if (typeof this.onMouseMove === 'function') {
        this.dx = this.clamp(dxTemp, 0, parentWidth);
        this.dy = this.clamp(dyTemp, 0, parentHeight);
        this.onMouseMove({
          dx: this.dx,
          dy: this.dy
        });
      }
    }
  }
  handleMouseUp(e) {
    e.stopPropagation();
    this.isDragging = false;
    document.removeEventListener("mousemove", this.handleMouseMove);
    document.removeEventListener("mouseup", this.handleMouseUp);
  }
  /** @name 外部用来监听事件的函数 **/
  on(eventName, callback) {
    if (eventName === 'mousemove') {
      this.onMouseMove = callback;
    }
    if (eventName === 'click') {
      this.onClick = callback;
    }
  }
  /** @name 外部设置偏移量的函数 **/
  setDraggablePostion(dx, dy) {
    this.dx = dx;
    this.dy = dy;
  }
  clamp(val, min, max) {
    return Math.max(min, Math.min(max, val));
  }
}

主要是对 mousedown/mousemove/mouseup 三个事件进行了简单的封装,这应该难度不是很大哈。😋

噢,还有😶,里面有一点逻辑是对容器点击 click 事件的处理,因为后面也会用到,就先写出来了。

具体使用:

<script src="./draggable.js" ></script>
<script src="./utils.js" ></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
  // ...
  
  const draggableInstance = new Draggable(draggableDOM);
  draggableInstance.on('mousemove', ({ dx, dy }) => {
    handleChangeSaturationValue(dx, dy);
    draggableDOM.style.transform = `translate3d(${dx}px, ${dy}px, 0)`;
  });
  
  // ...
  
  function handleChangeSaturationValue(dx, dy) { ... }
});

替换掉 handleMouseDown 函数与 dx/dy 变量,并给 handleChangeSaturationValue() 函数增加两个参数即可。

色调选择器

进行了简单的封装后,瞬间觉得简洁很多😀,而色调选择器的实现就比较简单了。

结构:

<div class="container__hue">
  <div class="draggable__hue"></div>
</div>

样式:

.container__hue {
  width: 250px;
  height: 16px;
  border-radius: 4px;
  margin-bottom: 20px;
  position: relative;
  background-image: linear-gradient(
    to right,
    rgb(255 0 0),
    rgb(255 255 0),
    rgb(0 255 0),
    rgb(0 255 255),
    rgb(0 0 255),
    rgb(255 0 255),
    rgb(255 0 0)
  );
}
.draggable__hue {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  cursor: move;
  user-select: none;
  position: absolute;
  top: 0;
  left: -8px;
  border: 2px solid #fff;
  box-sizing: border-box;
  background-color: red;
}

逻辑实现:

const draggableHueDOM = document.querySelector('.draggable__hue');
const draggableHueInstance = new Draggable(draggableHueDOM);
draggableHueInstance.on('mousemove', ({ dx }) => {
  draggableHueDOM.style.transform = `translate(${dx}px, 0)`;
});

效果:

2024809-1.gif

几行代码就能实现拖动选择了,秒!😋

但是,还没完❗我们需要把选择的色调给"反馈"到颜色中,也就要计算出具体的色调 h 的值。

先看看具体的实现过程:

draggableHueInstance.on('mousemove', ({ dx }) => {
  // 根据dx偏移量计算h
  handleChangeHue(dx);
  draggableHueDOM.style.transform = `translate(${dx}px, 0)`;
});

/** @name 拖动选择色调时,计算色调h的具体数值 **/
function handleChangeHue(dx) {
  const h = (dx / containerWidth) * 360;
  const rgb = hsvToRgb({
    h,
    s: color.s,
    v: color.v,
  });
  const colorResult = {
    ...color,
    ...rgb,
    h,
  };
  color = colorResult;
  colorFormat = transformColorFormat(colorResult);
}

上面,我们讲过色调的值范围是0~360,通过 dx / containerWidth 能计算出水平偏移量占容器宽度的百分比,在看看这个百分比占据360的多少就能很轻松计算出色调 h 的值了。

增加工具函数,utils.js 文件:

function hsvToRgb({ h, s, v }) {
  s /= 100;
  v /= 100;
  const i = ~~(h / 60);
  const f = h / 60 - i;
  const p = v * (1 - s);
  const q = v * (1 - s * f);
  const t = v * (1 - s * (1 - f));
  const index = i % 6;
  const r = [v, q, p, p, t, v][index] * 255;
  const g = [t, v, v, q, p, p][index] * 255;
  const b = [p, p, t, v, v, q][index] * 255;
  return { r, g, b };
}

这种工具函数网上找现成,或者直接问问AI就行,不用太去琢磨。👻

效果:

2024809-2.gif

这就能看到色调 h 的值在变化了,但还是没完呢❗

上面的颜色选择区域是不是也应该要跟着变才对吧?不能一直是红黑色调,要紧跟步伐才是。🏃

再来改改:

draggableHueInstance.on('mousemove', ({ dx }) => {
  handleChangeHue(dx);
  draggableHueDOM.style.transform = `translate(${dx}px, 0)`;
  // 更新颜色选择区域的变化
  updatePageView();
});

/** @name 统一更新页面视图 **/
function updatePageView() {
  containerDOM.style.backgroundColor = colorFormat.hslColor;
}

咱们在 colorFormat 变量中存储了很多颜色格式,有 rgbColor/rgbaColor/hexColor/hslColor/hsvColor,可以直接将 hslColor 格式赋值给了颜色选择区域。

效果:

2024809-5.gif

很棒!😋

透明度选择器

接下来就还有透明度选择器了,直接开整!👀

结构:

<div class="container__alpha">
  <div class="draggable__alpha"></div>
</div>

样式:

.container__alpha {
  width: 250px;
  height: 16px;
  border-radius: 4px;
  margin-bottom: 20px;
  position: relative;
  background: linear-gradient(to right, rgb(0 0 0 / 0), rgb(0 0 0 / 1)) top left / auto auto, 
              conic-gradient(
                #666 0.25turn,
                #999 0.25turn 0.5turn,
                #666 0.5turn 0.75turn,
                #999 0.75turn
              )
              top left / 16px 16px repeat;
}
.draggable__alpha {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  cursor: move;
  user-select: none;
  position: absolute;
  top: 0;
  left: -8px;
  border: 2px solid #fff;
  box-sizing: border-box;
  background-color: #000;
}

呃...这里这个背景稍微有一点麻烦,相信 linear-gradient 函数用于渐变大家或多或少还是有所了解的,而 conic-gradient 也是用于渐变的函数,只是它的方向不一样。这里小编也是从MDN上抄下来的,反正呢,Em...就是多练习吧。😂

image.png

逻辑实现:

const draggableAlphaDOM = document.querySelector('.draggable__alpha');
const draggableAlphaInstance = new Draggable(draggableAlphaDOM);
draggableAlphaInstance.on('mousemove', ({ dx }) => {
  handleChangeAlpha(dx);
  draggableAlphaDOM.style.transform = `translate(${dx}px, 0)`;
});

/** @name 拖动选择透明度时,计算透明度a的具体数值 **/
function handleChangeAlpha(dx) {
  const a = dx / containerWidth;
  const colorResult = {
    ...color,
    a,
  };
  color = colorResult;
  colorFormat = transformColorFormat(colorResult);
}

效果:

2024809-3.gif

由于,透明度的值范围是0~1,所以通过 dx / containerWidth 可以直接计算出透明度 a 的数值,后面的操作就都是同样的。

还有,咱们希望"颜色的选择"与"色调的选择"要能反馈到这透明度上,啥意思呢❓

现在的透明度选择器上一边是透明一边是黑色的,这好像不太对吧?应当是我们选择红色或者蓝色啥的,有一边是透明,有一边要是选择的颜色才是正确的。

如:

2024809-7.gif

具体实现:

draggableInstance.on('mousemove', ({ dx, dy }) => {
  // ...
  updatePageView();
});

/** @name 统一更新页面视图 **/
function updatePageView(dx) {
  containerDOM.style.backgroundColor = colorFormat.hslColor;
  containerAlphaDOM.style.background = `linear-gradient(to right, rgb(${color.r} ${color.g} ${color.b} / 0), rgb(${color.r} ${color.g} ${color.b} / 1)) top left / auto auto,conic-gradient(
                        #666 0.25turn,
                        #999 0.25turn 0.5turn,
                        #666 0.5turn 0.75turn,
                        #999 0.75turn
                        ) top left / 16px 16px repeat`;
}

还是在 updatePageView 函数中统一去更新页面的视图,如果是使用 Vue 或者 React 那直接绑定一下属性就可以了,不用再去操作原生DOM。

可点击选择

在浏览器内置的颜色选择器(<input type="color" />)中,还可以直接点击选择颜色与色调,如下:

2024809-4.gif

咱们肯定也希望自己做的颜色选择器也有这个功能囖,那要来如何实现呢❓

这个时候就要用到我们在封装拖动事件的时候提前埋的雷了。💣

来吧,且看:

draggableInstance.on('click', ({ dx, dy }) => {
  handleChangeSaturationValue(dx, dy);
  draggableDOM.style.transform = `translate3d(${dx}px, ${dy}px, 0)`;
  updatePageView();
});
draggableHueInstance.on('click', ({ dx }) => {
  handleChangeHue(dx);
  draggableHueDOM.style.transform = `translate(${dx}px, 0)`;
  updatePageView();
});
draggableAlphaInstance.on('click', ({ dx }) => {
  handleChangeAlpha(dx);
  draggableAlphaDOM.style.transform = `translate(${dx}px, 0)`;
  updatePageView();
});

给三个实例直接再监听一下点击事件(click)就行,做的事情和 mousemove 事件是一样的。

这里可以考虑考虑再优化一下,两个事件合起来❓😶

效果:

2024809-8.gif

其他细节

讲到这里,其实本章想说的内容也就差不多了,核心主要是颜色选择、色调选择、透明度选择,其余都是一些附加的细节。

Em......再来说说其中的两个稍微重要一点的小细节:

  • 其一,背景同步。

观察下面两图:

image.png image.png

可以看到,内置的颜色选择器小圆点的背景是与容器背景同步的,而咱们自己的颜色选择器是没有的,体验有点不好。😕

把小圆点背景改成透明不就行了?当然不行,当靠近边框就会露馅,这不是我们所期待的。

正确做法:

/** @name 统一更新页面视图 **/
function updatePageView() {
  // ...
  draggableDOM.style.backgroundColor = colorFormat.rgbColor;
  draggableHueDOM.style.backgroundColor = colorFormat.hslColor;
  draggableAlphaDOM.style.background = `linear-gradient(to right, ${colorFormat.rgbaColor}, ${colorFormat.rgbaColor}) top left / auto auto,
                                          conic-gradient(
                                                #666 0.25turn,
                                                #999 0.25turn 0.5turn,
                                                #666 0.5turn 0.75turn,
                                                #999 0.75turn
                                          ) ${-draggableAlphaInstance.dx - 4}px -2px / 16px 16px repeat`;
}

效果:

image.png

由于透明度选择器背景比较特殊,需要通过其 dx 控制,让小圆点在拖动中看起来背景效果更真实。呃....反正呢,你可以细细观察一下前后的对比,这里小编很难讲得清。🙊

  • 其二,初始位置。

现在小圆点的初始位置不太对,如:颜色选择器的小圆点初始化位置应该是在左下角的黑色位置,透明度的小圆点初始化位置应该是在右边为1的位置上。

image.png

需要咱们给它们都初始化一下位置:

function init() {
  // 颜色区域
  const dx = color.s * containerWidth;
  const dy = (1 - color.v) * containerHeight;
  draggableInstance.setDraggablePostion(dx, dy);
  draggableDOM.style.transform = `translate3d(${dx}px, ${dy}px, 0)`;
  // 色调(现在不用也可以,因为也是0,但是如果初始颜色不是黑色,那就有问题了,所以还是得加)
  const dxHue = color.h * containerWidth;
  draggableHueInstance.setDraggablePostion(dxHue);
  draggableHueDOM.style.transform = `translate(${dxHue}px, 0)`;
  // 透明度
  const dxAlpha = color.a * containerWidth;
  draggableAlphaInstance.setDraggablePostion(dxAlpha);
  draggableAlphaDOM.style.transform = `translate(${dxAlpha}px, 0)`;

  updatePageView();
}
init();

再增加一个函数,并直接执行一下。

效果:

image.png

现在看起来就比较完美了,收工收工。😋

完整源码

传送门 👈👈👈





至此,本篇文章就写完啦,撒花撒花。

image.png

希望本文对你有所帮助,如有任何疑问,期待你的留言哦。
老样子,点赞+评论=你会了,收藏=你精通了。