[js] 思源界面主题色跟随当前题头图变化的 js 片段

思源界面主题色跟随当前题头图变化的 js 片段

目前有按钮等变化,其他变化自行添加 css 容器

/*! Fast Average Color | © 2025 Denis Seleznev | MIT License | https://github.com/fast-average-color/fast-average-color */
'use strict';
function toHex(num) {
    var str = num.toString(16);
    return str.length === 1 ? '0' + str : str;
}
function arrayToHex(arr) {
    return '#' + arr.map(toHex).join('');
}
function isDark(color) {
    // http://www.w3.org/TR/AERT#color-contrast
    var result = (color[0] * 299 + color[1] * 587 + color[2] * 114) / 1000;
    return result < 128;
}
function prepareIgnoredColor(color) {
    if (!color) {
        return [];
    }
    return isRGBArray(color) ? color : [color];
}
function isRGBArray(value) {
    return Array.isArray(value[0]);
}
function isIgnoredColor(data, index, ignoredColor) {
    for (var i = 0; i < ignoredColor.length; i++) {
        if (isIgnoredColorAsNumbers(data, index, ignoredColor[i])) {
            return true;
        }
    }
    return false;
}
function isIgnoredColorAsNumbers(data, index, ignoredColor) {
    switch (ignoredColor.length) {
        case 3:
            // [red, green, blue]
            if (isIgnoredRGBColor(data, index, ignoredColor)) {
                return true;
            }
            break;
        case 4:
            // [red, green, blue, alpha]
            if (isIgnoredRGBAColor(data, index, ignoredColor)) {
                return true;
            }
            break;
        case 5:
            // [red, green, blue, alpha, threshold]
            if (isIgnoredRGBAColorWithThreshold(data, index, ignoredColor)) {
                return true;
            }
            break;
        default:
            return false;
    }
}
function isIgnoredRGBColor(data, index, ignoredColor) {
    // Ignore if the pixel are transparent.
    if (data[index + 3] !== 255) {
        return true;
    }
    if (data[index] === ignoredColor[0] &&
        data[index + 1] === ignoredColor[1] &&
        data[index + 2] === ignoredColor[2]) {
        return true;
    }
    return false;
}
function isIgnoredRGBAColor(data, index, ignoredColor) {
    if (data[index + 3] && ignoredColor[3]) {
        return data[index] === ignoredColor[0] &&
            data[index + 1] === ignoredColor[1] &&
            data[index + 2] === ignoredColor[2] &&
            data[index + 3] === ignoredColor[3];
    }
    // Ignore rgb components if the pixel are fully transparent.
    return data[index + 3] === ignoredColor[3];
}
function inRange(colorComponent, ignoredColorComponent, value) {
    return colorComponent >= (ignoredColorComponent - value) &&
        colorComponent <= (ignoredColorComponent + value);
}
function isIgnoredRGBAColorWithThreshold(data, index, ignoredColor) {
    var redIgnored = ignoredColor[0];
    var greenIgnored = ignoredColor[1];
    var blueIgnored = ignoredColor[2];
    var alphaIgnored = ignoredColor[3];
    var threshold = ignoredColor[4];
    var alphaData = data[index + 3];
    var alphaInRange = inRange(alphaData, alphaIgnored, threshold);
    if (!alphaIgnored) {
        return alphaInRange;
    }
    if (!alphaData && alphaInRange) {
        return true;
    }
    if (inRange(data[index], redIgnored, threshold) &&
        inRange(data[index + 1], greenIgnored, threshold) &&
        inRange(data[index + 2], blueIgnored, threshold) &&
        alphaInRange) {
        return true;
    }
    return false;
}
var DEFAULT_DOMINANT_DIVIDER = 24;
function dominantAlgorithm(arr, len, options) {
    var colorHash = {};
    var divider = options.dominantDivider || DEFAULT_DOMINANT_DIVIDER;
    var ignoredColor = options.ignoredColor;
    var step = options.step;
    var max = [0, 0, 0, 0, 0];
    for (var i = 0; i < len; i += step) {
        var red = arr[i];
        var green = arr[i + 1];
        var blue = arr[i + 2];
        var alpha = arr[i + 3];
        if (ignoredColor && isIgnoredColor(arr, i, ignoredColor)) {
            continue;
        }
        var key = Math.round(red / divider) + ',' +
            Math.round(green / divider) + ',' +
            Math.round(blue / divider);
        if (colorHash[key]) {
            colorHash[key] = [
                colorHash[key][0] + red * alpha,
                colorHash[key][1] + green * alpha,
                colorHash[key][2] + blue * alpha,
                colorHash[key][3] + alpha,
                colorHash[key][4] + 1
            ];
        }
        else {
            colorHash[key] = [red * alpha, green * alpha, blue * alpha, alpha, 1];
        }
        if (max[4] < colorHash[key][4]) {
            max = colorHash[key];
        }
    }
    var redTotal = max[0];
    var greenTotal = max[1];
    var blueTotal = max[2];
    var alphaTotal = max[3];
    var count = max[4];
    return alphaTotal ? [
        Math.round(redTotal / alphaTotal),
        Math.round(greenTotal / alphaTotal),
        Math.round(blueTotal / alphaTotal),
        Math.round(alphaTotal / count)
    ] : options.defaultColor;
}
function simpleAlgorithm(arr, len, options) {
    var redTotal = 0;
    var greenTotal = 0;
    var blueTotal = 0;
    var alphaTotal = 0;
    var count = 0;
    var ignoredColor = options.ignoredColor;
    var step = options.step;
    for (var i = 0; i < len; i += step) {
        var alpha = arr[i + 3];
        var red = arr[i] * alpha;
        var green = arr[i + 1] * alpha;
        var blue = arr[i + 2] * alpha;
        if (ignoredColor && isIgnoredColor(arr, i, ignoredColor)) {
            continue;
        }
        redTotal += red;
        greenTotal += green;
        blueTotal += blue;
        alphaTotal += alpha;
        count++;
    }
    return alphaTotal ? [
        Math.round(redTotal / alphaTotal),
        Math.round(greenTotal / alphaTotal),
        Math.round(blueTotal / alphaTotal),
        Math.round(alphaTotal / count)
    ] : options.defaultColor;
}
function sqrtAlgorithm(arr, len, options) {
    var redTotal = 0;
    var greenTotal = 0;
    var blueTotal = 0;
    var alphaTotal = 0;
    var count = 0;
    var ignoredColor = options.ignoredColor;
    var step = options.step;
    for (var i = 0; i < len; i += step) {
        var red = arr[i];
        var green = arr[i + 1];
        var blue = arr[i + 2];
        var alpha = arr[i + 3];
        if (ignoredColor && isIgnoredColor(arr, i, ignoredColor)) {
            continue;
        }
        redTotal += red * red * alpha;
        greenTotal += green * green * alpha;
        blueTotal += blue * blue * alpha;
        alphaTotal += alpha;
        count++;
    }
    return alphaTotal ? [
        Math.round(Math.sqrt(redTotal / alphaTotal)),
        Math.round(Math.sqrt(greenTotal / alphaTotal)),
        Math.round(Math.sqrt(blueTotal / alphaTotal)),
        Math.round(alphaTotal / count)
    ] : options.defaultColor;
}
function getDefaultColor(options) {
    return getOption(options, 'defaultColor', [0, 0, 0, 0]);
}
function getOption(options, name, defaultValue) {
    return (options[name] === undefined ? defaultValue : options[name]);
}
var MIN_SIZE = 10;
var MAX_SIZE = 100;
function isSvg(filename) {
    return filename.search(/\.svg(\?|$)/i) !== -1;
}
function getOriginalSize(resource) {
    if (isInstanceOfHTMLImageElement(resource)) {
        var width = resource.naturalWidth;
        var height = resource.naturalHeight;
        // For SVG images with only viewBox attribute
        if (!resource.naturalWidth && isSvg(resource.src)) {
            width = height = MAX_SIZE;
        }
        return {
            width: width,
            height: height,
        };
    }
    if (isInstanceOfHTMLVideoElement(resource)) {
        return {
            width: resource.videoWidth,
            height: resource.videoHeight
        };
    }
    if (isInstanceOfVideoFrame(resource)) {
        return {
            width: resource.codedWidth,
            height: resource.codedHeight,
        };
    }
    return {
        width: resource.width,
        height: resource.height
    };
}
function getSrc(resource) {
    if (isInstanceOfHTMLCanvasElement(resource)) {
        return 'canvas';
    }
    if (isInstanceOfOffscreenCanvas(resource)) {
        return 'offscreencanvas';
    }
    if (isInstanceOfVideoFrame(resource)) {
        return 'videoframe';
    }
    if (isInstanceOfImageBitmap(resource)) {
        return 'imagebitmap';
    }
    return resource.src;
}
function isInstanceOfHTMLImageElement(resource) {
    return typeof HTMLImageElement !== 'undefined' && resource instanceof HTMLImageElement;
}
var hasOffscreenCanvas = typeof OffscreenCanvas !== 'undefined';
function isInstanceOfOffscreenCanvas(resource) {
    return hasOffscreenCanvas && resource instanceof OffscreenCanvas;
}
function isInstanceOfHTMLVideoElement(resource) {
    return typeof HTMLVideoElement !== 'undefined' && resource instanceof HTMLVideoElement;
}
function isInstanceOfVideoFrame(resource) {
    return typeof VideoFrame !== 'undefined' && resource instanceof VideoFrame;
}
function isInstanceOfHTMLCanvasElement(resource) {
    return typeof HTMLCanvasElement !== 'undefined' && resource instanceof HTMLCanvasElement;
}
function isInstanceOfImageBitmap(resource) {
    return typeof ImageBitmap !== 'undefined' && resource instanceof ImageBitmap;
}
function prepareSizeAndPosition(originalSize, options) {
    var srcLeft = getOption(options, 'left', 0);
    var srcTop = getOption(options, 'top', 0);
    var srcWidth = getOption(options, 'width', originalSize.width);
    var srcHeight = getOption(options, 'height', originalSize.height);
    var destWidth = srcWidth;
    var destHeight = srcHeight;
    if (options.mode === 'precision') {
        return {
            srcLeft: srcLeft,
            srcTop: srcTop,
            srcWidth: srcWidth,
            srcHeight: srcHeight,
            destWidth: destWidth,
            destHeight: destHeight
        };
    }
    var factor;
    if (srcWidth > srcHeight) {
        factor = srcWidth / srcHeight;
        destWidth = MAX_SIZE;
        destHeight = Math.round(destWidth / factor);
    }
    else {
        factor = srcHeight / srcWidth;
        destHeight = MAX_SIZE;
        destWidth = Math.round(destHeight / factor);
    }
    if (destWidth > srcWidth || destHeight > srcHeight ||
        destWidth < MIN_SIZE || destHeight < MIN_SIZE) {
        destWidth = srcWidth;
        destHeight = srcHeight;
    }
    return {
        srcLeft: srcLeft,
        srcTop: srcTop,
        srcWidth: srcWidth,
        srcHeight: srcHeight,
        destWidth: destWidth,
        destHeight: destHeight
    };
}
var isWebWorkers = typeof window === 'undefined';
function makeCanvas() {
    if (isWebWorkers) {
        return hasOffscreenCanvas ? new OffscreenCanvas(1, 1) : null;
    }
    return document.createElement('canvas');
}
var ERROR_PREFIX = 'FastAverageColor: ';
function getError(message) {
    return Error(ERROR_PREFIX + message);
}
function outputError(error, silent) {
    if (!silent) {
        console.error(error);
    }
}
var FastAverageColor = /** @class */ (function () {
    function FastAverageColor() {
        this.canvas = null;
        this.ctx = null;
    }
    FastAverageColor.prototype.getColorAsync = function (resource, options) {
        if (!resource) {
            return Promise.reject(getError('call .getColorAsync() without resource'));
        }
        if (typeof resource === 'string') {
            // Web workers
            if (typeof Image === 'undefined') {
                return Promise.reject(getError('resource as string is not supported in this environment'));
            }
            var img = new Image();
            img.crossOrigin = options && options.crossOrigin || '';
            img.src = resource;
            return this.bindImageEvents(img, options);
        }
        else if (isInstanceOfHTMLImageElement(resource) && !resource.complete) {
            return this.bindImageEvents(resource, options);
        }
        else {
            var result = this.getColor(resource, options);
            return result.error ? Promise.reject(result.error) : Promise.resolve(result);
        }
    };
    /**
     * Get the average color from images, videos and canvas.
     */
    FastAverageColor.prototype.getColor = function (resource, options) {
        options = options || {};
        var defaultColor = getDefaultColor(options);
        if (!resource) {
            var error = getError('call .getColor() without resource');
            outputError(error, options.silent);
            return this.prepareResult(defaultColor, error);
        }
        var originalSize = getOriginalSize(resource);
        var size = prepareSizeAndPosition(originalSize, options);
        if (!size.srcWidth || !size.srcHeight || !size.destWidth || !size.destHeight) {
            var error = getError("incorrect sizes for resource \"".concat(getSrc(resource), "\""));
            outputError(error, options.silent);
            return this.prepareResult(defaultColor, error);
        }
        if (!this.canvas) {
            this.canvas = makeCanvas();
            if (!this.canvas) {
                var error = getError('OffscreenCanvas is not supported in this browser');
                outputError(error, options.silent);
                return this.prepareResult(defaultColor, error);
            }
        }
        if (!this.ctx) {
            this.ctx = this.canvas.getContext('2d', { willReadFrequently: true });
            if (!this.ctx) {
                var error = getError('Canvas Context 2D is not supported in this browser');
                outputError(error, options.silent);
                return this.prepareResult(defaultColor);
            }
            this.ctx.imageSmoothingEnabled = false;
        }
        this.canvas.width = size.destWidth;
        this.canvas.height = size.destHeight;
        try {
            this.ctx.clearRect(0, 0, size.destWidth, size.destHeight);
            this.ctx.drawImage(resource, size.srcLeft, size.srcTop, size.srcWidth, size.srcHeight, 0, 0, size.destWidth, size.destHeight);
            var bitmapData = this.ctx.getImageData(0, 0, size.destWidth, size.destHeight).data;
            return this.prepareResult(this.getColorFromArray4(bitmapData, options));
        }
        catch (originalError) {
            var error = getError("security error (CORS) for resource ".concat(getSrc(resource), ".\nDetails: https://developer.mozilla.org/en/docs/Web/HTML/CORS_enabled_image"));
            outputError(error, options.silent);
            if (!options.silent) {
                console.error(originalError);
            }
            return this.prepareResult(defaultColor, error);
        }
    };
    /**
     * Get the average color from a array when 1 pixel is 4 bytes.
     */
    FastAverageColor.prototype.getColorFromArray4 = function (arr, options) {
        options = options || {};
        var bytesPerPixel = 4;
        var arrLength = arr.length;
        var defaultColor = getDefaultColor(options);
        if (arrLength < bytesPerPixel) {
            return defaultColor;
        }
        var len = arrLength - arrLength % bytesPerPixel;
        var step = (options.step || 1) * bytesPerPixel;
        var algorithm;
        switch (options.algorithm || 'sqrt') {
            case 'simple':
                algorithm = simpleAlgorithm;
                break;
            case 'sqrt':
                algorithm = sqrtAlgorithm;
                break;
            case 'dominant':
                algorithm = dominantAlgorithm;
                break;
            default:
                throw getError("".concat(options.algorithm, " is unknown algorithm"));
        }
        return algorithm(arr, len, {
            defaultColor: defaultColor,
            ignoredColor: prepareIgnoredColor(options.ignoredColor),
            step: step,
            dominantDivider: options.dominantDivider,
        });
    };
    /**
     * Get color data from value ([r, g, b, a]).
     */
    FastAverageColor.prototype.prepareResult = function (value, error) {
        var rgb = value.slice(0, 3);
        var rgba = [value[0], value[1], value[2], value[3] / 255];
        var isDarkColor = isDark(value);
        return {
            value: [value[0], value[1], value[2], value[3]],
            rgb: 'rgb(' + rgb.join(',') + ')',
            rgba: 'rgba(' + rgba.join(',') + ')',
            hex: arrayToHex(rgb),
            hexa: arrayToHex(value),
            isDark: isDarkColor,
            isLight: !isDarkColor,
            error: error,
        };
    };
    /**
     * Destroy the instance.
     */
    FastAverageColor.prototype.destroy = function () {
        if (this.canvas) {
            this.canvas.width = 1;
            this.canvas.height = 1;
            this.canvas = null;
        }
        this.ctx = null;
    };
    FastAverageColor.prototype.bindImageEvents = function (resource, options) {
        var _this = this;
        return new Promise(function (resolve, reject) {
            var onload = function () {
                unbindEvents();
                var result = _this.getColor(resource, options);
                if (result.error) {
                    reject(result.error);
                }
                else {
                    resolve(result);
                }
            };
            var onerror = function () {
                unbindEvents();
                reject(getError("Error loading image \"".concat(resource.src, "\"")));
            };
            var onabort = function () {
                unbindEvents();
                reject(getError("Image \"".concat(resource.src, "\" loading aborted")));
            };
            var unbindEvents = function () {
                resource.removeEventListener('load', onload);
                resource.removeEventListener('error', onerror);
                resource.removeEventListener('abort', onabort);
            };
            resource.addEventListener('load', onload);
            resource.addEventListener('error', onerror);
            resource.addEventListener('abort', onabort);
        });
    };
    return FastAverageColor;
}());
if (typeof window !== 'undefined') {
    window.FastAverageColor = FastAverageColor;
}

