Zmienić kolor tekstu na podstawie jasności zakrytego obszaru tła?

114

Już od jakiegoś czasu myślałem o następujących kwestiach, więc teraz chcę poznać Twoje opinie, możliwe rozwiązania i tak dalej.

Szukam wtyczki lub techniki, która zmienia kolor tekstu lub przełącza się między wstępnie zdefiniowanymi obrazami / ikonami w zależności od średniej jasności pokrytych pikseli obrazu tła lub koloru jego rodzica.

Jeśli zakryty obszar tła jest raczej ciemny, zmień tekst na biały lub zmień ikony.

Ponadto byłoby wspaniale, gdyby skrypt zauważył, że rodzic nie ma zdefiniowanego koloru tła lub obrazu, a następnie kontynuuje wyszukiwanie najbliższego (od elementu nadrzędnego do elementu nadrzędnego ...).

Jak myślisz, wiesz o tym pomyśle? Czy jest już coś podobnego? przykłady skryptów?

Pozdrawiam, J.

James Cazzetta
źródło
1
Tylko myśl, a nie odpowiedź. Może istnieć sposób na ustawienie kolorów za pomocą HSL, a następnie sprawdzenie wartości jasności. Jeśli ta wartość jest powyżej określonej wartości, zastosuj regułę CSS.
Scott Brown
1
można sobie wyobrazić, że można wydzielić kolor tła elementu na wartości R, G, B (i opcjonalnie alfa), pracując nad drzewem DOM, jeśli kanał alfa jest ustawiony na zero. Jednak próba określenia koloru obrazu tła to zupełnie inna sprawa.
jackwanders
@ Pascal Całkiem podobny i dobry wkład .. ale to nie jest dokładna odpowiedź na moje pytanie.
James Cazzetta,

Odpowiedzi:

176

Ciekawe zasoby do tego:

Oto algorytm W3C (również z demonstracją 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>

Alex Ball
źródło
Felix masz rację! Jestem teraz daleko, ale na pewno, kiedy wrócę, zaktualizuję odpowiedź
Alex Ball
1
Można go skrócić do następującego, pod warunkiem, że przekażesz mu obiekt :::: const setContrast = rgb => (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000> 125? „czarny”: „biały”
Luke Robertson
czy potrzebujesz jquery tylko do zmiany css?
bluejayke
@bluejayke nie, są inne sposoby ;-)
Alex Ball
63

Ten artykuł o 24 sposobach obliczania kontrastu kolorów może Cię zainteresować. Zignoruj ​​pierwszy zestaw funkcji, ponieważ są one błędne, ale formuła YIQ pomoże Ci określić, czy użyć jasnego, czy ciemnego koloru pierwszego planu.

Po uzyskaniu koloru tła elementu (lub przodka) możesz użyć tej funkcji z artykułu, aby określić odpowiedni kolor pierwszego planu:

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
źródło
Dzięki, to jest naprawdę pomocne ... To zależy od ustawionego koloru tła .. Ale czy wiesz, jak uzyskać średni kolor obrazu, przechodząc przez każdy piksel (jak w pętli)?
James Cazzetta,
4
W es6 możesz to zrobić za pomocą: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
Wziąłem tę funkcję i nieco ją rozszerzyłem, abyś mógł zwrócić dwa niestandardowe kolory zamiast zawsze czarno-białego. Zauważ, że jeśli dwa kolory są blisko siebie, możesz jeszcze dostać problemy kontrastu, ale jest to dobra alternatywa dla powrocie bezwzględnych kolorach jsfiddle.net/1905occv/1
Hanna
2
ten jest coo, po prostu dostosowałbym yiq do> = 160, działało lepiej dla mnie.
Arturo
15

Interesujące pytanie. Moją natychmiastową myślą było odwrócenie koloru tła jako tekstu. Wymaga to po prostu przeanalizowania tła i odwrócenia jego wartości RGB.

Coś takiego: 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
źródło
Prawdopodobnie chciałbyś desaturować swój „odwrócony” kolor, uśredniając odwrócone wartości R, G, B i ustawiając je sobie równe. Jednak to rozwiązanie pobiera kolor bazowy z ciągu znaków, a nie z właściwości CSS elementu. Aby rozwiązanie było wiarygodne, musiałoby dynamicznie uzyskiwać kolory tła, które zwykle zwracają wartości rgb () lub rgba (), ale mogą się różnić w zależności od przeglądarki.
jackwanders
Tak. Aby ułatwić analizę, użyłem tylko wartości szesnastkowej. Zaktualizowałem skrzypce, aby obejmowało pobieranie koloru elementu z CSS. Zaktualizowałem skrzypce i dodałem rodzaj regulacji jasności (nie wiem nic o matematyce kolorów, więc prawdopodobnie nie jest to naprawdę jasność).
jeremyharris
1
Co powiesz na to? stackoverflow.com/questions/2541481/…
jeremyharris
2
A jeśli kolor tła to #808080!?
Nathan MacInnes,
1
@NathanMacInnes nadal będzie to odwracać, tak się składa, że ​​odwrócenie czegoś dokładnie w środku widma spowoduje samo w sobie. Ten kod po prostu odwraca kolor, który ma swoje ograniczenia.
jeremyharris
9

mix-blend-mode Zrób sztuczkę:

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>

Dodatek (marzec 2018): Następnie ładny samouczek wyjaśniający różne typy trybów / implementacji: https://css-tricks.com/css-techniques-and-effects-for-knockout-text/

James Cazzetta
źródło
5

Uważam, że skrypt BackgroundCheck jest bardzo przydatny.

Wykrywa ogólną jasność tła (czy to obraz tła, czy kolor) i stosuje klasę do przypisanego elementu tekstowego ( background--lightlub background--dark), w zależności od jasności tła.

Może być stosowany do nieruchomych i ruchomych elementów.

( Źródło )

cptstarling
źródło
Dzięki za wkład!
James Cazzetta
Czy to działa w przypadku kolorów tła? Szybko przeczytałem skrypt i nie widzę, aby sprawdzał jasność przy użyciu koloru tła. Tylko obrazy.
Jørgen Skår Fischer
1
Witaj Jørgen, myślę, że skrypt colourBrightness może służyć twojemu celowi: github.com/jamiebrittain/colourBrightness.js
cptstarling
5

Jeśli używasz ES6, przekonwertuj hex na RGB, a następnie możesz użyć tego:

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
źródło
4

Oto moja próba:

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

Następnie, aby go użyć:

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

Spowoduje to, że tekst będzie odpowiednio czarny lub biały. Aby wykonać ikony:

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

Wtedy każda ikona mogłaby wyglądać 'save' + iconSuffix + '.jpg'.

Zauważ, że to nie zadziała, gdy jakikolwiek kontener przepełni swój element nadrzędny (na przykład, jeśli wysokość CSS wynosi 0, a przepełnienie nie jest ukryte). Uzyskanie takiej pracy byłoby znacznie bardziej złożone.

Nathan MacInnes
źródło
1

Łącząc odpowiedzi [@ alex-ball, @jeremyharris] stwierdziłem, że jest to dla mnie najlepszy sposób:

        $('.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
źródło