Files
tools/color_pick.html
2025-11-28 16:31:49 +08:00

876 lines
24 KiB
HTML

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>颜色选择器</title>
<style>
body {
font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif;
margin: 20px;
line-height: 1.6;
}
input {
font-family:
"SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas,
"Courier New", monospace;
}
.container {
max-width: 900px;
margin: 0 auto;
padding: 20px;
}
h1 {
text-align: center;
margin-bottom: 30px;
font-weight: 600;
color: #333;
font-size: 2rem;
}
.color-picker {
display: flex;
flex-direction: column;
margin-bottom: 20px;
}
.main-content {
display: flex;
gap: 0;
margin-bottom: 30px;
}
.left-column {
flex: 1;
display: flex;
flex-direction: column;
padding: 25px;
border-right: 1px solid #ccc;
}
.right-column {
flex: 1;
display: flex;
flex-direction: column;
padding: 25px;
padding-top: 0;
}
.basic-color-controls {
display: flex;
flex-direction: row;
align-items: flex-start;
margin-bottom: 15px;
}
.color-preview {
width: 100px;
height: 100px;
border: 1px solid #ccc;
margin-right: 15px;
}
.sliders {
display: flex;
flex-direction: column;
flex: 1;
}
.slider-group {
display: flex;
align-items: center;
margin-bottom: 3px;
}
.slider-label {
width: 30px;
margin-right: 10px;
font-weight: 500;
color: #555;
}
input[type="range"] {
width: 200px;
}
.color-values {
margin-top: 15px;
flex: 1;
padding-bottom: 15px;
border-bottom: 1px solid #ccc;
}
.color-values div {
margin-bottom: 5px;
}
/* 颜色历史记录样式 */
.color-history {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 8px;
margin-top: 10px;
}
.history-color {
width: 25px;
height: 25px;
border: 1px solid #ccc;
cursor: pointer;
border-radius: 3px;
transition: transform 0.2s ease;
}
.history-color:hover {
transform: scale(1.1);
}
.history-section {
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid #ccc;
}
.history-section h3 {
margin: 0 0 10px 0;
font-size: 0.9rem;
color: #666;
text-align: center;
}
/* 复制按钮样式 */
.copy-button {
background-color: #4caf50;
color: white;
padding: 5px 10px;
border: none;
border-radius: 4px;
cursor: pointer;
margin-left: 5px;
position: relative;
font-size: 12px;
font-weight: 500;
transition: background-color 0.2s ease;
}
.copy-button:hover {
background-color: #3e8e41;
}
/* 复制成功对勾样式 */
.copy-success-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 16px;
display: none; /* 初始隐藏 */
}
/* 错误提示 */
.error-message {
color: red;
margin-top: 5px;
}
/* 输入框样式 */
.input-container {
flex: 1;
display: flex;
flex-direction: column;
margin-top: 20px;
}
.input-container label {
display: block;
margin-bottom: 5px;
font-weight: 500;
color: #555;
}
.input-container input {
width: 100%;
padding: 8px 12px;
margin-bottom: 10px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
font-size: 14px;
transition: border-color 0.2s ease;
}
.input-container input:focus {
outline: none;
border-color: #4caf50;
}
.input-container button {
background-color: #4caf50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
margin-bottom: 10px;
font-weight: 500;
transition: background-color 0.2s ease;
}
.input-container button:hover {
background-color: #3e8e41;
}
#results {
margin-top: 20px;
border-top: 1px solid #ccc;
padding-top: 10px;
}
/* 高级颜色选择器样式 */
.advanced-color-picker {
display: flex;
margin-top: 15px;
padding-top: 15px;
justify-content: center;
align-items: flex-start;
}
.color-gradient {
width: 256px;
height: 256px;
background: linear-gradient(to right, white, red);
position: relative;
cursor: crosshair;
}
.color-gradient::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(to bottom, transparent, black);
}
.color-selector {
width: 12px;
height: 12px;
border: 1px solid black;
border-radius: 50%;
background-color: white;
position: absolute;
transform: translate(-50%, -50%);
pointer-events: none;
}
.hue-slider {
width: 24px;
height: 256px;
background: linear-gradient(
to bottom,
hsl(360, 100%, 50%),
hsl(300, 100%, 50%),
hsl(240, 100%, 50%),
hsl(180, 100%, 50%),
hsl(120, 100%, 50%),
hsl(60, 100%, 50%),
hsl(0, 100%, 50%)
);
margin-left: 20px;
cursor: row-resize;
position: relative;
}
.hue-selector {
width: 30px;
height: 4px;
background-color: transparent;
border: 3px solid black;
border-radius: 5px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
cursor: pointer;
}
.color-display {
width: 80px;
height: 80px;
border: 1px solid #ccc;
margin-left: 20px;
}
.color-info {
margin-left: 20px;
}
.copy-button.success {
background-color: #4caf50; /* Green */
}
</style>
</head>
<body>
<div class="container">
<h1>颜色选择器</h1>
<div class="main-content">
<!-- 左列 -->
<div class="left-column">
<div class="basic-color-controls">
<div
id="color-preview"
class="color-preview"
style="background-color: #ffffff"
></div>
<div class="sliders">
<div class="slider-group">
<label for="red" class="slider-label">R:</label>
<input type="range" id="red" min="0" max="255" value="255" />
<span id="red-value">255</span>
</div>
<div class="slider-group">
<label for="green" class="slider-label">G:</label>
<input type="range" id="green" min="0" max="255" value="255" />
<span id="green-value">255</span>
</div>
<div class="slider-group">
<label for="blue" class="slider-label">B:</label>
<input type="range" id="blue" min="0" max="255" value="255" />
<span id="blue-value">255</span>
</div>
</div>
</div>
<!-- 高级颜色选择器 -->
<div class="advanced-color-picker">
<div class="color-gradient">
<div class="color-selector"></div>
</div>
<div class="hue-slider">
<div class="hue-selector"></div>
</div>
</div>
<!-- 颜色历史记录 -->
<div class="history-section">
<h3>颜色历史记录</h3>
<div class="color-history"></div>
</div>
</div>
<!-- 右列 -->
<div class="right-column">
<div class="color-values">
<div>
RGB: <span id="rgb-value">rgb(255, 255, 255)</span>
<button class="copy-button" data-target="rgb">
复制 <span class="copy-success-icon">&#10004;</span>
</button>
</div>
<div>
Hex: <span id="hex-value">#ffffff</span>
<button class="copy-button" data-target="hex">
复制 <span class="copy-success-icon">&#10004;</span>
</button>
</div>
<div>
HSL: <span id="hsl-value">hsl(0, 0%, 100%)</span>
<button class="copy-button" data-target="hsl">
复制 <span class="copy-success-icon">&#10004;</span>
</button>
</div>
<div>
HSV: <span id="hsv-value">hsv(0, 0%, 100%)</span>
<button class="copy-button" data-target="hsv">
复制 <span class="copy-success-icon">&#10004;</span>
</button>
</div>
</div>
<!-- 输入框 -->
<div class="input-container">
<label for="rgb-r">红色 (R):</label>
<input
type="number"
id="rgb-r"
min="0"
max="255"
value="255"
required
/>
<label for="rgb-g">绿色 (G):</label>
<input
type="number"
id="rgb-g"
min="0"
max="255"
value="255"
required
/>
<label for="rgb-b">蓝色 (B):</label>
<input
type="number"
id="rgb-b"
min="0"
max="255"
value="255"
required
/>
<button id="convert-rgb-button">转换 RGB</button>
<label for="hex-input">十六进制颜色值:</label>
<input type="text" id="hex-input" placeholder="例如: #ffffff" />
<button id="convert-hex-button">转换 Hex</button>
<div id="input-error" class="error-message"></div>
</div>
</div>
</div>
</div>
<script>
const colorPreview = document.getElementById("color-preview");
const redSlider = document.getElementById("red");
const greenSlider = document.getElementById("green");
const blueSlider = document.getElementById("blue");
const redValue = document.getElementById("red-value");
const greenValue = document.getElementById("green-value");
const blueValue = document.getElementById("blue-value");
const rgbValue = document.getElementById("rgb-value");
const hexValue = document.getElementById("hex-value");
const hslValue = document.getElementById("hsl-value");
const hsvValue = document.getElementById("hsv-value");
const copyButtons = document.querySelectorAll(".copy-button");
const colorHistory = document.querySelector(".color-history");
const rgbRInput = document.getElementById("rgb-r");
const rgbGInput = document.getElementById("rgb-g");
const rgbBInput = document.getElementById("rgb-b");
const hexInput = document.getElementById("hex-input");
const convertRgbButton = document.getElementById("convert-rgb-button");
const convertHexButton = document.getElementById("convert-hex-button");
const inputError = document.getElementById("input-error");
// 高级颜色选择器元素
const colorGradient = document.querySelector(".color-gradient");
const colorSelector = document.querySelector(".color-selector");
const hueSlider = document.querySelector(".hue-slider");
const hueSelector = document.querySelector(".hue-selector");
let historyColors = []; // 存储颜色历史记录
let currentColor = { h: 0, s: 100, v: 100 }; // 存储当前颜色 HSV 值
// 更新颜色历史记录
function updateHistory(color) {
if (!historyColors.includes(color)) {
historyColors.unshift(color); // 添加到数组开头
if (historyColors.length > 10) {
historyColors.pop(); // 限制历史记录数量
}
renderHistory();
}
}
// 渲染颜色历史记录
function renderHistory() {
colorHistory.innerHTML = ""; // 清空历史记录
historyColors.forEach((color) => {
const historyDiv = document.createElement("div");
historyDiv.classList.add("history-color");
historyDiv.style.backgroundColor = color;
historyDiv.addEventListener("click", () => {
setColors(color);
});
colorHistory.appendChild(historyDiv);
});
}
// 将颜色值设置到所有控件
function setColors(color) {
const rgb = hexToRgb(color);
if (rgb) {
redSlider.value = rgb.r;
greenSlider.value = rgb.g;
blueSlider.value = rgb.b;
rgbRInput.value = rgb.r;
rgbGInput.value = rgb.g;
rgbBInput.value = rgb.b;
hexInput.value = color;
updateColor();
}
}
function updateColor() {
const red = parseInt(redSlider.value);
const green = parseInt(greenSlider.value);
const blue = parseInt(blueSlider.value);
redValue.textContent = red;
greenValue.textContent = green;
blueValue.textContent = blue;
rgbRInput.value = red;
rgbGInput.value = green;
rgbBInput.value = blue;
const rgb = `rgb(${red}, ${green}, ${blue})`;
rgbValue.textContent = rgb;
const hex = rgbToHex(red, green, blue);
hexValue.textContent = hex;
hexInput.value = hex;
const hsl = rgbToHsl(red, green, blue);
hslValue.textContent = `hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%)`;
const hsv = rgbToHsv(red, green, blue);
hsvValue.textContent = `hsv(${hsv.h}, ${hsv.s}%, ${hsv.v}%)`;
colorPreview.style.backgroundColor = hex;
}
function rgbToHex(r, g, b) {
return (
"#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)
);
}
function hexToRgb(hex) {
// Expand shorthand form (e.g. "03F") to full form (e.g. "03F")
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
hex = hex.replace(shorthandRegex, function (m, r, g, b) {
return r + r + g + g + b + b;
});
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
}
: null;
}
function rgbToHsl(r, g, b) {
(r /= 255), (g /= 255), (b /= 255);
const max = Math.max(r, g, b),
min = Math.min(r, g, b);
let h,
s,
l = (max + min) / 2;
if (max == min) {
h = s = 0; // achromatic
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0);
break;
case g:
h = (b - r) / d + 2;
break;
case b:
h = (r - g) / d + 4;
break;
}
h /= 6;
}
return {
h: Math.round(h * 360),
s: Math.round(s * 100),
l: Math.round(l * 100),
};
}
function rgbToHsv(r, g, b) {
r = r / 255;
g = g / 255;
b = b / 255;
let max = Math.max(r, g, b);
let min = Math.min(r, g, b);
let h,
s,
v = max;
let d = max - min;
s = max === 0 ? 0 : d / max;
if (max === min) {
h = 0; // achromatic
} else {
if (max === r) {
h = (g - b) / d + (g < b ? 6 : 0);
} else if (max === g) {
h = (b - r) / d + 2;
} else if (max === b) {
h = (r - g) / d + 4;
}
h /= 6;
}
return {
h: Math.round(h * 360),
s: Math.round(s * 100),
v: Math.round(v * 100),
};
}
function hsvToRgb(h, s, v) {
s = s / 100;
v = v / 100;
let r, g, b;
let i = Math.floor(h / 60);
let f = h / 60 - i;
let p = v * (1 - s);
let q = v * (1 - s * f);
let t = v * (1 - s * (1 - f));
switch (i % 6) {
case 0:
r = v;
g = t;
b = p;
break;
case 1:
r = q;
g = v;
b = p;
break;
case 2:
r = p;
g = v;
b = t;
break;
case 3:
r = p;
g = q;
b = v;
break;
case 4:
r = t;
g = p;
b = v;
break;
case 5:
r = v;
g = p;
b = q;
break;
}
return {
r: Math.round(r * 255),
g: Math.round(g * 255),
b: Math.round(b * 255),
};
}
redSlider.addEventListener("input", updateColor);
greenSlider.addEventListener("input", updateColor);
blueSlider.addEventListener("input", updateColor);
// 转换 RGB
convertRgbButton.addEventListener("click", function () {
const r = parseInt(rgbRInput.value);
const g = parseInt(rgbGInput.value);
const b = parseInt(rgbBInput.value);
if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) {
const color = rgbToHex(r, g, b);
inputError.textContent = "";
setColors(color);
updateHistory(color); // 在这里添加当前颜色到历史记录
} else {
inputError.textContent = "无效的 RGB 颜色值";
}
});
// 转换 Hex
convertHexButton.addEventListener("click", function () {
const input = hexInput.value.trim();
if (/^#([0-9a-f]{3}){1,2}$/i.test(input)) {
inputError.textContent = "";
setColors(input);
updateHistory(input); // 在这里添加当前颜色到历史记录
} else {
inputError.textContent = "无效的 Hex 颜色值";
}
});
// 复制到剪贴板 (兼容方案)
function copyToClipboard(text) {
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.style.position = "fixed"; // 防止页面滚动
document.body.appendChild(textarea);
textarea.focus();
textarea.select();
try {
const successful = document.execCommand("copy");
if (!successful) {
console.error("复制失败,请手动复制");
alert("复制失败,请手动复制");
}
} catch (err) {
console.error("复制失败: ", err);
alert("复制失败,请手动复制");
}
document.body.removeChild(textarea);
}
// 显示复制成功消息
function showCopySuccess(button) {
const successIcon = button.querySelector(".copy-success-icon");
const originalText = button.textContent;
button.classList.add("success"); // Add success class
button.textContent = "完成"; // Change button text
setTimeout(() => {
button.textContent = "复制"; // Restore original text
button.classList.remove("success"); // Remove success class
}, 1500);
}
// 复制到剪贴板
copyButtons.forEach((button) => {
button.addEventListener("click", function () {
const target = this.dataset.target;
let text = "";
let colorToSave = hexValue.textContent; // Always save hex value
switch (target) {
case "rgb":
text = rgbValue.textContent;
break;
case "hex":
text = hexValue.textContent;
break;
case "hsl":
text = hslValue.textContent;
break;
case "hsv":
text = hsvValue.textContent;
break;
}
copyToClipboard(text);
updateHistory(colorToSave);
showCopySuccess(this); // Pass the button element
});
});
// 高级颜色选择器事件
colorGradient.addEventListener("mousedown", (e) => {
updateGradientColor(e);
function updateGradientColor(e) {
const rect = colorGradient.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// 限制选择器在渐变区域内
const boundedX = Math.max(0, Math.min(x, rect.width));
const boundedY = Math.max(0, Math.min(y, rect.height));
colorSelector.style.left = boundedX + "px";
colorSelector.style.top = boundedY + "px";
// 计算饱和度和亮度
const saturation = (boundedX / rect.width) * 100;
const value = (1 - boundedY / rect.height) * 100;
currentColor.s = saturation;
currentColor.v = value;
updateFromAdvancedColor(); // Update everything from advanced color
}
function stopUpdate() {
document.removeEventListener("mousemove", updateGradientColor);
document.removeEventListener("mouseup", stopUpdate);
}
document.addEventListener("mousemove", updateGradientColor);
document.addEventListener("mouseup", stopUpdate);
});
hueSlider.addEventListener("mousedown", (e) => {
updateHue(e);
function updateHue(e) {
const rect = hueSlider.getBoundingClientRect();
const y = e.clientY - rect.top;
// 限制选择器在渐变区域内
const boundedY = Math.max(0, Math.min(y, rect.height));
hueSelector.style.top = boundedY + "px";
// 计算色相
const hue = ((1 - boundedY / rect.height) * 360) % 360;
currentColor.h = hue;
updateFromAdvancedColor(); // Update everything from advanced color
}
function stopUpdate() {
document.removeEventListener("mousemove", updateHue);
document.removeEventListener("mouseup", stopUpdate);
}
document.addEventListener("mousemove", updateHue);
document.addEventListener("mouseup", stopUpdate);
});
// 更新所有颜色控件
function updateFromAdvancedColor() {
const rgb = hsvToRgb(currentColor.h, currentColor.s, currentColor.v);
// Update basic sliders
redSlider.value = rgb.r;
greenSlider.value = rgb.g;
blueSlider.value = rgb.b;
// Update input boxes
rgbRInput.value = rgb.r;
rgbGInput.value = rgb.g;
rgbBInput.value = rgb.b;
// Call the main updateColor function to update everything else
updateColor();
// Update gradient background
colorGradient.style.background = `linear-gradient(to right, white, hsl(${currentColor.h}, 100%, 50%))`;
}
renderHistory(); // 初始化颜色历史记录
updateColor(); // 初始化颜色显示
</script>
</body>
</html>