I am trying to have the menu change between black & white depending on the colour underneath it. I have tried mix-blend-mode but it creates certain scenarios where the text just becomes illegible.
I have managed to get it to analyse the background and output a colour but it seems to just read itself when making the decision. I've tried to get around it (run the analysis on a :before that sits away from the text but I don't think it's working either...
The issue is: It recognises the background but it won't change the text colour to contrast against the background. Dark background = white text., light background = black text I've tried a few different ways but they would either flicker the colour or just not change the colour at all..
function getContrastRatio(color1, color2) {
// Convert hex colors to RGB if necessary
color1 = (color1.charAt(0) === '#') ? color1.substr(1) : color1;
color2 = (color2.charAt(0) === '#') ? color2.substr(1) : color2;
// Extract RGB values
var r1 = parseInt(color1.substr(0, 2), 16);
var g1 = parseInt(color1.substr(2, 2), 16);
var b1 = parseInt(color1.substr(4, 2), 16);
var r2 = parseInt(color2.substr(0, 2), 16);
var g2 = parseInt(color2.substr(2, 2), 16);
var b2 = parseInt(color2.substr(4, 2), 16);
// Calculate relative luminance
var lum1 = (Math.max(r1, g1, b1) + Math.min(r1, g1, b1)) / 2;
var lum2 = (Math.max(r2, g2, b2) + Math.min(r2, g2, b2)) / 2;
// Calculate contrast ratio
var contrastRatio = (lum1 + 0.05) / (lum2 + 0.05);
return contrastRatio;
}
function readBackgroundColor() {
var menu = document.querySelector('.sticky-menu');
var contentTop = menu.offsetTop + menu.offsetHeight;
// Use the body as the default content element
var content = document.body;
// Iterate over all elements with class "colour" to find the one under the menu
var colours = document.querySelectorAll('.colour');
for (var i = 0; i < colours.length; i++) {
var rect = colours[i].getBoundingClientRect();
if (rect.top >= contentTop && colours[i] !== menu) { // Exclude the menu from consideration
break;
}
content = colours[i];
}
// Check if the content element is a child of the menu
if (!menu.contains(content)) {
var computedStyle = window.getComputedStyle(content);
var backgroundColor = computedStyle.backgroundColor;
// Calculate contrast ratio for black and white text
var blackContrast = getContrastRatio(backgroundColor, 'black');
var whiteContrast = getContrastRatio(backgroundColor, 'white');
// Choose the color with better contrast
var textColor = blackContrast > whiteContrast ? 'black' : 'white';
menu.style.color = textColor;
console.log("Background:", backgroundColor, "Text:", textColor);
}
}
// Event listener for scroll
window.addEventListener('scroll', readBackgroundColor);
// Initial call to read background color
readBackgroundColor();
body, html {padding:0;margin:0;}
.colour {height:250px;width:100vw;}
.black {background:black;}
.blue {background:blue;}
.red {background:red;}
.sticky-menu {
position: fixed;
top: 0;
padding: 10px;
line-height:0;
font-size:50px;
}
<div class="sticky-menu">home</div>
<div class="colour black"></div>
<div class="colour white"></div>
<div class="colour blue"></div>
<div class="colour red"></div>
<div class="colour white"></div>
First issue was the parameters to the contrast ratio function. It seems that
getComputedStylereturns the background in argb(25, 25, 25)format. Another issue was that.whiteclass wasn't defined asbackground: whiteso it didn't return the desired computed background ofrgb(255, 255, 255). And lastly, I tookcontrastfunction from this answerOne last issue, this example provided as-is you can certainly shorten it.
EDIT: The exact format of
getComputedStylebackground is not well defined. See this answer. This means further testing is needed to ensure format is indeed as expected, or handle all cases.