Changer la couleur du texte en fonction de la luminosité de la zone d'arrière-plan couverte?

114

J'ai réfléchi à ce qui suit depuis un moment déjà, alors maintenant je veux connaître vos opinions, les solutions possibles, etc.

Je recherche un plugin ou une technique qui change la couleur d'un texte ou bascule entre des images / icônes prédéfinies en fonction de la luminosité moyenne des pixels couverts de l'image d'arrière-plan ou de la couleur de son parent.

Si la zone couverte de son arrière-plan est plutôt sombre, rendez le texte blanc ou changez les icônes.

De plus, ce serait formidable si le script remarquait si le parent n'a pas de couleur d'arrière-plan ou d'image définie et continue à rechercher le plus proche (de l'élément parent à son élément parent ..).

Que pensez-vous, que savez-vous de cette idée? Y a-t-il déjà quelque chose de similaire? exemples de script?

Bravo, J.

James Cazzetta
la source
1
Juste une pensée plutôt qu'une réponse. Il peut y avoir un moyen de définir vos couleurs en utilisant HSL puis en regardant la valeur de luminosité. Si cette valeur est supérieure à une certaine valeur, appliquez une règle css.
Scott Brown
1
vous pouvez éventuellement analyser la couleur d'arrière-plan d'un élément en valeurs R, V, B (et alpha facultatif), en remontant l'arborescence DOM si le canal alpha est défini sur zéro. Cependant, essayer de déterminer la couleur d'une image d'arrière-plan est une tout autre affaire.
jackwanders
@Pascal Assez similaire, et bonne entrée .. mais ce n'est pas la réponse exacte à ma question.
James Cazzetta

Réponses:

175

Ressources intéressantes pour cela:

Voici l'algorithme du W3C (avec la démo JSFiddle aussi ):

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
la source
Félix tu as raison! Je suis absent maintenant, mais bien sûr, quand je reviendrai, je mettrai à jour la réponse
Alex Ball
1
Peut être raccourci à ce qui suit, à condition que vous lui passiez un objet :::: const setContrast = rgb => (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000> 125? 'black': 'white'
Luke Robertson
avez-vous besoin de jquery juste pour changer le css?
bluejayke
@bluejayke non, il y a d'autres moyens ;-)
Alex Ball
63

Cet article sur 24 façons de calculer le contraste des couleurs pourrait vous intéresser. Ignorez le premier ensemble de fonctions car elles sont incorrectes, mais la formule YIQ vous aidera à déterminer s'il faut ou non utiliser une couleur de premier plan claire ou foncée.

Une fois que vous avez obtenu la couleur d'arrière-plan de l'élément (ou de l'ancêtre), vous pouvez utiliser cette fonction de l'article pour déterminer une couleur de premier plan appropriée:

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
la source
Merci, c'est vraiment utile .. Cela dépend de la couleur de fond définie .. Mais savez-vous comment obtenir la couleur moyenne d'une image en parcourant chaque pixel (comme dans une boucle)?
James Cazzetta
4
Dans es6, vous pouvez le faire avec: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
J'ai pris cette fonction et l'ai élargie un peu pour que vous puissiez renvoyer deux couleurs personnalisées, plutôt que toujours le noir et blanc. Notez que si les couleurs sont proches l'une de l'autre, vous pouvez toujours avoir des problèmes de contraste, mais c'est une bonne alternative au retour des couleurs absolues jsfiddle.net/1905occv/1
Hanna
2
celui-ci est coo, je voudrais simplement ajuster le yiq à> = 160, cela a mieux fonctionné pour moi.
Arturo
15

Question interessante. Ma pensée immédiate a été d'inverser la couleur de l'arrière-plan en tant que texte. Cela implique simplement d'analyser l'arrière-plan et d'inverser sa valeur RVB.

Quelque chose comme ça: 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
la source
Vous voudrez probablement désaturer votre couleur `` inversée '' en faisant la moyenne des valeurs R, V, B inversées et en les définissant comme égales les unes aux autres. Cependant, cette solution obtient sa couleur de base à partir d'une chaîne, et non de la propriété CSS de l'élément. Pour être fiable, la solution devrait obtenir dynamiquement des couleurs d'arrière-plan, qui renvoient généralement des valeurs rgb () ou rgba (), mais peuvent différer selon le navigateur.
jackwanders
Oui. Pour faciliter l'analyse, j'ai juste utilisé une valeur hexadécimale. J'ai mis à jour le violon pour inclure la saisie de la couleur de l'élément à partir du CSS. J'ai mis à jour le violon et inclus une sorte de contrôle de la luminosité (je ne sais rien sur les mathématiques des couleurs, donc ce n'est probablement pas vraiment la luminosité).
jeremyharris
1
Que dis-tu de ça? stackoverflow.com/questions/2541481/…
jeremyharris
2
Et si la couleur d'arrière-plan est #808080!?
Nathan MacInnes
1
@NathanMacInnes ça va toujours l'inverser, il se trouve qu'inverser quelque chose en plein milieu du spectre se traduira par lui-même. Ce code inverse simplement la couleur, ce qui a ses limites.
jeremyharris
9

mix-blend-mode fait l'affaire:

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>

Ajout (mars 2018): Suite, un joli tutoriel expliquant tous les différents types de modes / implémentations: https://css-tricks.com/css-techniques-and-effects-for-knockout-text/

James Cazzetta
la source
5

J'ai trouvé le script BackgroundCheck très utile.

Il détecte la luminosité globale de l'arrière-plan (que ce soit une image d'arrière-plan ou une couleur) et applique une classe à l'élément de texte attribué ( background--lightou background--dark), en fonction de la luminosité de l'arrière-plan.

Il peut être appliqué aux éléments fixes et mobiles.

( Source )

cptstarling
la source
Merci pour la contribution!
James Cazzetta
Cela fonctionne-t-il pour les couleurs d'arrière-plan? J'ai lu rapidement le script et je ne peux pas le voir utiliser la couleur d'arrière-plan pour vérifier la luminosité. Seulement des images.
Jørgen Skår Fischer
1
Bonjour Jørgen, je pense que le script colourBrightness peut servir votre objectif: github.com/jamiebrittain/colourBrightness.js
cptstarling
5

Si vous utilisez ES6, convertissez hexadécimal en RVB puis pouvez utiliser ceci:

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
la source
4

Voici ma tentative:

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

Alors pour l'utiliser:

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

Cela rendra immédiatement le texte noir ou blanc selon le cas. Pour faire les icônes:

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

Ensuite, chaque icône pourrait ressembler à 'save' + iconSuffix + '.jpg'.

Notez que cela ne fonctionnera pas lorsqu'un conteneur déborde de son parent (par exemple, si la hauteur CSS est de 0 et que le dépassement n'est pas masqué). Faire fonctionner cela serait beaucoup plus complexe.

Nathan MacInnes
la source
1

En combinant les réponses [@ alex-ball, @jeremyharris], j'ai trouvé que c'était la meilleure façon pour moi:

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