diff --git a/main.js b/main.js index 090ffa1..6e35083 100644 --- a/main.js +++ b/main.js @@ -9,17 +9,35 @@ const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.querySelector('#app').innerHTML = `
-
- - +
+

RGB

+
+ + +
+
+ + +
+
+ + +
-
- - -
-
- - +
+

HSV

+
+ + +
+
+ + +
+
+ + +
@@ -157,30 +175,108 @@ scene.add(createLabel('R', new THREE.Vector3(1.3, 0, 0))); scene.add(createLabel('G', new THREE.Vector3(0, 1.3, 0))); scene.add(createLabel('B', new THREE.Vector3(0, 0, 1.3))); -// スライダーの制御 -function updateMarkerPosition() { - // 256段階から0-1の範囲に変換 - const r = (parseFloat(document.getElementById('r-slider').value) - 1) / 255; - const g = (parseFloat(document.getElementById('g-slider').value) - 1) / 255; - const b = (parseFloat(document.getElementById('b-slider').value) - 1) / 255; +// HSVからRGBへの変換関数を追加 +function hsvToRgb(h, s, v) { + s = s / 100; + v = v / 100; + const i = Math.floor(h / 60); + const f = h / 60 - i; + const p = v * (1 - s); + const q = v * (1 - f * s); + const t = v * (1 - (1 - f) * s); + + let r, g, b; + switch (i % 6) { + case 0: [r, g, b] = [v, t, p]; break; + case 1: [r, g, b] = [q, v, p]; break; + case 2: [r, g, b] = [p, v, t]; break; + case 3: [r, g, b] = [p, q, v]; break; + case 4: [r, g, b] = [t, p, v]; break; + case 5: [r, g, b] = [v, p, q]; break; + } + return [r, g, b]; +} + +// RGBからHSVへの変換関数を追加 +function rgbToHsv(r, g, b) { + r = r / 255; + g = g / 255; + b = b / 255; - marker.position.set(r, g, b); + const max = Math.max(r, g, b); + const min = Math.min(r, g, b); + const d = max - min; - // マーカーの色を更新 - marker.material.color.setRGB(r, g, b); + let h, s, v; + v = max; + s = max === 0 ? 0 : d / max; - // 値の表示を更新(1-256の範囲で表示) - document.getElementById('r-value').textContent = Math.round(r * 255 + 1); - document.getElementById('g-value').textContent = Math.round(g * 255 + 1); - document.getElementById('b-value').textContent = Math.round(b * 255 + 1); + if (max === min) { + h = 0; + } else { + 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 *= 60; + } - // カラープレビューの更新 + return [h, s * 100, v * 100]; +} + +// スライダーの制御関数を更新 +function updateMarkerPosition(source = 'rgb') { + if (source === 'rgb') { + // RGBスライダーからの更新 + const r = (parseFloat(document.getElementById('r-slider').value) - 1) / 255; + const g = (parseFloat(document.getElementById('g-slider').value) - 1) / 255; + const b = (parseFloat(document.getElementById('b-slider').value) - 1) / 255; + + // HSVスライダーを更新 + const [h, s, v] = rgbToHsv(r * 255, g * 255, b * 255); + document.getElementById('h-slider').value = h; + document.getElementById('s-slider').value = s; + document.getElementById('v-slider').value = v; + document.getElementById('h-value').textContent = Math.round(h); + document.getElementById('s-value').textContent = Math.round(s); + document.getElementById('v-value').textContent = Math.round(v); + + updateMarkerAndColor(r, g, b); + } else { + // HSVスライダーからの更新 + const h = parseFloat(document.getElementById('h-slider').value); + const s = parseFloat(document.getElementById('s-slider').value); + const v = parseFloat(document.getElementById('v-slider').value); + + const [r, g, b] = hsvToRgb(h, s, v); + + // RGBスライダーを更新 + document.getElementById('r-slider').value = Math.round(r * 255) + 1; + document.getElementById('g-slider').value = Math.round(g * 255) + 1; + document.getElementById('b-slider').value = Math.round(b * 255) + 1; + document.getElementById('r-value').textContent = Math.round(r * 255) + 1; + document.getElementById('g-value').textContent = Math.round(g * 255) + 1; + document.getElementById('b-value').textContent = Math.round(b * 255) + 1; + + updateMarkerAndColor(r, g, b); + } +} + +function updateMarkerAndColor(r, g, b) { + marker.position.set(r, g, b); + marker.material.color.setRGB(r, g, b); document.getElementById('current-color').style.backgroundColor = `rgb(${r * 255}, ${g * 255}, ${b * 255})`; } +// イベントリスナーを更新 ['r-slider', 'g-slider', 'b-slider'].forEach(id => { - document.getElementById(id).addEventListener('input', updateMarkerPosition); + document.getElementById(id).addEventListener('input', () => updateMarkerPosition('rgb')); +}); + +['h-slider', 's-slider', 'v-slider'].forEach(id => { + document.getElementById(id).addEventListener('input', () => updateMarkerPosition('hsv')); }); // アニメーションループ diff --git a/style.css b/style.css index b497889..0c1e260 100644 --- a/style.css +++ b/style.css @@ -103,6 +103,18 @@ button:focus-visible { padding: 20px; border-radius: 10px; color: white; + min-width: 250px; + } + + .control-section { + margin-bottom: 20px; + } + + .control-section h3 { + margin: 0 0 10px 0; + font-size: 1.1em; + border-bottom: 1px solid rgba(255, 255, 255, 0.3); + padding-bottom: 5px; } .slider-group { @@ -112,12 +124,15 @@ button:focus-visible { .slider-group label { display: block; margin-bottom: 5px; + font-size: 0.9em; } input[type="range"] { - width: 200px; + width: 100%; + margin: 0; } #current-color { - margin-top: 10px; + margin: 10px auto; + border-radius: 5px; }