Cómo lograr el contraste perfecto entre texto claro y una imagen de fondo

 

 

 


Índice
  1. Aquí está el plan
  2. Paso 1: leer los colores de la imagen desde el lienzo
  • Paso 2: encuentra el píxel con menor contraste
    1. Paso 3: Prepare una fórmula de mezcla de colores para probar los niveles de opacidad de la superposición
    2. Paso 4: encuentre la opacidad de superposición que alcance nuestro objetivo de contraste
    3. Mejoras y limitaciones
    4. Aprendí algunas cosas en el camino.
    5. En resumen…
  • ¿Alguna vez te has encontrado con un sitio donde hay texto claro sobre una imagen de fondo clara? Si es así, sabrás lo difícil que es leerlo. Una forma popular de evitarlo es utilizar una superposición transparente. Pero esto nos lleva a una pregunta importante: ¿qué tan transparente debería ser esa superposición? No es que siempre estemos tratando con los mismos tamaños, pesos y colores de fuente y, por supuesto, diferentes imágenes darán como resultado diferentes contrastes.

     

    Intentar eliminar el contraste deficiente del texto en las imágenes de fondo es muy parecido a jugar Whac-a-Mole. En lugar de adivinar, podemos resolver este problema con HTML canvasy un poco de matemáticas.

    Como esto:

    Podríamos decir “¡Problema resuelto!” y simplemente finalice este artículo aquí. ¿Pero dónde está la diversión en eso? Lo que quiero mostrarte es cómo funciona esta herramienta para que tengas una nueva forma de manejar este problema tan común.

    Aquí está el plan

    Primero, seamos específicos acerca de nuestros objetivos. Hemos dicho que queremos texto legible encima de una imagen de fondo, pero ¿qué significa “legible”? Para nuestros propósitos, usaremos la definición WCAG de legibilidad de nivel AA, que dice que el texto y los colores de fondo necesitan suficiente contraste entre ellos para que un color sea 4,5 veces más claro que el otro.

    Elijamos un color de texto, una imagen de fondo y un color de superposición como punto de partida. Dadas esas entradas, queremos encontrar el nivel de opacidad de superposición que haga que el texto sea legible sin ocultar tanto la imagen que también sea difícil de ver. Para complicar un poco las cosas, usaremos una imagen con espacios tanto oscuros como claros y nos aseguraremos de que la superposición tenga eso en cuenta.

    Nuestro resultado final será un valor que podemos aplicar a la opacitypropiedad CSS de la superposición que nos brinda la cantidad correcta de transparencia que hace que el texto sea 4,5 veces más claro que el fondo.

    Para encontrar la opacidad de superposición óptima, seguiremos cuatro pasos:

    1. Pondremos la imagen en un HTML canvas, lo que nos permitirá leer los colores de cada píxel de la imagen.
    2. Encontraremos el píxel de la imagen que tenga menor contraste con el texto.
    3. A continuación, prepararemos una fórmula de mezcla de colores que podemos usar para probar diferentes niveles de opacidad sobre el color de ese píxel.
    4. Finalmente, ajustaremos la opacidad de nuestra superposición hasta que el contraste del texto alcance el objetivo de legibilidad. Y estas no serán sólo conjeturas aleatorias: usaremos técnicas de búsqueda binaria para acelerar este proceso.

    ¡Empecemos!

    Paso 1: leer los colores de la imagen desde el lienzo

    Canvas nos permite “leer” los colores contenidos en una imagen. Para hacer eso, necesitamos “dibujar” la imagen en un canvaselemento y luego usar el método de contexto del lienzo ( ctx) getImageData()para producir una lista de los colores de la imagen.

    function getImagePixelColorsUsingCanvas(image, canvas) { // The canvas's context (often abbreviated as ctx) is an object // that contains a bunch of functions to control your canvas const ctx = canvas.getContext('2d');
 // The width can be anything, so I picked 500 because it's large // enough to catch details but small enough to keep the // calculations quick. canvas.width = 500;
 // Make sure the canvas matches proportions of our image canvas.height = (image.height / image.width) * canvas.width;
 // Grab the image and canvas measurements so we can use them in the next step const sourceImageCoordinates = [0, 0, image.width, image.height]; const destinationCanvasCoordinates = [0, 0, canvas.width, canvas.height];
 // Canvas's drawImage() works by mapping our image's measurements onto // the canvas where we want to draw it ctx.drawImage( image, ...sourceImageCoordinates, ...destinationCanvasCoordinates );
 // Remember that getImageData only works for same-origin or // cross-origin-enabled images. // https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image const imagePixelColors = ctx.getImageData(...destinationCanvasCoordinates); return imagePixelColors;}

    El getImageData()método nos proporciona una lista de números que representan los colores de cada píxel. Cada píxel está representado por cuatro números: rojo, verde, azul y opacidad (también llamado “alfa”). Sabiendo esto, podemos recorrer la lista de píxeles y encontrar cualquier información que necesitemos. Esto será útil en el siguiente paso.

     

    Paso 2: encuentra el píxel con menor contraste

    Antes de hacer esto, necesitamos saber cómo calcular el contraste. Escribiremos una función llamada getContrast()que toma dos colores y escupe un número que representa el nivel de contraste entre los dos. Cuanto mayor sea el número, mejor será el contraste para la legibilidad.

    Cuando comencé a investigar colores para este proyecto, esperaba encontrar una fórmula sencilla. Resultó que había varios pasos.

    Para calcular el contraste entre dos colores, necesitamos conocer sus niveles de luminancia, que es esencialmente el brillo (Stacie Arellano profundiza en la luminancia que vale la pena comprobar).

    Gracias al W3C conocemos la fórmula para calcular el contraste mediante luminancia:

    const contrast = (lighterColorLuminance + 0.05) / (darkerColorLuminance + 0.05);

    Obtener la luminancia de un color significa que tenemos que convertir el color del valor RGB normal de 8 bits utilizado en la web (donde cada color es 0-255) a lo que se llama RGB lineal. La razón por la que necesitamos hacer esto es que el brillo no aumenta de manera uniforme a medida que cambian los colores. Necesitamos convertir nuestros colores a un formato en el que el brillo varíe uniformemente con los cambios de color. Eso nos permite calcular correctamente la luminancia. Nuevamente, el W3C es de ayuda aquí:

    const luminance = (0.2126 * getLinearRGB(r) + 0.7152 * getLinearRGB(g) + 0.0722 * getLinearRGB(b));

    ¡Pero espera hay más! Para convertir RGB de 8 bits (0 a 255) a RGB lineal, necesitamos pasar por lo que se llama RGB estándar (también llamado sRGB), que está en una escala de 0 a 1.

    Entonces el proceso va:

    8-bit RGB → standard RGB → linear RGB → luminance

    Y una vez que tengamos la luminancia de ambos colores que queremos comparar, podemos ingresar los valores de luminancia para obtener el contraste entre sus respectivos colores.

    // getContrast is the only function we need to interact with directly.// The rest of the functions are intermediate helper steps.function getContrast(color1, color2) { const color1_luminance = getLuminance(color1); const color2_luminance = getLuminance(color2); const lighterColorLuminance = Math.max(color1_luminance, color2_luminance); const darkerColorLuminance = Math.min(color1_luminance, color2_luminance); const contrast = (lighterColorLuminance + 0.05) / (darkerColorLuminance + 0.05); return contrast;}
function getLuminance({r,g,b}) { return (0.2126 * getLinearRGB(r) + 0.7152 * getLinearRGB(g) + 0.0722 * getLinearRGB(b));}function getLinearRGB(primaryColor_8bit) { // First convert from 8-bit rbg (0-255) to standard RGB (0-1) const primaryColor_sRGB = convert_8bit_RGB_to_standard_RGB(primaryColor_8bit);
 // Then convert from sRGB to linear RGB so we can use it to calculate luminance const primaryColor_RGB_linear = convert_standard_RGB_to_linear_RGB(primaryColor_sRGB); return primaryColor_RGB_linear;}function convert_8bit_RGB_to_standard_RGB(primaryColor_8bit) { return primaryColor_8bit / 255;}function convert_standard_RGB_to_linear_RGB(primaryColor_sRGB) { const primaryColor_linear = primaryColor_sRGB 0.03928 ? primaryColor_sRGB/12.92 : Math.pow((primaryColor_sRGB + 0.055) / 1.055, 2.4); return primaryColor_linear;}

    Ahora que podemos calcular el contraste, necesitaremos mirar nuestra imagen del paso anterior y recorrer cada píxel, comparando el contraste entre el color de ese píxel y el color del texto de primer plano. A medida que recorremos los píxeles de la imagen, realizaremos un seguimiento del peor contraste (más bajo) hasta el momento y, cuando lleguemos al final del bucle, sabremos cuál es el color con peor contraste de la imagen.

     

    function getWorstContrastColorInImage(textColor, imagePixelColors) { let worstContrastColorInImage; let worstContrast = Infinity; // This guarantees we won't start too low for (let i = 0; i imagePixelColors.data.length; i += 4) { let pixelColor = { r: imagePixelColors.data[i], g: imagePixelColors.data[i + 1], b: imagePixelColors.data[i + 2], }; let contrast = getContrast(textColor, pixelColor); if(contrast worstContrast) { worstContrast = contrast; worstContrastColorInImage = pixelColor; } } return worstContrastColorInImage;}

    Paso 3: Prepare una fórmula de mezcla de colores para probar los niveles de opacidad de la superposición

    Ahora que sabemos cuál es el color de peor contraste en nuestra imagen, el siguiente paso es establecer qué tan transparente debe ser la superposición y ver cómo eso cambia el contraste con el texto.

    Cuando implementé esto por primera vez, utilicé un lienzo separado para mezclar colores y leer los resultados. Sin embargo, gracias al artículo de Ana Tudor sobre transparencia, ahora sé que existe una fórmula conveniente para calcular el color resultante al mezclar un color base con una capa transparente.

    Para cada canal de color (rojo, verde y azul), aplicaríamos esta fórmula para obtener el color mezclado:

    mixedColor = baseColor + (overlayColor - baseColor) * overlayOpacity

    Entonces, en código, se vería así:

    function mixColors(baseColor, overlayColor, overlayOpacity) { const mixedColor = { r: baseColor.r + (overlayColor.r - baseColor.r) * overlayOpacity, g: baseColor.g + (overlayColor.g - baseColor.g) * overlayOpacity, b: baseColor.b + (overlayColor.b - baseColor.b) * overlayOpacity, } return mixedColor;}

    Ahora que podemos mezclar colores, podemos probar el contraste cuando se aplica el valor de opacidad de superposición.

     

    function getTextContrastWithImagePlusOverlay({textColor, overlayColor, imagePixelColor, overlayOpacity}) { const colorOfImagePixelPlusOverlay = mixColors(imagePixelColor, overlayColor, overlayOpacity); const contrast = getContrast(textColor, colorOfImagePixelPlusOverlay); return contrast;}

    ¡Con eso, tenemos todas las herramientas que necesitamos para encontrar la opacidad de superposición óptima!

    Paso 4: encuentre la opacidad de superposición que alcance nuestro objetivo de contraste

    Podemos probar la opacidad de una superposición y ver cómo eso afecta el contraste entre el texto y la imagen. Vamos a probar varios niveles de opacidad diferentes hasta que encontremos el contraste que dé en el blanco, donde el texto es 4,5 veces más claro que el fondo. Puede parecer una locura, pero no te preocupes; No vamos a adivinar al azar. Usaremos una búsqueda binaria, que es un proceso que nos permite reducir rápidamente el posible conjunto de respuestas hasta obtener un resultado preciso.

    Así es como funciona una búsqueda binaria:

    • Adivina en el medio.
    • Si la suposición es demasiado alta, eliminamos la mitad superior de las respuestas. ¿Demasiado baja? En su lugar, eliminamos la mitad inferior.
    • Adivina en medio de ese nuevo rango.
    • Repita este proceso hasta que obtengamos un valor.

    Resulta que tengo una herramienta para mostrar cómo funciona esto:

    En este caso, estamos tratando de adivinar un valor de opacidad que esté entre 0 y 1. Entonces, adivinaremos en el medio, probaremos si el contraste resultante es demasiado alto o demasiado bajo, eliminaremos la mitad de las opciones y adivinaremos nuevamente. Si limitamos la búsqueda binaria a ocho conjeturas, obtendremos una respuesta precisa en un instante.

    Antes de comenzar a buscar, necesitaremos una forma de verificar si es necesaria una superposición en primer lugar. ¡No tiene sentido optimizar una superposición que ni siquiera necesitamos!

    function isOverlayNecessary(textColor, worstContrastColorInImage, desiredContrast) { const contrastWithoutOverlay = getContrast(textColor, worstContrastColorInImage); return contrastWithoutOverlay desiredContrast;}

    Ahora podemos usar nuestra búsqueda binaria para buscar la opacidad de superposición óptima:

    function findOptimalOverlayOpacity(textColor, overlayColor, worstContrastColorInImage, desiredContrast) { // If the contrast is already fine, we don't need the overlay, // so we can skip the rest. const isOverlayNecessary = isOverlayNecessary(textColor, worstContrastColorInImage, desiredContrast); if (!isOverlayNecessary) { return 0; }
 const opacityGuessRange = { lowerBound: 0, midpoint: 0.5, upperBound: 1, }; let numberOfGuesses = 0; const maxGuesses = 8;
 // If there's no solution, the opacity guesses will approach 1, // so we can hold onto this as an upper limit to check for the no-solution case. const opacityLimit = 0.99;
 // This loop repeatedly narrows down our guesses until we get a result while (numberOfGuesses maxGuesses) { numberOfGuesses++;
 const currentGuess = opacityGuessRange.midpoint; const contrastOfGuess = getTextContrastWithImagePlusOverlay({ textColor, overlayColor, imagePixelColor: worstContrastColorInImage, overlayOpacity: currentGuess, });
 const isGuessTooLow = contrastOfGuess desiredContrast; const isGuessTooHigh = contrastOfGuess desiredContrast; if (isGuessTooLow) { opacityGuessRange.lowerBound = currentGuess; } else if (isGuessTooHigh) { opacityGuessRange.upperBound = currentGuess; }
 const newMidpoint = ((opacityGuessRange.upperBound - opacityGuessRange.lowerBound) / 2) + opacityGuessRange.lowerBound; opacityGuessRange.midpoint = newMidpoint; }
 const optimalOpacity = opacityGuessRange.midpoint; const hasNoSolution = optimalOpacity opacityLimit;
 if (hasNoSolution) { console.log('No solution'); // Handle the no-solution case however you'd like return opacityLimit; } return optimalOpacity;}

    Una vez completado nuestro experimento, ahora sabemos exactamente qué tan transparente debe ser nuestra superposición para mantener nuestro texto legible sin ocultar demasiado la imagen de fondo.

     

    ¡Lo hicimos!

    Mejoras y limitaciones

    Los métodos que hemos cubierto sólo funcionan si el color del texto y el color de superposición tienen suficiente contraste para empezar. Por ejemplo, si tuviera que elegir un color de texto que sea el mismo que el de su superposición, no habrá una solución óptima a menos que la imagen no necesite ninguna superposición.

    Además, incluso si el contraste es matemáticamente aceptable, eso no siempre garantiza que se verá genial. Esto es especialmente cierto para texto oscuro con una superposición clara y una imagen de fondo ocupada. Varias partes de la imagen pueden distraer la atención del texto, dificultando la lectura incluso cuando el contraste numérico es fino. Por eso la recomendación popular es utilizar texto claro sobre fondo oscuro.

    Tampoco hemos tenido en cuenta dónde están situados los píxeles ni cuántos hay de cada color. Un inconveniente de esto es que un píxel en la esquina podría ejercer demasiada influencia en el resultado. El beneficio, sin embargo, es que no tenemos que preocuparnos por cómo se distribuyen los colores de la imagen o dónde está el texto porque, siempre que hayamos manejado dónde está la menor cantidad de contraste, estaremos seguros en cualquier otro lugar.

    Aprendí algunas cosas en el camino.

    Hay algunas cosas con las que me quedé después de este experimento y me gustaría compartirlas con ustedes:

    • ¡Ser específico acerca de un objetivo realmente ayuda! Comenzamos con el objetivo vago de querer texto legible en una imagen y terminamos con un nivel de contraste específico al que podíamos aspirar.
    • Es muy importante tener claros los términos. Por ejemplo, el RGB estándar no era lo que esperaba. Aprendí que lo que consideraba RGB “normal” (0 a 255) se llama formalmente RGB de 8 bits. Además, pensé que la "L" en las ecuaciones que investigué significaba "luminosidad", pero en realidad significa "luminancia", que no debe confundirse con "luminosidad". Aclarar los términos ayuda a la forma en que codificamos y a la forma en que discutimos el resultado final.
    • Complejo no significa irresoluble. Los problemas que parecen difíciles se pueden dividir en partes más pequeñas y manejables.
    • Cuando recorres el camino, ves los atajos. Para el caso común de texto blanco sobre una superposición negra transparente, nunca necesitará una opacidad superior a 0,54 para lograr una legibilidad de nivel WCAG AA.

    En resumen…

    Ahora tienes una manera de hacer que tu texto sea legible en una imagen de fondo sin sacrificar demasiado la imagen. Si has llegado hasta aquí, espero haberte podido dar una idea general de cómo funciona todo.

    Originalmente comencé este proyecto porque vi (e hice) demasiados banners de sitios web donde el texto era difícil de leer sobre una imagen de fondo o la imagen de fondo estaba demasiado oscurecida por la superposición. Quería hacer algo al respecto y quería darles a otros una manera de hacer lo mismo. Escribí este artículo con la esperanza de que comprendas mejor la legibilidad en la web. Espero que también hayas aprendido algunos trucos ingeniosos sobre el lienzo.

    Si has hecho algo interesante con legibilidad o lienzo, ¡me encantaría saberlo en los comentarios!






    SUSCRÍBETE A NUESTRO BOLETÍN 

    No te pierdas de nuestro contenido ni de ninguna de nuestras guías para que puedas avanzar en los juegos que más te gustan.










    Al suscribirte, aceptas nuestra política de privacidad y nuestros términos de servicio.






    Tal vez te puede interesar:

    1. La innovación no puede mantener la Web rápida
    2. Rendimiento web ultrarrápido
    3. Tabla de contenidos fijos con estados activos de desplazamiento
    4. “cambiar tamaño: ninguno;” en áreas de texto es una mala experiencia de usuario

    Cómo lograr el contraste perfecto entre texto claro y una imagen de fondo

    Cómo lograr el contraste perfecto entre texto claro y una imagen de fondo

    Aquí está el planPaso 1: leer los colores de la imagen desde el lienzoPaso 3: Prepare una fórmula de mezcla de colores para probar los niveles de opacidad d

    programar

    es

    https://pseint.es/static/images/programar-como-lograr-el-contraste-perfecto-entre-texto-claro-y-una-imagen-de-fondo-1673-0.jpg

    2024-06-13

     

    Cómo lograr el contraste perfecto entre texto claro y una imagen de fondo
    Cómo lograr el contraste perfecto entre texto claro y una imagen de fondo

    Si crees que alguno de los contenidos (texto, imagenes o multimedia) en esta página infringe tus derechos relativos a propiedad intelectual, marcas registradas o cualquier otro de tus derechos, por favor ponte en contacto con nosotros en el mail [email protected] y retiraremos este contenido inmediatamente

     

     

    Update cookies preferences