// ==================================================
// 你的 CoverThemeColor 类 (已修改)
// ==================================================

class CoverThemeColor {
    constructor() {
        this.fac = null;
        this.currentProtyle = null;
        this.backgroundObserver = null; 
        this.tabObserver = null;  
        this.lastProcessedUrl = '';
        this.debounceTimer = null;

        // 直接实例化内嵌的 FastAverageColor
        if (window.FastAverageColor) {
            this.fac = new window.FastAverageColor();
            console.log('CoverThemeColor: (诊断) FastAverageColor 已内嵌并实例化。');
        } else {
            console.warn('CoverThemeColor: (诊断) 内嵌 FastAverageColor 失败。图片题头图功能将不可用。');
        }

        this.init();
    }

    init() {
        // 不再需要 loadScript
        this.findTabBar();
    }

    findTabBar() {
        const tabBar = document.querySelector('.layout__center .layout-tab-bar');

        if (tabBar) {
            console.log('CoverThemeColor (诊断): 成功找到 Tab 栏, 启动监视器。');
            this.setupTabObserver(tabBar);
    
            this.handleTabSwitch();  
        } else {
            setTimeout(() => this.findTabBar(), 200);
        }
    }

    setupTabObserver(tabBar) {
        this.tabObserver = new MutationObserver(() => {
            clearTimeout(this.debounceTimer);
            this.debounceTimer = setTimeout(() => this.handleTabSwitch(), 150);
        });

        this.tabObserver.observe(tabBar, {
            attributes: true,
            attributeFilter: ['class'], 
            subtree: true 
        });
    }

