Dyanimically Adjusting Image Text for Contrast

Dyanimically Adjusting Image Text for Contrast

Yesterday I was pleasantly surprised to discover that one of my favorite JavaScript libraries, Color Thief, had gotten a major update. Color Thief examines an image and can tell you the dominant color as well as the five most used colors. I thought this was pretty cool, and over the past, I kid you not, 14 years, I've blogged about it a few times:

So yeah, it's a cool library, and as I said, I was stoked to see it get a major upgrade. The last version improves the library quite a bit, adding n TypeScript definitions but also a set of features that can directly help with creating text that contrasts well with the image. After examining an image, it has a basic isDark variable that can be checked, two contrast variables for white and black, and best of all, a simple textColor value that provides the best suggestion (and yes, this won't be perfect, I'll touch on why at the end) for what text color should be used to provide contrast.

As an example, consider this simple demo. First, I've got some HTML and CSS to render text over an image. Here's the HTML:

<div class="image-container">
	<img src="https://unsplash.it/640/425?image=40">
	<div class="overlay-text">Your Text Here</div>
</div>

And here's the CSS (I used Gemini to help write this):

.image-container {
  position: relative;
  display: inline-block; /* Keeps the container the same size as the image */
}

.image-container img {
  display: block; /* Removes unwanted whitespace at the bottom of the image */
  max-width: 100%;
  height: auto;
}

.overlay-text {
  position: absolute;
  bottom: 15px; /* Padding from the bottom */
  right: 15px;  /* Padding from the right */
  
  /* Styling */
  color: white; 
  font-family: sans-serif;
  font-weight: bold;
  text-shadow: 1px 1px 4px rgba(0,0,0,0.7); /* Helps readability on busy images */
}

You can see how it looks below. The text-shadow absolutely helps (and I wish I had known about this before), but it's still a bit washed out:

See the Pen Text on image test by Raymond Camden (@cfjedimaster) on CodePen.

To correct this, I added Color Thief. Given a pointer to an image object, all you need to do is run geColorSync. One of the values returned will be textColor and I can then update the CSS on the fly.

So, first I modified my HTML to have two images, nicely showing different levels of darkness ("Levels of Darkness" is the name of my new darkwave band - coming soon):

<div class="image-container">
	<img src="https://unsplash.it/640/425?image=80" crossorigin>
	<div class="overlay-text">Your Text Here</div>
</div>

<div class="image-container">
	<img src="https://unsplash.it/640/425?image=40" crossorigin>
	<div class="overlay-text">Your Text Here</div>
</div>

My CSS stayed the same, and as you remember, defaults to white text. Now here's the JavaScript I use to examine and update that color:

import { getColorSync } from 'https://unpkg.com/colorthief@3/dist/index.js';

/*
const img = document.querySelector('img');
const caption = document.querySelector('#caption-text');

const color = getColorSync(img);
console.log(color.hex());
console.log(color.isDark, color.textColor);
caption.style.color = color.textColor;
*/

// get the containers
let containers = document.querySelectorAll('.image-container');
containers.forEach(c => {
	let myImage = c.querySelector('img');
	let myText = c.querySelector('div.overlay-text');
	// assume loaded - may be bad
	const color = getColorSync(myImage);
	myText.style.color = color.textColor;

});

You'll note the comment where I mention I should check and see if the image is loaded, but I was feeling pretty lazy. Anyway, the result is perfect!

See the Pen Text on image test (with CT) by Raymond Camden (@cfjedimaster) on CodePen.

Neat! So - a few caveats, things to think about, etc. I could probably also have used a simple black background on the div element wrapping my text. What I have now though feels a bit less obtrusive. Also note that it's possible for a dark image to have a "light corner" where your text is. In that case, Color Thief would be thrown off and return a value that's not helpful. (I plan on filing an issue on their repo suggesting a way to perhaps examine part of an image.) Finally, this could also be useful if you are using Cloudinary to host your image. Cloudinary can dynamically add text to an image (see my blog post where I demonstrate this with the National Parks System API) and in theory, you could use this to help determine the best color.

Let me know what you think!