解决:动态文本和背景色一致导致文字看不清楚,用js获取背景图片主色调,并获取对比度最大的hex色值给文字

sg.js公共方法

javascript 复制代码
    image: {
        /**
 * 获取图片主色调的 HEX 值
 * @param {string} imageUrl - 图片URL(需注意跨域问题)
 * @param {number} sampleStep - 采样步长(值越大速度越快,精度越低,默认10)
 * @returns {Promise<string>} 主色调的HEX值(如 #ff0000)
 * @returns {widthPercent} 采用图片的百分比宽度(小数表示识别图片的范围)
 * @returns {heightPercent} 采用图片的百分比高度(小数表示识别图片的范围)
 */
        getImageMainColorHex(imageUrl, { sampleStep = 10, widthPercent = 1, heightPercent = 1 } = {}) {
            return new Promise((resolve, reject) => {
                // 1. 创建图片对象
                const img = new Image();
                // 解决跨域问题(需服务器配合允许跨域)
                img.crossOrigin = 'Anonymous';

                img.onload = function () {
                    try {
                        // 2. 创建Canvas并绘制图片
                        const canvas = document.createElement('canvas');
                        const ctx = canvas.getContext('2d');
                        // 设置Canvas尺寸与图片一致
                        canvas.width = img.width;
                        canvas.height = img.height;
                        ctx.drawImage(img, 0, 0);

                        // 3. 获取像素数据(Uint8ClampedArray,每4个值代表一个像素的RGBA)
                        const imageData = ctx.getImageData(0, 0, canvas.width * widthPercent, canvas.height * heightPercent);
                        const data = imageData.data;
                        const colorCount = {}; // 存储颜色出现次数 { "r,g,b": count }

                        // 4. 采样像素(跳过部分像素提升性能)
                        for (let i = 0; i < data.length; i += 4 * sampleStep) {
                            const r = data[i];     // 红色通道 (0-255)
                            const g = data[i + 1]; // 绿色通道 (0-255)
                            const b = data[i + 2]; // 蓝色通道 (0-255)
                            const a = data[i + 3]; // 透明度通道 (0-255,透明像素跳过)

                            // 跳过透明像素
                            if (a < 128) continue;

                            // 用 "r,g,b" 作为键统计次数
                            const colorKey = `${r},${g},${b}`;
                            colorCount[colorKey] = (colorCount[colorKey] || 0) + 1;
                        }

                        // 5. 找到出现次数最多的颜色
                        let maxCount = 0;
                        let mainColorKey = '255,255,255'; // 默认白色
                        for (const [key, count] of Object.entries(colorCount)) {
                            if (count > maxCount) {
                                maxCount = count;
                                mainColorKey = key;
                            }
                        }

                        // 6. 将 RGB 转换为 HEX 格式
                        const [r, g, b] = mainColorKey.split(',').map(Number);
                        const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;

                        resolve(hex);
                    } catch (error) {
                        reject(new Error(`获取主色调失败:${error.message}`));
                    }
                };

                img.onerror = function () {
                    reject(new Error('图片加载失败(可能是跨域、URL错误或网络问题)'));
                };

                // 7. 加载图片(最后执行,避免onload未绑定完成)
                img.src = imageUrl;
            });
        },
        /**
 * 将 Hex 色值转换为 RGB 对象
 * @param {string} hex - 支持 #fff、#ffffff、fff 等格式
 * @returns {object} { r: number, g: number, b: number }
 */
        hexToRgb(hex) {
            // 移除 # 号(如果有)
            const cleanHex = hex.replace(/^#/, '');

            // 处理 3 位简写的 Hex 色值(如 #fff → #ffffff)
            const fullHex = cleanHex.length === 3
                ? cleanHex.split('').map(char => char + char).join('')
                : cleanHex;

            // 验证 Hex 格式是否正确
            if (!/^[0-9A-Fa-f]{6}$/.test(fullHex)) {
                throw new Error('无效的 Hex 色值格式,请检查输入');
            }

            // 转换为 RGB 数值
            return {
                r: parseInt(fullHex.substring(0, 2), 16),
                g: parseInt(fullHex.substring(2, 4), 16),
                b: parseInt(fullHex.substring(4, 6), 16)
            };
        },

        /**
         * 计算颜色的相对亮度(WCAG 公式)
         * @param {object} rgb - { r: number, g: number, b: number }
         * @returns {number} 亮度值(0-1 之间)
         */
        getRelativeLuminance(rgb) {
            // 将 RGB 值转换为 0-1 范围,并进行伽马校正
            const normalize = (value) => {
                const v = value / 255;
                return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
            };

            const r = normalize(rgb.r);
            const g = normalize(rgb.g);
            const b = normalize(rgb.b);

            // 计算相对亮度
            return 0.2126 * r + 0.7152 * g + 0.0722 * b;
        },

        /**
         * 生成高对比度的对比色(符合 WCAG 标准)
         * @param {string} hex - Hex 色值
         * @param {object} [options] - 可选配置
         * @param {string} [options.lightColor='#ffffff'] - 亮色背景的对比色
         * @param {string} [options.darkColor='#000000'] - 暗色背景的对比色
         * @returns {string} 对比色的 Hex 值
         */
        getContrastColor(hex, options = {}) {
            const {
                lightColor = '#ffffff',
                darkColor = '#000000'
            } = options;

            try {
                const rgb = this.hexToRgb(hex);
                const luminance = this.getRelativeLuminance(rgb);

                // 亮度阈值 0.5(可调整,0.4-0.6 之间都合理)
                return luminance > 0.5 ? darkColor : lightColor;
            } catch (error) {
                console.error('生成对比色失败:', error.message);
                return '#000000'; // 默认返回黑色
            }
        },

        /**
         * 生成 Hex 色值的反向色(反色)
         * @param {string} hex - Hex 色值
         * @returns {string} 反向色的 Hex 值
         */
        getInverseColor(hex) {
            try {
                const rgb = this.hexToRgb(hex);
                // 计算反色:255 - 原数值
                const r = 255 - rgb.r;
                const g = 255 - rgb.g;
                const b = 255 - rgb.b;

                // 转换回 Hex 格式
                const toHex = (value) => value.toString(16).padStart(2, '0');
                return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
            } catch (error) {
                console.error('生成反向色失败:', error.message);
                return '#ffffff'; // 默认返回白色
            }
        },
        // -------------------------------------
}

demo

html 复制代码
<template>
  <div :class="$options.name" style="background-color: lightgray">
    <h1>
      图片主色调:<span :style="{ color: hex }">{{ hex }}</span>
    </h1>
    <h1>
      图片主色调的对比度HEX值:<span :style="{ color: contrastColor }">{{ contrastColor }}</span>
    </h1>
    <h1>
      图片主色调的反色HEX值:<span :style="{ color: inverseColor }">{{ inverseColor }}</span>
    </h1>
  </div>
</template>
<script>
export default {
  data() {
    return {
      hex: null,
      contrastColor: null,
      inverseColor:null,
    };
  },
  created() {
    // ------------------- 测试使用示例 -------------------
    // 注意:图片URL需满足跨域要求(同域/配置CORS/Base64)
    let testImageUrl =
      "https://pss.bdstatic.com/static/superman/img/logo/logo_white-d0c9fe2af5.png"; // 替换为实际图片URL
    testImageUrl = `~@/../static/img/bg/activeDetail/1.jpg`;
    testImageUrl = `~@/../static/demo/testimg.jpg`;
    this.$g.image
      .getImageMainColorHex(testImageUrl, { heightPercent: 0.15 })
      .then((hex) => {
        this.hex = hex;
        this.contrastColor = this.$g.image.getContrastColor(this.hex);
        this.inverseColor = this.$g.image.getInverseColor(this.hex);
        console.log(`图片主色调的对比度HEX值:`, this.contrastColor);
        console.log(`图片主色调的反色HEX值:`, this.inverseColor);
      })
      .catch((error) => {
        console.error(`错误:${error.message}`);
      });
  },
};
</script>
相关推荐
送鱼的老默2 小时前
学习笔记--vue3 watchEffect监听的各种姿势用法和总结
前端·vue.js
英俊潇洒美少年2 小时前
js 同步异步,宏任务微任务的关系
开发语言·javascript·ecmascript
用户69371750013842 小时前
Android 手机终于能当电脑用了
android·前端
wooyoo2 小时前
花了一周 vibe 了一个 OpenClaw 的 Agent 市场,聊聊过程中踩的坑
前端·后端·agent
angerdream2 小时前
最新版vue3+TypeScript开发入门到实战教程之路由详解
前端·javascript·vue.js
送鱼的老默2 小时前
学习笔记--vue3 watch监听的各种姿势用法和总结
前端·vue.js
猪八宅百炼成仙2 小时前
解决 el-date-picker type:daterange 在 layout 布局中的宽度问题
前端·element
小贺要学前端3 小时前
ES6 还没用明白,JavaScript 已经快到 ES2026 了
前端·javascript·es6
逛逛GitHub3 小时前
最近用的贼多的 3 个 Claude Code 开源宝藏,感觉太爽了。
github