    handleTabSwitch() {
        const focusedTab = document.querySelector('.layout__center .layout-tab-bar .item--focus');
        if (!focusedTab) {
            console.log('CoverThemeColor (诊断): handleTabSwitch - 未找到 focused tab (.item--focus)');
            return;
        }

        const protyleId = focusedTab.dataset.id;
        if (!protyleId) {
            console.log('CoverThemeColor (诊断): handleTabSwitch - Focused tab 没有 data-id');
            return;
        }

        const protyleElement = document.querySelector(`.layout__center .protyle[data-id="${protyleId}"]`);
        if (!protyleElement) {
            console.log(`CoverThemeColor (诊断): handleTabSwitch - 未找到 data-id 为 ${protyleId} 的 protyle`);
            return;
        }

        if (protyleElement === this.currentProtyle) {
            return;
        }

        console.log(`CoverThemeColor (诊断): handleTabSwitch - 检测到文档切换! 新 ID: ${protyleId}`);
        this.currentProtyle = protyleElement;

        this.setupBackgroundObserver(protyleElement);
    }

    setupBackgroundObserver(protyleElement) {
        if (this.backgroundObserver) {
            this.backgroundObserver.disconnect();
            this.backgroundObserver = null;
        }

        const backgroundEl = protyleElement.querySelector('.protyle-background');

        if (backgroundEl) {
            this.updateColor();

            this.backgroundObserver = new MutationObserver(() => {
                clearTimeout(this.debounceTimer);
                this.debounceTimer = setTimeout(() => this.updateColor(), 150);
            });

            this.backgroundObserver.observe(backgroundEl, {
                childList: true,
                subtree: true,
                attributes: true,
                attributeFilter: ['src', 'style']
            });
        } else {
            this.resetColor();
        }
    }

