You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在网页不实际滚动时捕获滚动输入?用于光谱颜色选择器调饱和度

解决方案:用虚拟滚动容器实现无页面滚动的饱和度调节

核心思路

不需要让页面整体滚动,而是创建一个铺满视口的隐藏滚动条的容器,容器内部放置一个高度超出自身的元素(提供滚动范围),通过监听容器的滚动位置计算饱和度值——既可以用滚动操作调节饱和度,又能保持页面内容居中,完全看不到滚动条。


修改后的完整代码

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>

关键细节说明

  1. 隐藏滚动条的容器:

    • 给.scroll-container设置overflow-y: scroll,同时通过浏览器专属属性隐藏滚动条,保证视觉上无多余元素。
    • 内部.scroll-content高度设为200%,让滚动范围刚好对应饱和度0-100%(滚动到顶部是0%,底部是100%,可通过100 - scrollRatio*100反转逻辑)。
  2. 滚动转饱和度的计算:

    • 用scrollTop / (scrollHeight - clientHeight)得到滚动比例,这个值是滚动距离占可滚动总范围的百分比,直接映射为饱和度数值。
  3. 动态背景更新:

    • 将固定颜色渐变改为HSL动态生成,每个色相的饱和度都绑定当前计算值,滚动时背景会实时变化,直观反馈饱和度调整效果。
  4. 颜色信息同步:

    • 鼠标移动时计算当前色相(Y轴)和亮度(X轴),结合滚动得到的饱和度,实时更新RGB、HSL、HEX信息,同时同步系统颜色选择器的输入值。

内容的提问来源于stack exchange,提问作者oosh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 16:02:02