¿Cambiar el color del texto según el brillo del área de fondo cubierta?

114

Ya he pensado en lo siguiente por un tiempo, así que ahora quiero conocer vuestras opiniones, posibles soluciones, etc.

Estoy buscando un complemento o técnica que cambie el color de un texto o cambie entre imágenes / iconos predefinidos según el brillo promedio de los píxeles cubiertos de la imagen o color de fondo de los padres.

Si el área cubierta de su fondo es bastante oscura, haga que el texto sea blanco o cambie los iconos.

Además, sería genial si el script notase si el padre no tiene un color de fondo o una imagen definidos y luego continúe buscando el más cercano (desde el elemento principal hasta su elemento principal ..).

¿Qué piensas, sabes de esta idea? ¿Hay algo similar por ahí? ejemplos de guiones?

Saludos, J.

James Cazzetta
fuente
1
Solo un pensamiento más que una respuesta. Puede haber una forma de configurar sus colores usando HSL y luego mirando el valor de luminosidad. Si ese valor está por encima de cierto valor, aplique una regla CSS.
Scott Brown
1
posiblemente podría analizar el color de fondo de un elemento en valores R, G, B (y alfa opcional), trabajando en el árbol DOM si el canal alfa se establece en cero. Sin embargo, tratar de determinar el color de una imagen de fondo es otra cuestión.
jackwanders
@Pascal Bastante similar y buena entrada ... pero no es la respuesta exacta a mi pregunta.
James Cazzetta

Respuestas:

175

Recursos interesantes para esto:

Aquí está el algoritmo W3C (con la demostración de JSFiddle también ):

const rgb = [255, 0, 0];

// Randomly change to showcase updates
setInterval(setContrast, 1000);

function setContrast() {
  // Randomly update colours
  rgb[0] = Math.round(Math.random() * 255);
  rgb[1] = Math.round(Math.random() * 255);
  rgb[2] = Math.round(Math.random() * 255);

  // http://www.w3.org/TR/AERT#color-contrast
  const brightness = Math.round(((parseInt(rgb[0]) * 299) +
                      (parseInt(rgb[1]) * 587) +
                      (parseInt(rgb[2]) * 114)) / 1000);
  const textColour = (brightness > 125) ? 'black' : 'white';
  const backgroundColour = 'rgb(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ')';
  $('#bg').css('color', textColour); 
  $('#bg').css('background-color', backgroundColour);
}
#bg {
  width: 200px;
  height: 50px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div id="bg">Text Example</div>

Alex Ball
fuente
Felix tienes razón! Estoy lejos ahora, pero seguro, cuando regrese actualizaré la respuesta
Alex Ball
1
Se puede acortar a lo siguiente, siempre que le pase un objeto :::: const setContrast = rgb => (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000> 125? 'negro': 'blanco'
Luke Robertson
¿Necesita jquery solo para cambiar el CSS?
bluejayke
@bluejayke no, hay otras formas ;-)
Alex Ball
63

Este artículo sobre 24 formas de calcular el contraste de color puede ser de su interés. Ignore el primer conjunto de funciones porque son incorrectas, pero la fórmula YIQ le ayudará a determinar si debe utilizar un color de primer plano claro u oscuro.

Una vez que obtenga el color de fondo del elemento (o del antepasado), puede usar esta función del artículo para determinar un color de primer plano adecuado:

function getContrastYIQ(hexcolor){
    hexcolor = hexcolor.replace("#", "");
    var r = parseInt(hexcolor.substr(0,2),16);
    var g = parseInt(hexcolor.substr(2,2),16);
    var b = parseInt(hexcolor.substr(4,2),16);
    var yiq = ((r*299)+(g*587)+(b*114))/1000;
    return (yiq >= 128) ? 'black' : 'white';
}
Cyang
fuente
Gracias, esto es realmente útil. Esto depende del color de fondo establecido. Pero, ¿sabe cómo obtener el color promedio de una imagen pasando por cada píxel (como en un bucle)?
James Cazzetta
4
En es6 puedes hacer esto con:const getContrastYIQ = hc => { const [r, g, b] = [0, 2, 4].map( p => parseInt( hc.substr( p, 2 ), 16 ) ); return ((r * 299) + (g * 587) + (b * 114)) / 1000 >= 128; }
Centril
Tomé esta función y la expandí un poco para que pudiera devolver dos colores personalizados, en lugar de siempre blanco y negro. Tenga en cuenta que si los colores están muy juntos, es posible que aún tenga problemas de contraste, pero esta es una buena alternativa para devolver colores absolutos. Jsfiddle.net/1905occv/1
Hanna
2
este es genial, simplemente ajustaría el yiq a> = 160, funcionó mejor para mí.
Arturo
15

Interesante pregunta. Mi pensamiento inmediato fue invertir el color del fondo como texto. Esto implica simplemente analizar el fondo e invertir su valor RGB.

Algo como esto: http://jsfiddle.net/2VTnZ/2/

var rgb = $('#test').css('backgroundColor');
var colors = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
var brightness = 1;

var r = colors[1];
var g = colors[2];
var b = colors[3];

var ir = Math.floor((255-r)*brightness);
var ig = Math.floor((255-g)*brightness);
var ib = Math.floor((255-b)*brightness);