    // loadScript 方法已被移除

    updateColor() {
        if (!this.currentProtyle) {
             console.log('CoverThemeColor (诊断): updateColor - 失败, this.currentProtyle 为空。');
            return;
        }

        const img = this.currentProtyle.querySelector('.protyle-background__img img');

        if (!img) {
            this.resetColor();
            return;
        }

        const style = img.getAttribute('style');
        if (style && style.includes('linear-gradient')) {
    
            if (style === this.lastProcessedUrl) return; 
    
            console.log('CoverThemeColor (诊断): updateColor - 找到渐变色 (linear-gradient)。');
    
            const gradientRegex = /linear-gradient\([^,]+,([^,)]+)/;
            const colorMatch = style.match(gradientRegex);
    
            if (colorMatch && colorMatch[1]) {
                const colorRegex = /(#[0-9a-fA-F]{3,6}|rgba?\([^)]+\))/;
                const finalColorMatch = colorMatch[1].match(colorRegex);
        
                if (finalColorMatch && finalColorMatch[1]) {
                    const color = finalColorMatch[1].trim();
                    console.log(`CoverThemeColor (诊断): 提取到渐变色: ${color}`);
                    this.applyColor(color); 
                    this.lastProcessedUrl = style; 
                    return; 
                }
            }
            console.log('CoverThemeColor (诊断): 找到了渐变色, 但提取颜色失败。');
        }

        if (!this.fac) {
            if (img.src && img.src.length > 200) {
                 console.log('CoverThemeColor (诊断): updateColor - 找到图片, 但 fac 未加载, 无法处理。');
            }
            this.resetColor();
            return;
        }

        if (img.src) {
    
            if (img.src === this.lastProcessedUrl) {
                return; 
            }
    
            if (img.complete) {
                this.processImage(img);
            } else {
                img.onload = () => this.processImage(img);
                img.onerror = () => {
                    console.error('CoverThemeColor: 图片加载失败。', img.src);
                    this.lastProcessedUrl = img.src;  
                };
            }
        } else {
            this.resetColor();
        }
    }

    processImage(img) {
        if (!img || !img.src || (img.src.startsWith('data:image/') && img.src.length < 200)) {
            if (this.lastProcessedUrl !== img.src) {
                this.resetColor();
            }
            this.lastProcessedUrl = img.src;
            return;
        }

        this.lastProcessedUrl = img.src;
        console.log(`CoverThemeColor (诊断): processImage - 正在处理图片: ${img.src.substring(0, 50)}...`);

        this.fac.getColorAsync(img)  
            .then(color => {
                if (color) {
                    this.applyColor(color.hex); 
                }
            })
            .catch(e => {
                console.error('CoverThemeColor: 提取颜色失败 (fac.getColorAsync 失败)。', e);
                this.resetColor();
            });
    }

    applyColor(color) {
        console.log(`CoverThemeColor (诊断): applyColor - 正在应用颜色: ${color}`);
        const rootStyle = document.documentElement.style;
        rootStyle.setProperty('--b3-theme-primary', color);

        let hexColor = color;
        if (color.startsWith('rgb')) {
            hexColor = this.rgbToHex(color);
        }

        if (!hexColor || !hexColor.startsWith('#') || hexColor.length < 4) {
             console.warn('CoverThemeColor: 无法计算变体, 颜色不是有效的Hex或RGB:', color);
             rootStyle.removeProperty('--b3-theme-primary-light');
             rootStyle.removeProperty('--b3-theme-primary-dark');
            return;
        }

        try {
            const lightColor = this.adjustColor(hexColor, 20); 
            const darkColor = this.adjustColor(hexColor, -20); 
            rootStyle.setProperty('--b3-theme-primary-light', lightColor);
            rootStyle.setProperty('--b3-theme-primary-dark', darkColor);
        } catch (e) {
            console.error('CoverThemeColor: 计算亮/暗变体失败。', e);
        }
    }

    resetColor() {
        if (this.lastProcessedUrl !== '') { 
            console.log('CoverThemeColor (诊断): resetColor - 正在重置颜色。');
        }
        this.lastProcessedUrl = ''; 
        const rootStyle = document.documentElement.style;
        rootStyle.removeProperty('--b3-theme-primary');
        rootStyle.removeProperty('--b3-theme-primary-light');
        rootStyle.removeProperty('--b3-theme-primary-dark');
    }

    adjustColor(color, percent) {
        if (color.length === 4) {
            color = `#${color[1]}${color[1]}${color[2]}${color[2]}${color[3]}${color[3]}`;
        }
  
        let r = parseInt(color.substring(1, 3), 16);
        let g = parseInt(color.substring(3, 5), 16);
        let b = parseInt(color.substring(5, 7), 16);

        const amount = Math.round(2.55 * percent);

        r = Math.max(0, Math.min(255, r + amount));
        g = Math.max(0, Math.min(255, g + amount));
        b = Math.max(0, Math.min(255, b + amount));

        const rr = r.toString(16).padStart(2, '0');
        const gg = g.toString(16).padStart(2, '0');
        const bb = b.toString(16).padStart(2, '0');

        return `#${rr}${gg}${bb}`;
    }
  
    rgbToHex(rgb) {
        try {
            const match = rgb.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
            if (!match) return '#000000'; 
            const componentToHex = (c) => {
                const hex = parseInt(c, 10).toString(16);
                return hex.length == 1 ? "0" + hex : hex;
            };
            return "#" + componentToHex(match[1]) + componentToHex(match[2]) + componentToHex(match[3]);
        } catch (e) {
            console.error('CoverThemeColor: rgbToHex 转换失败', e);
            return '#000000';
        }
    }
}

if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', () => {
        window.coverThemeColorSnippet = new CoverThemeColor();
    });
} else {
    if (document.body) {
         window.coverThemeColorSnippet = new CoverThemeColor();
    } else {
         document.addEventListener('DOMContentLoaded', () => {
            window.coverThemeColorSnippet = new CoverThemeColor();
        });
    }
}

  • 思源笔记

    思源笔记是一款隐私优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。

    融合块、大纲和双向链接,重构你的思维。

    28446 引用 • 119770 回帖
  • 代码片段

    代码片段分为 CSS 与 JS 两种代码,添加在 [设置 - 外观 - 代码片段] 中,这些代码会在思源笔记加载时自动执行,用于改善笔记的样式或功能。

    用户在该标签下分享代码片段时需在帖子标题前添加 [css] [js] 用于区分代码片段类型。

    285 引用 • 1985 回帖
