Skip to content

Commit 7001524

Browse files
committed
feat(tools): 新增在线取色器与颜色转换器工具
添加支持 HEX、RGB 和 HSL 格式转换的在线取色器工具,包含中英文文档及交互式前端实现。同时更新网站副标题文案,将“博客与文档”和“blog and docs”统一调整为“博客网站”和“blog website”。
1 parent daae7bf commit 7001524

4 files changed

Lines changed: 340 additions & 2 deletions

File tree

assets/js/tools/color.js

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
(function() {
2+
const container = document.getElementById('tool-color');
3+
if (!container) return;
4+
5+
const lang = container.getAttribute('data-lang') || 'zh-cn';
6+
7+
const i18n = {
8+
'zh-cn': {
9+
labelPreview: '颜色预览',
10+
labelPicker: '颜色选择器',
11+
labelHex: 'HEX 十六进制',
12+
labelRgb: 'RGB (红, 绿, 蓝)',
13+
labelHsl: 'HSL (色相, 饱和度, 亮度)',
14+
placeholderHex: '#000000',
15+
copyBtn: '复制',
16+
copied: '已复制'
17+
},
18+
'en': {
19+
labelPreview: 'Color Preview',
20+
labelPicker: 'Color Picker',
21+
labelHex: 'HEX',
22+
labelRgb: 'RGB (Red, Green, Blue)',
23+
labelHsl: 'HSL (Hue, Saturation, Lightness)',
24+
placeholderHex: '#000000',
25+
copyBtn: 'Copy',
26+
copied: 'Copied'
27+
}
28+
};
29+
30+
const t = i18n[lang] || i18n['en'];
31+
32+
container.innerHTML = `
33+
<style>
34+
#tool-color .tool-container { max-width: 100%; }
35+
#tool-color .color-grid { display: grid; grid-template-columns: 1fr 2fr; gap: 2rem; margin-top: 1.5rem; }
36+
@media (max-width: 768px) { #tool-color .color-grid { grid-template-columns: 1fr; } }
37+
#tool-color .preview-card {
38+
background: var(--card-background);
39+
border: 1px solid var(--border-color);
40+
border-radius: 12px;
41+
padding: 1.5rem;
42+
display: flex;
43+
flex-direction: column;
44+
align-items: center;
45+
gap: 1rem;
46+
}
47+
#tool-color .color-preview {
48+
width: 100%;
49+
height: 150px;
50+
border-radius: 8px;
51+
border: 1px solid var(--border-color);
52+
box-shadow: inset 0 2px 4px rgba(0,0,0,0.1);
53+
}
54+
#tool-color .input-group { margin-bottom: 1.5rem; }
55+
#tool-color label { font-weight: bold; font-size: 1.4rem; color: var(--card-text-color-main); display: block; margin-bottom: 0.5rem; }
56+
#tool-color input[type="text"], #tool-color input[type="number"] {
57+
width: 100%;
58+
padding: 1rem;
59+
border: 1px solid var(--border-color);
60+
border-radius: 6px;
61+
background: var(--body-background);
62+
color: var(--card-text-color-main);
63+
font-family: 'Fira Code', monospace;
64+
font-size: 1.4rem;
65+
outline: none;
66+
transition: border-color 0.2s;
67+
}
68+
#tool-color input:focus { border-color: var(--accent-color); }
69+
#tool-color .rgb-inputs, #tool-color .hsl-inputs { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 0.8rem; }
70+
#tool-color input[type="color"] {
71+
width: 100%;
72+
height: 50px;
73+
padding: 0;
74+
border: none;
75+
border-radius: 6px;
76+
cursor: pointer;
77+
background: none;
78+
}
79+
#tool-color .copy-wrapper { position: relative; display: flex; gap: 0.5rem; }
80+
#tool-color .btn-copy {
81+
padding: 0.4rem 1rem;
82+
font-size: 1.2rem;
83+
background: var(--accent-color);
84+
color: #fff;
85+
border-radius: 4px;
86+
border: none;
87+
cursor: pointer;
88+
white-space: nowrap;
89+
}
90+
</style>
91+
<div class="tool-container">
92+
<div class="color-grid">
93+
<div class="preview-card">
94+
<div class="input-group" style="width: 100%;">
95+
<label>${t.labelPreview}</label>
96+
<div id="color-preview" class="color-preview" style="background-color: #3b82f6;"></div>
97+
</div>
98+
<div class="input-group" style="width: 100%;">
99+
<label>${t.labelPicker}</label>
100+
<input type="color" id="color-picker" value="#3b82f6">
101+
</div>
102+
</div>
103+
<div class="inputs-card">
104+
<div class="input-group">
105+
<label>${t.labelHex}</label>
106+
<div class="copy-wrapper">
107+
<input type="text" id="hex-input" value="#3b82f6" placeholder="${t.placeholderHex}">
108+
<button class="btn-copy" data-target="hex-input">${t.copyBtn}</button>
109+
</div>
110+
</div>
111+
<div class="input-group">
112+
<label>${t.labelRgb}</label>
113+
<div class="rgb-inputs">
114+
<input type="number" id="rgb-r" min="0" max="255" value="59">
115+
<input type="number" id="rgb-g" min="0" max="255" value="130">
116+
<input type="number" id="rgb-b" min="0" max="255" value="246">
117+
</div>
118+
<div class="copy-wrapper" style="margin-top: 0.5rem;">
119+
<input type="text" id="rgb-string" readonly value="rgb(59, 130, 246)">
120+
<button class="btn-copy" data-target="rgb-string">${t.copyBtn}</button>
121+
</div>
122+
</div>
123+
<div class="input-group">
124+
<label>${t.labelHsl}</label>
125+
<div class="hsl-inputs">
126+
<input type="number" id="hsl-h" min="0" max="360" value="217">
127+
<input type="number" id="hsl-s" min="0" max="100" value="91">
128+
<input type="number" id="hsl-l" min="0" max="100" value="60">
129+
</div>
130+
<div class="copy-wrapper" style="margin-top: 0.5rem;">
131+
<input type="text" id="hsl-string" readonly value="hsl(217, 91%, 60%)">
132+
<button class="btn-copy" data-target="hsl-string">${t.copyBtn}</button>
133+
</div>
134+
</div>
135+
</div>
136+
</div>
137+
</div>
138+
`;
139+
140+
const picker = document.getElementById('color-picker');
141+
const preview = document.getElementById('color-preview');
142+
const hexInput = document.getElementById('hex-input');
143+
const rgbR = document.getElementById('rgb-r');
144+
const rgbG = document.getElementById('rgb-g');
145+
const rgbB = document.getElementById('rgb-b');
146+
const rgbString = document.getElementById('rgb-string');
147+
const hslH = document.getElementById('hsl-h');
148+
const hslS = document.getElementById('hsl-s');
149+
const hslL = document.getElementById('hsl-l');
150+
const hslString = document.getElementById('hsl-string');
151+
152+
// Helper: Hex to RGB
153+
function hexToRgb(hex) {
154+
hex = hex.replace(/^#/, '');
155+
if (hex.length === 3) {
156+
hex = hex.split('').map(c => c + c).join('');
157+
}
158+
const bigint = parseInt(hex, 16);
159+
return {
160+
r: (bigint >> 16) & 255,
161+
g: (bigint >> 8) & 255,
162+
b: bigint & 255
163+
};
164+
}
165+
166+
// Helper: RGB to Hex
167+
function rgbToHex(r, g, b) {
168+
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();
169+
}
170+
171+
// Helper: RGB to HSL
172+
function rgbToHsl(r, g, b) {
173+
r /= 255; g /= 255; b /= 255;
174+
const max = Math.max(r, g, b), min = Math.min(r, g, b);
175+
let h, s, l = (max + min) / 2;
176+
if (max === min) {
177+
h = s = 0;
178+
} else {
179+
const d = max - min;
180+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
181+
switch (max) {
182+
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
183+
case g: h = (b - r) / d + 2; break;
184+
case b: h = (r - g) / d + 4; break;
185+
}
186+
h /= 6;
187+
}
188+
return {
189+
h: Math.round(h * 360),
190+
s: Math.round(s * 100),
191+
l: Math.round(l * 100)
192+
};
193+
}
194+
195+
// Helper: HSL to RGB
196+
function hslToRgb(h, s, l) {
197+
h /= 360; s /= 100; l /= 100;
198+
let r, g, b;
199+
if (s === 0) {
200+
r = g = b = l;
201+
} else {
202+
const hue2rgb = (p, q, t) => {
203+
if (t < 0) t += 1;
204+
if (t > 1) t -= 1;
205+
if (t < 1/6) return p + (q - p) * 6 * t;
206+
if (t < 1/2) return q;
207+
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
208+
return p;
209+
};
210+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
211+
const p = 2 * l - q;
212+
r = hue2rgb(p, q, h + 1/3);
213+
g = hue2rgb(p, q, h);
214+
b = hue2rgb(p, q, h - 1/3);
215+
}
216+
return {
217+
r: Math.round(r * 255),
218+
g: Math.round(g * 255),
219+
b: Math.round(b * 255)
220+
};
221+
}
222+
223+
function updateUI(hex, r, g, b, h, s, l) {
224+
preview.style.backgroundColor = hex;
225+
picker.value = hex;
226+
hexInput.value = hex;
227+
rgbR.value = r;
228+
rgbG.value = g;
229+
rgbB.value = b;
230+
rgbString.value = `rgb(${r}, ${g}, ${b})`;
231+
hslH.value = h;
232+
hslS.value = s;
233+
hslL.value = l;
234+
hslString.value = `hsl(${h}, ${s}%, ${l}%)`;
235+
}
236+
237+
picker.addEventListener('input', (e) => {
238+
const hex = e.target.value.toUpperCase();
239+
const rgb = hexToRgb(hex);
240+
const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
241+
updateUI(hex, rgb.r, rgb.g, rgb.b, hsl.h, hsl.s, hsl.l);
242+
});
243+
244+
hexInput.addEventListener('input', (e) => {
245+
let hex = e.target.value;
246+
if (/^#?[0-9A-F]{6}$/i.test(hex)) {
247+
if (!hex.startsWith('#')) hex = '#' + hex;
248+
hex = hex.toUpperCase();
249+
const rgb = hexToRgb(hex);
250+
const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
251+
updateUI(hex, rgb.r, rgb.g, rgb.b, hsl.h, hsl.s, hsl.l);
252+
}
253+
});
254+
255+
[rgbR, rgbG, rgbB].forEach(el => {
256+
el.addEventListener('input', () => {
257+
let r = parseInt(rgbR.value) || 0;
258+
let g = parseInt(rgbG.value) || 0;
259+
let b = parseInt(rgbB.value) || 0;
260+
r = Math.min(255, Math.max(0, r));
261+
g = Math.min(255, Math.max(0, g));
262+
b = Math.min(255, Math.max(0, b));
263+
const hex = rgbToHex(r, g, b);
264+
const hsl = rgbToHsl(r, g, b);
265+
updateUI(hex, r, g, b, hsl.h, hsl.s, hsl.l);
266+
});
267+
});
268+
269+
[hslH, hslS, hslL].forEach(el => {
270+
el.addEventListener('input', () => {
271+
let h = parseInt(hslH.value) || 0;
272+
let s = parseInt(hslS.value) || 0;
273+
let l = parseInt(hslL.value) || 0;
274+
h = Math.min(360, Math.max(0, h));
275+
s = Math.min(100, Math.max(0, s));
276+
l = Math.min(100, Math.max(0, l));
277+
const rgb = hslToRgb(h, s, l);
278+
const hex = rgbToHex(rgb.r, rgb.g, rgb.b);
279+
updateUI(hex, rgb.r, rgb.g, rgb.b, h, s, l);
280+
});
281+
});
282+
283+
// Copy buttons logic
284+
container.querySelectorAll('.btn-copy').forEach(btn => {
285+
btn.onclick = () => {
286+
const targetId = btn.getAttribute('data-target');
287+
const input = document.getElementById(targetId);
288+
input.select();
289+
document.execCommand('copy');
290+
291+
const originalText = btn.innerText;
292+
btn.innerText = t.copied;
293+
setTimeout(() => { btn.innerText = originalText; }, 2000);
294+
};
295+
});
296+
})();

config/_default/languages.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ title = "Fernweh的个人博客"
66
contentDir = "content/zh-cn"
77
weight = 1
88
[zh-cn.params.sidebar]
9-
subtitle = "我的数字花园 🌱 - 基于 GitHub Pages 搭建的博客与文档。欢迎分享想法!"
9+
subtitle = "我的数字花园 🌱 - 基于 GitHub Pages 搭建的博客网站。欢迎分享想法!"
1010

1111
[en]
1212
languageName = "English"
@@ -15,4 +15,4 @@ title = "Personal Blogs for Fernweh"
1515
contentDir = "content/en"
1616
weight = 2
1717
[en.params.sidebar]
18-
subtitle = "My digital garden 🌱 - A blog and docs built with GitHub Pages. Thoughts welcome!"
18+
subtitle = "My digital garden 🌱 - A blog website built with GitHub Pages. Thoughts welcome!"

content/en/tools/color/index.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
title: "Color Picker & Converter"
3+
description: "Online color picker and converter for HEX, RGB, and HSL formats."
4+
date: 2026-03-17
5+
layout: "page"
6+
---
7+
8+
A simple and intuitive tool to pick colors and convert them between HEX, RGB, and HSL formats.
9+
10+
{{< tool id="color" >}}
11+
12+
### How to use
13+
- **Pick a Color**: Use the color picker or click the preview box to choose a color visually.
14+
- **HEX Input**: Enter a 6-digit hex code (e.g., `#FF5733`) to see its RGB and HSL equivalents.
15+
- **RGB/HSL Inputs**: Adjust the individual values to fine-tune your color.
16+
- **Copy**: Click the **Copy** button next to any format to copy it to your clipboard.
17+
18+
### Understanding Color Formats
19+
- **HEX**: A hexadecimal representation of RGB values, commonly used in web design.
20+
- **RGB**: Red, Green, and Blue values (0-255).
21+
- **HSL**: Hue (0-360), Saturation (0-100%), and Lightness (0-100%). It's often more intuitive for humans to understand than RGB.

content/zh-cn/tools/color/index.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
title: "取色器与颜色转换器"
3+
description: "在线取色器,支持 HEX、RGB 和 HSL 格式之间的转换。"
4+
date: 2026-03-17
5+
layout: "page"
6+
---
7+
8+
一个简单直观的工具,用于选取颜色并在 HEX、RGB 和 HSL 格式之间进行转换。
9+
10+
{{< tool id="color" >}}
11+
12+
### 如何使用
13+
- **选取颜色**: 使用颜色选择器或点击预览框来直观地选择颜色。
14+
- **HEX 输入**: 输入 6 位十六进制代码(例如 `#FF5733`)以查看其对应的 RGB 和 HSL 值。
15+
- **RGB/HSL 输入**: 调整各个数值以微调颜色。
16+
- **复制**: 点击任何格式旁边的 **复制** 按钮将其复制到剪贴板。
17+
18+
### 了解颜色格式
19+
- **HEX**: RGB 值的十六进制表示,常用于网页设计。
20+
- **RGB**: 红、绿、蓝数值 (0-255)。
21+
- **HSL**: 色相 (0-360)、饱和度 (0-100%) 和 亮度 (0-100%)。对于人类来说,它通常比 RGB 更直观。

0 commit comments

Comments
 (0)