Files
wordpress-theme-aksrt/assets/js/color-extract.js
T

148 lines
3.9 KiB
JavaScript

/**
* AKSRT Color Extract
*
* Extracts dominant colors from images and applies them as dynamic accents.
* Uses canvas-based color sampling — no external dependencies.
*
* Usage:
* <img data-aksrt-color-extract data-aksrt-color-target=".card">
* <!-- Will set --aksrt-dynamic-color on .card based on image colors -->
*
* @package AKSRT
*/
(function () {
'use strict';
var canvas = null;
var ctx = null;
function getCanvas() {
if (!canvas) {
canvas = document.createElement('canvas');
canvas.width = 40;
canvas.height = 40;
ctx = canvas.getContext('2d', { willReadFrequently: true });
}
return { canvas: canvas, ctx: ctx };
}
function extractColor(img) {
try {
var c = getCanvas();
c.ctx.drawImage(img, 0, 0, 40, 40);
var data = c.ctx.getImageData(0, 0, 40, 40).data;
var r = 0, g = 0, b = 0, count = 0;
var step = 4; // sample every 4th pixel for speed
for (var i = 0; i < data.length; i += 4 * step) {
var pr = data[i], pg = data[i + 1], pb = data[i + 2];
// Skip very dark or very light pixels
var brightness = (pr + pg + pb) / 3;
if (brightness < 20 || brightness > 240) continue;
// Skip near-transparent
if (data[i + 3] < 128) continue;
r += pr;
g += pg;
b += pb;
count++;
}
if (count === 0) return null;
r = Math.round(r / count);
g = Math.round(g / count);
b = Math.round(b / count);
return { r: r, g: g, b: b, hex: rgbToHex(r, g, b) };
} catch (e) {
// CORS or tainted canvas — silently fail
return null;
}
}
function rgbToHex(r, g, b) {
return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
function lighten(r, g, b, amount) {
return rgbToHex(
Math.min(255, r + amount),
Math.min(255, g + amount),
Math.min(255, b + amount)
);
}
function darken(r, g, b, amount) {
return rgbToHex(
Math.max(0, r - amount),
Math.max(0, g - amount),
Math.max(0, b - amount)
);
}
function applyColor(target, color) {
if (!target || !color) return;
var r = color.r, g = color.g, b = color.b;
target.style.setProperty('--aksrt-dynamic-color', color.hex);
target.style.setProperty('--aksrt-dynamic-color-rgb', r + ',' + g + ',' + b);
target.style.setProperty('--aksrt-dynamic-color-light', lighten(r, g, b, 60));
target.style.setProperty('--aksrt-dynamic-color-dark', darken(r, g, b, 40));
// Add a subtle tinted background if target doesn't already have one
if (!target.style.background && !target.style.backgroundColor) {
target.style.setProperty('--aksrt-dynamic-bg', 'rgba(' + r + ',' + g + ',' + b + ',0.05)');
}
}
function processImage(img) {
if (img.dataset.aksrtColorDone) return;
// Need crossOrigin for canvas access
if (!img.crossOrigin && img.src.indexOf(window.location.origin) !== 0) {
img.crossOrigin = 'anonymous';
}
function extract() {
var color = extractColor(img);
if (!color) return;
img.dataset.aksrtColorDone = '1';
var targetSelector = img.dataset.aksrtColorTarget;
var target = targetSelector
? img.closest(targetSelector) || document.querySelector(targetSelector)
: img.parentElement;
applyColor(target, color);
}
if (img.complete && img.naturalWidth > 0) {
extract();
} else {
img.addEventListener('load', extract, { once: true });
}
}
function init() {
document.querySelectorAll('[data-aksrt-color-extract]').forEach(processImage);
}
// Public API
window.AksrtColorExtract = {
extract: extractColor,
apply: applyColor,
process: processImage,
init: init
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();