1 操作
lovexmm521 在 2025-10-25 18:00:29 更新了该帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • stevehfut

    FastAverageColor.js 有办法一起集成到代码片段吗(要单独插个文件话比较麻烦)

  • stevehfut

    目前有按钮等变化,其他变化自行添加 css 容器

    具体如何操作

    2 回复
  • lovexmm521

    ok,上一个版是尊重 QYL 大佬版,现在是完整版

  • lovexmm521

    我不知道你需要那个容器换,也不能给别人瞎换容器 我只能给你个 css 容器作为参考 然后 ai 直接代码添加就行

    [data-theme-mode="light"] {
        --QYL-custom-primary-main: 292deg;
        /* 主色 */
        --b3-theme-primary: #8A5CF5;
        --b3-theme-primary-light: rgba(138, 92, 245, 0.55);
        --b3-theme-primary-lighter: rgba(138, 92, 245, 0.32);
        --b3-theme-primary-lightest: rgba(138, 92, 245, 0.15);
        /* 编辑器背景色 */
        --b3-theme-background: #f6f5fa;
        --b3-theme-background-light: #e3e1ec;
        /* 面板背景色 */
        --b3-theme-surface: #eeedf5;
        --b3-theme-surface-light: rgba(239, 237, 245, 0.8);
        --b3-theme-surface-lighter: rgba(181, 176, 211, 0.68);
        /* 文字颜色 */
        --b3-theme-on-primary: rgba(255,255,255,.9);
        --b3-theme-on-error: var(--b3-theme-on-primary);
        --b3-theme-on-secondary: var(--b3-theme-on-primary);
        --b3-theme-on-background: #11253e;
        --b3-theme-on-surface: #617691;
        --b3-theme-on-surface-light: #617691;
        /* 顶部工具栏 */
        --b3-toolbar-blur-background: #f3f6f8;
        --b3-toolbar-color: var(--b3-theme-on-surface-light);
        --b3-toolbar-hover: var(--b3-list-hover);
        /* 滚动条 */
        --b3-scroll-color: #dbd5eb;
        /* 悬停 */
        --b3-list-hover: rgba(210, 194, 249, 0.8);
        --b3-list-icon-hover: rgba(210, 194, 249, 0.8);
        /* 菜单 */
        --b3-menu-background: var(--b3-theme-background);
        /* 提示 */
        --b3-tooltips-background: var(--b3-theme-background);
        --b3-tooltips-color: var(--b3-theme-on-surface);
        --b3-tooltips-second-color: var(--b3-theme-on-surface-light);
        --b3-tooltips-shadow: var(--b3-point-shadow);
        /* 遮罩 */
        --b3-mask-background: rgba(220, 220, 220, 0.4);
        /* switch */
        --b3-switch-background: #d4dadf;
        /* 行级代码背景色 */
        --b3-protyle-code-background: var(--b3-theme-surface);
        /* 页签 */
        --QYL-tab-item: rgba(212, 221, 233, 0.3);
        --QYL-tab-item-focus: rgba(210, 194, 249, 0.8);
        /* 状态栏 */
        --QYL-status-background: var(--b3-theme-background);
        /* 毛玻璃 */
        --QYL-Aero-background: rgba(246, 245, 250, 0.7);
        --QYL-Aero-background-wrap: rgba(246, 245, 250, 0.7);
        /* 引述块 */
        --QYL-blockquote-background: #ebeaf2;
        --QYL-blockquote-svg: #c9c9d4;
        --QYL-blockquote: #7d7d86;
        /* 行级代码 */
        --QYL-inline-code: var(--b3-theme-primary);
    }
    /* 细节调整 */
    .QYLColorBlock[data-theme-mode="light"] {
        --b3-theme-surface: #e6e3f4;
    }
    
  • wilsons 1 1 赞同

    我觉得这个不错,如果有心的人做成主题,一定很酷

    image.png

    image.png

    image.png