$('#test').css('color', 'rgb('+ir+','+ig+','+ib+')');
Jeremyharris
fuente
Probablemente desee desaturar su color 'invertido' promediando los valores R, G, B invertidos y estableciéndolos iguales entre sí. Sin embargo, esta solución obtiene su color base de una cadena y no de la propiedad CSS del elemento. Para ser confiable, la solución tendría que obtener dinámicamente colores de fondo, que generalmente devuelven valores rgb () o rgba (), pero podrían diferir según el navegador.
jackwanders
Si. Para facilitar el análisis, utilicé un valor hexadecimal. Actualicé el violín para incluir la toma del color del elemento del CSS. Actualicé el violín e incluí una especie de control de brillo (no sé nada sobre matemáticas de color, por lo que probablemente no sea realmente brillo).
jeremyharris
1
¿Qué tal esto? stackoverflow.com/questions/2541481/…
jeremyharris
2
¿Qué pasa si el color de fondo es #808080?
Nathan MacInnes
1
@NathanMacInnes todavía lo invertirá, da la casualidad de que invertir algo justo en el medio del espectro resultará en sí mismo. Este código simplemente invierte el color, lo que tiene sus limitaciones.
jeremyharris
9

mix-blend-mode Hace el truco:

header {
  overflow: hidden;
  height: 100vh;
  background: url(https://www.w3schools.com/html/pic_mountain.jpg) 50%/cover;
}

h2 {
  color: white;
  font: 900 35vmin/50vh arial;
  text-align: center;
  mix-blend-mode: difference;
  filter: drop-shadow(0.05em 0.05em orange);
}
<header>
  <h2 contentEditable role='textbox' aria-multiline='true' >Edit me here</h2>
</header>

Adición (marzo de 2018): A continuación, un buen tutorial que explica todos los diferentes tipos de modos / implementaciones: https://css-tricks.com/css-techniques-and-effects-for-knockout-text/

James Cazzetta
fuente
5

He encontrado que el script BackgroundCheck es muy útil.

Detecta el brillo general del fondo (ya sea una imagen de fondo o un color) y aplica una clase al elemento de texto asignado ( background--lighto background--dark), dependiendo del brillo del fondo.

Se puede aplicar a elementos fijos y móviles.

( Fuente )

cptstarling
fuente
¡Gracias por la contribución!
James Cazzetta
¿Funciona esto para colores de fondo? He leído rápidamente el guión y no veo que utilice el color de fondo para comprobar el brillo. Solo imagenes.
Jørgen Skår Fischer
1
Hola Jørgen, creo que el guión colourBrightness puede servirte para tu propósito: github.com/jamiebrittain/colourBrightness.js
cptstarling
5

Si está utilizando ES6, convierta hexadecimal a RGB y luego puede usar esto:

const hexToRgb = hex => {
    // turn hex val to RGB
    const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
    return result
        ? {
              r: parseInt(result[1], 16),
              g: parseInt(result[2], 16),
              b: parseInt(result[3], 16)
          }
        : null
}

// calc to work out if it will match on black or white better
const setContrast = rgb =>
    (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000 > 125 ? 'black' : 'white'

const getCorrectColor = setContrast(hexToRgb(#ffffff))
Luke Robertson
fuente
4

Aquí está mi intento:

(function ($) {
    $.fn.contrastingText = function () {
        var el = this,
            transparent;
        transparent = function (c) {
            var m = c.match(/[0-9]+/g);
            if (m !== null) {
                return !!m[3];
            }
            else return false;
        };
        while (transparent(el.css('background-color'))) {
            el = el.parent();
        }
        var parts = el.css('background-color').match(/[0-9]+/g);
        this.lightBackground = !!Math.round(
            (
                parseInt(parts[0], 10) + // red
                parseInt(parts[1], 10) + // green
                parseInt(parts[2], 10) // blue
            ) / 765 // 255 * 3, so that we avg, then normalize to 1
        );
        if (this.lightBackground) {
            this.css('color', 'black');
        } else {
            this.css('color', 'white');
        }
        return this;
    };
}(jQuery));

Entonces para usarlo:

var t = $('#my-el');
t.contrastingText();

Esto inmediatamente hará que el texto sea blanco o negro, según corresponda. Para hacer los iconos:

if (t.lightBackground) {
    iconSuffix = 'black';
} else {
    iconSuffix = 'white';
}

Entonces cada icono podría verse así 'save' + iconSuffix + '.jpg'.

Tenga en cuenta que esto no funcionará donde un contenedor desborde a su padre (por ejemplo, si la altura de CSS es 0 y el desbordamiento no está oculto). Hacer que eso funcione sería mucho más complejo.

Nathan MacInnes
fuente
1

Al combinar las respuestas [@ alex-ball, @jeremyharris] encontré que esta es la mejor manera para mí:

        $('.elzahaby-bg').each(function () {
            var rgb = $(this).css('backgroundColor');
            var colors = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);

            var r = colors[1];
            var g = colors[2];
            var b = colors[3];

            var o = Math.round(((parseInt(r) * 299) + (parseInt(g) * 587) + (parseInt(b) * 114)) /1000);

            if(o > 125) {
                $(this).css('color', 'black');
            }else{
                $(this).css('color', 'white');
            }
        });
*{
    padding: 9px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.0/jquery.min.js"></script>
<div class='elzahaby-bg' style='background-color:#000'>color is white</div>

<div class='elzahaby-bg' style='background-color:#fff'>color is black</div>
<div class='elzahaby-bg' style='background-color:yellow'>color is black</div>
<div class='elzahaby-bg' style='background-color:red'>color is white</div>

A. El-zahaby
fuente