Изменить цвет текста в зависимости от яркости закрываемой области фона?

114

Я уже некоторое время думал о следующем, поэтому теперь хочу узнать ваше мнение, возможные решения и так далее.

Я ищу плагин или метод, который изменяет цвет текста или переключается между предопределенными изображениями / значками в зависимости от средней яркости покрытых пикселей его родительского фонового изображения или -цвета.

Если закрытая область фона довольно темная, сделайте текст белым или переключите значки.

Кроме того, было бы замечательно, если бы сценарий заметил, что родительский элемент не имеет определенного background-color или -image, а затем продолжил бы поиск ближайшего (от родительского элемента к его родительскому элементу ..).

Как вы думаете, знаете об этой идее? Есть ли что-то подобное? сценарии-примеры?

Ура, Дж.

Джеймс Каззетта
источник
1
Просто мысль, а не ответ. Может быть способ установить цвета с помощью HSL, а затем посмотреть значение яркости. Если это значение выше определенного значения, примените правило CSS.
Скотт Браун
1
возможно, вы могли бы разобрать цвет фона элемента на значения R, G, B (и необязательные альфа), работая с деревом DOM, если альфа-канал установлен на ноль. Однако совсем другое дело - попытаться определить цвет фонового изображения.
jackwanders 08
уже ответил здесь stackoverflow.com/questions/5650924/javascript-color-contraster
Pascal
@Pascal Очень похоже, и хороший ввод ... но это не точный ответ на мой вопрос.
Джеймс Каззетта 05

Ответы:

177

Интересные ресурсы для этого:

Вот алгоритм W3C (с демонстрацией 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>

Алекс Болл
источник
Феликс, ты прав! Я сейчас в отъезде, но уверен, что когда вернусь, я обновлю ответ
Алекс Болл
1
Может быть сокращено до следующего, если вы передаете ему объект :::: const setContrast = rgb => (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000> 125? 'черный': 'белый'
Люк Робертсон
вам нужен jquery только для изменения css?
bluejayke 07
@bluejayke нет, есть другие способы ;-)
Alex Ball
63

Эта статья о 24 способах расчета цветового контраста может быть вам интересна. Игнорируйте первый набор функций, потому что они неверны, но формула YIQ поможет вам определить, использовать ли светлый или темный цвет переднего плана.

Как только вы получите цвет фона элемента (или предка), вы можете использовать эту функцию из статьи, чтобы определить подходящий цвет переднего плана:

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
источник
Спасибо, это действительно полезно .. Это зависит от установленного background-color .. Но знаете ли вы, как получить средний цвет изображения, просматривая каждый пиксель (например, в цикле)?
Джеймс Каззетта,
4
В es6 вы можете сделать это с помощью: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
Я взял эту функцию и немного расширил ее, чтобы вы могли возвращать два пользовательских цвета, а не всегда черный и белый. Обратите внимание: если цвета расположены близко друг к другу, у вас все равно могут возникнуть проблемы с контрастом, но это хорошая альтернатива возврату абсолютных цветов jsfiddle.net/1905occv/1
Ханна
2
это круто, я бы просто настроил yiq на> = 160, у меня сработало лучше.
Артуро
15

Интересный вопрос. Моей немедленной мыслью было инвертировать цвет фона как текста. Это включает в себя простой синтаксический анализ фона и инвертирование его значения RGB.

Примерно так: 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+')');
Джеремихаррис
источник
Вы, вероятно, захотите обесцветить свой «инвертированный» цвет, усреднив инвертированные значения R, G, B и установив их равными друг другу. Однако это решение получает свой базовый цвет из строки, а не из свойства CSS элемента. Чтобы быть надежным, решение должно динамически получать цвета фона, которые обычно возвращают значения rgb () или rgba (), но могут отличаться в зависимости от браузера.
jackwanders 08
Да. Для простоты разбора я просто использовал шестнадцатеричное значение. Я обновил скрипку, чтобы включить захват цвета элемента из CSS. Я обновил скрипку и включил своего рода регулировку яркости (я ничего не знаю о математике цвета, поэтому, вероятно, это не настоящая яркость).
jeremyharris 08
1
Как насчет этого? stackoverflow.com/questions/2541481/…
jeremyharris
2
Что делать, если цвет фона #808080!?
Натан Макиннес,
1
@NathanMacInnes, он все равно инвертирует это, просто так получилось, что инвертирование чего-то прямо в середине спектра приведет к самому себе. Этот код просто инвертирует цвет, что имеет свои ограничения.
jeremyharris
9

mix-blend-mode делает трюк:

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>

Дополнение (март 2018 г.): Далее следует хороший учебник, объясняющий все типы режимов / реализаций: https://css-tricks.com/css-techniques-and-effects-for-knockout-text/

Джеймс Каззетта
источник
5

Я нашел скрипт BackgroundCheck очень полезным.

Он обнаруживает чрезмерную яркость фона (будь то фоновое изображение или цвет) и применяет класс к назначенному текстовому элементу ( background--lightили background--dark) в зависимости от яркости фона.

Может применяться к неподвижным и движущимся элементам.

( Источник )

cptstarling
источник
Спасибо за вклад!
Джеймс Каззетта, 03
Это работает для цветов фона? Я быстро прочитал сценарий и не вижу, чтобы он использовал цвет фона для проверки яркости. Только изображения.
Jørgen Skår Fischer
1
Привет, Йорген, я думаю, что сценарий colourBrightness может послужить вашей цели: github.com/jamiebrittain/colourBrightness.js
cptstarling
5

Если вы используете ES6, конвертируйте шестнадцатеричный формат в RGB, а затем можете использовать это:

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

Вот моя попытка:

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

Затем использовать:

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

Это сразу сделает текст либо черным, либо белым по мере необходимости. Чтобы сделать значки:

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

Тогда каждая иконка могла выглядеть так 'save' + iconSuffix + '.jpg'.

Обратите внимание, что это не сработает, если какой-либо контейнер выходит за пределы своего родителя (например, если высота CSS равна 0 и переполнение не скрыто). Было бы намного сложнее заставить это работать.

Натан Макиннес
источник
1

Объединив ответы [@ alex-ball, @jeremyharris], я обнаружил, что это лучший способ для меня:

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

А. Эль-захаби
источник