Modificare il colore del testo in base alla luminosità dell'area di sfondo coperta?


114

Ho pensato a quanto segue già da un po ', quindi ora voglio conoscere le tue opinioni, le possibili soluzioni e così via.

Sto cercando un plug-in o una tecnica che cambi il colore di un testo o passi da immagini / icone predefinite a seconda della luminosità media dei pixel coperti dell'immagine di sfondo o del colore del genitore.

Se l'area coperta dello sfondo è piuttosto scura, rendi il testo bianco o cambia le icone.

Inoltre, sarebbe fantastico se lo script notasse se il genitore non ha un colore di sfondo o un'immagine definiti e poi continuasse a cercare il più vicino (dall'elemento genitore al suo elemento genitore ..).

Cosa ne pensi di sapere di questa idea? C'è già qualcosa di simile là fuori? script-esempi?

Saluti, J.


1
Solo un pensiero piuttosto che una risposta. Potrebbe esserci un modo per impostare i colori utilizzando HSL, quindi osservando il valore di luminosità. Se quel valore è superiore a un certo valore, applica una regola CSS.
Scott Brown

1
si potrebbe plausibilmente analizzare il colore di sfondo di un elemento in valori R, G, B (e alfa opzionale), lavorando sull'albero DOM se il canale alfa è impostato su zero. Tuttavia, cercare di determinare il colore di un'immagine di sfondo è tutta un'altra cosa.
jackwanders


@Pascal Abbastanza simile, e un buon input .. ma non è la risposta esatta alla mia domanda.
James Cazzetta

Risposte:


175

Risorse interessanti per questo:

Ecco l'algoritmo W3C (anche con la demo di JSFiddle ):

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>


Felix hai ragione! Ora sono via, ma certo, quando tornerò aggiornerò la risposta
Alex Ball

1
Può essere abbreviato come segue, a condizione di passargli un oggetto :::: const setContrast = rgb => (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000> 125? 'black': 'white'
Luke Robertson il

hai solo bisogno di jquery solo per cambiare il css?
bluejayke

@bluejayke no, ci sono altri modi ;-)
Alex Ball

63

Questo articolo su 24 modi per calcolare il contrasto del colore potrebbe interessarti. Ignora la prima serie di funzioni perché sono sbagliate, ma la formula YIQ ti aiuterà a determinare se utilizzare o meno un colore di primo piano chiaro o scuro.

Una volta ottenuto il colore di sfondo dell'elemento (o dell'antenato), puoi usare questa funzione dell'articolo per determinare un colore di primo piano adatto:

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';
}

Grazie, è davvero utile .. Dipende dal colore di sfondo impostato .. Ma sai come ottenere il colore medio di un'immagine scorrendo ogni pixel (come in un loop)?
James Cazzetta

4
In es6 puoi farlo 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

Ho preso questa funzione e l'ho ampliata un po 'in modo da poter restituire due colori personalizzati, anziché sempre bianco e nero. Nota che se i colori sono due vicini potresti ancora avere problemi di contrasto, ma questa è una buona alternativa alla restituzione di colori assoluti jsfiddle.net/1905occv/1
Hanna

2
questo è coo, regolerei semplicemente yiq su> = 160, ha funzionato meglio per me.
Arturo

15

Domanda interessante. Il mio pensiero immediato è stato quello di invertire il colore dello sfondo come il testo. Ciò comporta semplicemente l'analisi dello sfondo e l'inversione del suo valore RGB.

Qualcosa del genere: 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+')');

Probabilmente vorresti desaturare il tuo colore "invertito" calcolando la media dei valori R, G, B invertiti e impostandoli uguali tra loro. Tuttavia, questa soluzione ottiene il suo colore di base da una stringa e non dalla proprietà CSS dell'elemento. Per essere affidabile, la soluzione dovrebbe ottenere dinamicamente i colori di sfondo, che di solito restituiscono valori rgb () o rgba (), ma potrebbero differire a seconda del browser.
jackwanders

Sì. Per facilità di analisi, ho appena usato un valore esadecimale. Ho aggiornato il violino per includere l'acquisizione del colore dell'elemento dal CSS. Ho aggiornato il violino e ho incluso una sorta di controllo della luminosità (non so nulla di matematica del colore quindi probabilmente non è veramente luminosità).
jeremyharris


2
E se il colore di sfondo fosse #808080!?
Nathan MacInnes

1
@NathanMacInnes lo invertirà ancora, così accade che invertire qualcosa proprio nel mezzo dello spettro risulterà da solo. Questo codice inverte semplicemente il colore, che viene fornito con i suoi limiti.
jeremyharris

9

mix-blend-mode fa il trucco:

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>

Aggiunta (marzo 2018): Di seguito, un bel tutorial che spiega tutti i diversi tipi di modalità / implementazioni: https://css-tricks.com/css-techniques-and-effects-for-knockout-text/


5

Ho trovato lo script BackgroundCheck molto utile.

Rileva la luminosità complessiva dello sfondo (che si tratti di un'immagine di sfondo o di un colore) e applica una classe all'elemento di testo assegnato ( background--lighto background--dark), a seconda della luminosità dello sfondo.

Può essere applicato a elementi fermi e in movimento.

( Fonte )


Grazie per il contributo!
James Cazzetta

Funziona per i colori di sfondo? Ho letto velocemente la sceneggiatura e non riesco a vederla utilizzare il colore di sfondo per verificare la luminosità. Solo immagini.
Jørgen Skår Fischer

1
Ciao Jørgen, penso che lo script colourBrightness possa servire al tuo scopo: github.com/jamiebrittain/colourBrightness.js
cptstarling

5

Se stai usando ES6, converti esadecimale in RGB, quindi puoi usare questo:

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))

4

Ecco il mio tentativo:

(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));

Quindi per usarlo:

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

Questo renderà immediatamente il testo bianco o nero, a seconda dei casi. Per fare le icone:

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

Quindi ogni icona potrebbe assomigliare 'save' + iconSuffix + '.jpg'.

Tieni presente che questo non funzionerà quando un contenitore supera il suo genitore (ad esempio, se l'altezza CSS è 0 e l'overflow non è nascosto). Fare in modo che funzioni sarebbe molto più complesso.


1

Combinando le risposte [@ alex-ball, @jeremyharris] ho scoperto che questo è il modo migliore per me:

        $('.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>

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.