lovexmm521
窈窕淑女,君子好逑 爱发电:https://afdian.com/a/QianQian517

推荐标签 标签

  • RabbitMQ

    RabbitMQ 是一个开源的 AMQP 实现,服务器端用 Erlang 语言编写,支持多种语言客户端,如:Python、Ruby、.NET、Java、C、PHP、ActionScript 等。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

    49 引用 • 60 回帖 • 342 关注
  • WebComponents

    Web Components 是 W3C 定义的标准,它给了前端开发者扩展浏览器标签的能力,可以方便地定制可复用组件,更好的进行模块化开发,解放了前端开发者的生产力。

    1 引用 • 18 关注
  • IDEA

    IDEA 全称 IntelliJ IDEA,是一款 Java 语言开发的集成环境,在业界被公认为最好的 Java 开发工具之一。IDEA 是 JetBrains 公司的产品,这家公司总部位于捷克共和国的首都布拉格,开发人员以严谨著称的东欧程序员为主。

    182 引用 • 400 回帖
  • Visio
    1 引用 • 2 回帖
  • Sillot

    Insights(注意当前设置 master 为默认分支)

    汐洛彖夲肜矩阵(Sillot T☳Converbenk Matrix),致力于服务智慧新彖乄,具有彖乄驱动、极致优雅、开发者友好的特点。其中汐洛绞架(Sillot-Gibbet)基于自思源笔记(siyuan-note),前身是思源笔记汐洛版(更早是思源笔记汐洛分支),是智慧新录乄终端(多端融合,移动端优先)。

    主仓库地址:Hi-Windom/Sillot

    文档地址:sillot.db.sc.cn

    注意事项:

    1. ⚠️ 汐洛仍在早期开发阶段,尚不稳定
    2. ⚠️ 汐洛并非面向普通用户设计,使用前请了解风险
    3. ⚠️ 汐洛绞架基于思源笔记,开发者尽最大努力与思源笔记保持兼容,但无法实现 100% 兼容
    29 引用 • 25 回帖 • 152 关注
  • 创业

    你比 99% 的人都优秀么?

    81 引用 • 1396 回帖 • 1 关注
  • PWA

    PWA(Progressive Web App)是 Google 在 2015 年提出、2016 年 6 月开始推广的项目。它结合了一系列现代 Web 技术,在网页应用中实现和原生应用相近的用户体验。

    14 引用 • 69 回帖 • 186 关注
  • GitLab

    GitLab 是利用 Ruby 一个开源的版本管理系统,实现一个自托管的 Git 项目仓库,可通过 Web 界面操作公开或私有项目。

    46 引用 • 72 回帖
  • Linux

    Linux 是一套免费使用和自由传播的类 Unix 操作系统,是一个基于 POSIX 和 Unix 的多用户、多任务、支持多线程和多 CPU 的操作系统。它能运行主要的 Unix 工具软件、应用程序和网络协议,并支持 32 位和 64 位硬件。Linux 继承了 Unix 以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。

    960 引用 • 946 回帖
  • 心情

    心是产生任何想法的源泉,心本体会陷入到对自己本体不能理解的状态中,因为心能产生任何想法,不能分出对错,不能分出自己。

    59 引用 • 369 回帖 • 2 关注
  • 数据库

    据说 99% 的性能瓶颈都在数据库。

    348 引用 • 765 回帖 • 1 关注
  • 持续集成

    持续集成(Continuous Integration)是一种软件开发实践,即团队开发成员经常集成他们的工作,通过每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。

    15 引用 • 7 回帖
  • WordPress

    WordPress 是一个使用 PHP 语言开发的博客平台,用户可以在支持 PHP 和 MySQL 数据库的服务器上架设自己的博客。也可以把 WordPress 当作一个内容管理系统(CMS)来使用。WordPress 是一个免费的开源项目,在 GNU 通用公共许可证(GPLv2)下授权发布。

    46 引用 • 114 回帖 • 139 关注
  • golang

    Go 语言是 Google 推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发 Go,是因为过去 10 多年间软件开发的难度令人沮丧。Go 是谷歌 2009 发布的第二款编程语言。

    502 引用 • 1397 回帖 • 240 关注
  • WiFiDog

    WiFiDog 是一套开源的无线热点认证管理工具,主要功能包括:位置相关的内容递送;用户认证和授权;集中式网络监控。

    1 引用 • 7 回帖 • 633 关注
  • H2

    H2 是一个开源的嵌入式数据库引擎,采用 Java 语言编写,不受平台的限制,同时 H2 提供了一个十分方便的 web 控制台用于操作和管理数据库内容。H2 还提供兼容模式,可以兼容一些主流的数据库,因此采用 H2 作为开发期的数据库非常方便。

    11 引用 • 54 回帖 • 691 关注
  • Rust

    Rust 是一门赋予每个人构建可靠且高效软件能力的语言。Rust 由 Mozilla 开发,最早发布于 2014 年 9 月。

    60 引用 • 22 回帖 • 2 关注
  • 强迫症

    强迫症(OCD)属于焦虑障碍的一种类型,是一组以强迫思维和强迫行为为主要临床表现的神经精神疾病,其特点为有意识的强迫和反强迫并存,一些毫无意义、甚至违背自己意愿的想法或冲动反反复复侵入患者的日常生活。

    15 引用 • 161 回帖 • 1 关注
  • ZooKeeper

    ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现,是 Hadoop 和 HBase 的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。

    61 引用 • 29 回帖 • 14 关注
  • Hadoop

    Hadoop 是由 Apache 基金会所开发的一个分布式系统基础架构。用户可以在不了解分布式底层细节的情况下,开发分布式程序。充分利用集群的威力进行高速运算和存储。

    95 引用 • 122 回帖 • 634 关注
  • V2Ray
    1 引用 • 15 回帖 • 4 关注
  • JVM

    JVM(Java Virtual Machine)Java 虚拟机是一个微型操作系统,有自己的硬件构架体系,还有相应的指令系统。能够识别 Java 独特的 .class 文件(字节码),能够将这些文件中的信息读取出来,使得 Java 程序只需要生成 Java 虚拟机上的字节码后就能在不同操作系统平台上进行运行。

    180 引用 • 120 回帖 • 1 关注
  • AWS
    11 引用 • 28 回帖 • 2 关注
  • VirtualBox

    VirtualBox 是一款开源虚拟机软件,最早由德国 Innotek 公司开发,由 Sun Microsystems 公司出品的软件,使用 Qt 编写,在 Sun 被 Oracle 收购后正式更名成 Oracle VM VirtualBox。

    10 引用 • 2 回帖 • 14 关注
  • 电影

    这是一个不能说的秘密。

    125 引用 • 610 回帖
  • CSS

    CSS(Cascading Style Sheet)“层叠样式表”是用于控制网页样式并允许将样式信息与网页内容分离的一种标记性语言。

    200 引用 • 545 回帖
  • NetBeans

    NetBeans 是一个始于 1997 年的 Xelfi 计划,本身是捷克布拉格查理大学的数学及物理学院的学生计划。此计划延伸而成立了一家公司进而发展这个商用版本的 NetBeans IDE,直到 1999 年 Sun 买下此公司。Sun 于次年(2000 年)六月将 NetBeans IDE 开源,直到现在 NetBeans 的社群依然持续增长。

    78 引用 • 102 回帖 • 724 关注