如何在网页不实际滚动时捕获滚动输入?用于光谱颜色选择器调饱和度
解决方案:用虚拟滚动容器实现无页面滚动的饱和度调节
核心思路
不需要让页面整体滚动,而是创建一个铺满视口的隐藏滚动条的容器,容器内部放置一个高度超出自身的元素(提供滚动范围),通过监听容器的滚动位置计算饱和度值——既可以用滚动操作调节饱和度,又能保持页面内容居中,完全看不到滚动条。
修改后的完整代码
HTML + CSS + JS
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>HTML Colour Picker</title> <style> html, body { height: 100%; margin: 0; font-family: monospace; } p {margin: 0px;} /* 滚动容器:铺满视口,隐藏滚动条 */ .scroll-container { width: 100%; height: 100vh; overflow-y: scroll; scrollbar-width: none; /* Firefox */ -ms-overflow-style: none; /* IE/Edge */ position: relative; } .scroll-container::-webkit-scrollbar { display: none; /* Chrome/Safari */ } /* 滚动内容:高度设为200%,提供0-100%的滚动范围对应饱和度 */ .scroll-content { height: 200%; } /* 颜色选择器区域:绝对定位覆盖容器 */ .picker-area { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient(to left, black, transparent, white), linear-gradient(#f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00); } /* 信息面板与颜色输入框样式 */ .info { position: absolute; top: 10px; left: 10px; background: rgba(255,255,255,0.8); padding: 8px; border-radius: 4px; z-index: 10; } input[type="color"] { position: absolute; top: 10px; right: 10px; z-index: 10; width: 40px; height: 40px; border: none; cursor: pointer; } svg { position: absolute; top: 0; left: 0; z-index: 5; width: 100%; height: 100%; pointer-events: none; } </style> </head> <body> <div class="scroll-container" id="scrollContainer"> <div class="scroll-content"></div> <div class="picker-area" id="pickerArea"></div> </div> <input type="color" id="colorInput"> <div class="info"> <p id="pos">pos: move the cursor to start</p> <p id="rgb">red: move the cursor to start</p> <p id="hsl">red: move the cursor to start</p> <p id="hex">hex: move the cursor to start</p> </div> <svg width="16px" height="16px" id="cursorSvg"> <circle cx="8" cy="8" r="8" fill="#fff6" stroke="black" stroke-width="2"></circle> </svg> <script> const scrollContainer = document.getElementById('scrollContainer'); const pickerArea = document.getElementById('pickerArea'); const colorInput = document.getElementById('colorInput'); const posEl = document.getElementById('pos'); const rgbEl = document.getElementById('rgb'); const hslEl = document.getElementById('hsl'); const hexEl = document.getElementById('hex'); const cursorSvg = document.getElementById('cursorSvg'); let currentSaturation = 100; // 初始饱和度100% let currentHue = 0; let currentLightness = 50; // 监听滚动事件,计算饱和度 scrollContainer.addEventListener('scroll', () => { const scrollRatio = scrollContainer.scrollTop / (scrollContainer.scrollHeight - scrollContainer.clientHeight); currentSaturation = Math.round(scrollRatio * 100); updatePickerBackground(); updateColorInfo(); }); // 监听鼠标移动,获取色相和亮度 pickerArea.addEventListener('mousemove', (e) => { currentLightness = Math.round((e.clientX / window.innerWidth) * 100); currentHue = Math.round((e.clientY / window.innerHeight) * 360); // 更新光标位置 cursorSvg.style.left = `${e.clientX - 8}px`; cursorSvg.style.top = `${e.clientY - 8}px`; updateColorInfo(); }); // 点击选择器同步颜色到输入框 pickerArea.addEventListener('click', () => { const hex = hslToHex(currentHue, currentSaturation, currentLightness); colorInput.value = hex; }); // 输入框颜色变化同步到选择器 colorInput.addEventListener('input', (e) => { const rgb = hexToRgb(e.target.value); const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b); currentHue = Math.round(hsl.h); currentSaturation = Math.round(hsl.s); currentLightness = Math.round(hsl.l); // 同步滚动位置 const scrollRatio = currentSaturation / 100; scrollContainer.scrollTop = scrollRatio * (scrollContainer.scrollHeight - scrollContainer.clientHeight); updatePickerBackground(); updateColorInfo(); }); // 动态更新选择器背景(根据当前饱和度) function updatePickerBackground() { const hueGradient = `linear-gradient( hsl(0, ${currentSaturation}%, 50%), hsl(60, ${currentSaturation}%, 50%), hsl(120, ${currentSaturation}%, 50%), hsl(180, ${currentSaturation}%, 50%), hsl(240, ${currentSaturation}%, 50%), hsl(300, ${currentSaturation}%, 50%), hsl(360, ${currentSaturation}%, 50%) )`; pickerArea.style.background = `linear-gradient(to left, black, transparent, white), ${hueGradient}`; } // 更新颜色信息显示 function updateColorInfo() { const rgb = hslToRgb(currentHue, currentSaturation, currentLightness); const hex = hslToHex(currentHue, currentSaturation, currentLightness); posEl.textContent = `pos: X=${currentLightness}, Y=${currentHue}`; rgbEl.textContent = `rgb: ${rgb.r}, ${rgb.g}, ${rgb.b}`; hslEl.textContent = `hsl: ${currentHue}, ${currentSaturation}%, ${currentLightness}%`; hexEl.textContent = `hex: ${hex}`; } // HSL转RGB工具函数 function hslToRgb(h, s, l) { s /= 100; l /= 100; const c = (1 - Math.abs(2 * l - 1)) * s; const x = c * (1 - Math.abs((h / 60) % 2 - 1)); const m = l - c/2; let r, g, b; if (h >= 0 && h < 60) {r = c; g = x; b = 0;} else if (h >= 60 && h < 120) {r = x; g = c; b = 0;} else if (h >= 120 && h < 180) {r = 0; g = c; b = x;} else if (h >= 180 && h < 240) {r = 0; g = x; b = c;} else if (h >= 240 && h < 300) {r = x; g = 0; b = c;} else {r = c; g = 0; b = x;} return { r: Math.round((r + m) * 255), g: Math.round((g + m) * 255), b: Math.round((b + m) * 255) }; } // HSL转HEX工具函数 function hslToHex(h, s, l) { const rgb = hslToRgb(h, s, l); return `#${((1 << 24) + (rgb.r << 16) + (rgb.g << 8) + rgb.b).toString(16).slice(1)}`; } // HEX转RGB工具函数 function hexToRgb(hex) { 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; } // RGB转HSL工具函数 function rgbToHsl(r, g, b) { r /= 255; g /= 255; b /= 255; const max = Math.max(r, g, b); const min = Math.min(r, g, b); let h, s, l = (max + min) / 2; if (max === min) {h = s = 0;} 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: h * 360, s: s * 100, l: l * 100 }; } // 初始化 updatePickerBackground(); </script> </body> </html>
关键细节说明
隐藏滚动条的容器:
- 给
.scroll-container设置overflow-y: scroll,同时通过浏览器专属属性隐藏滚动条,保证视觉上无多余元素。 - 内部
.scroll-content高度设为200%,让滚动范围刚好对应饱和度0-100%(滚动到顶部是0%,底部是100%,可通过100 - scrollRatio*100反转逻辑)。
- 给
滚动转饱和度的计算:
- 用
scrollTop / (scrollHeight - clientHeight)得到滚动比例,这个值是滚动距离占可滚动总范围的百分比,直接映射为饱和度数值。
- 用
动态背景更新:
- 将固定颜色渐变改为HSL动态生成,每个色相的饱和度都绑定当前计算值,滚动时背景会实时变化,直观反馈饱和度调整效果。
颜色信息同步:
- 鼠标移动时计算当前色相(Y轴)和亮度(X轴),结合滚动得到的饱和度,实时更新RGB、HSL、HEX信息,同时同步系统颜色选择器的输入值。
内容的提问来源于stack exchange,提问作者oosh
相关产品推荐
相关产品推荐

