Havia algo que me incomodava depois de ler o artigo de David sobre o invert
filtro na semana passada. Era esta frase:
Os valores informados pelo
window.getComputedStyle(el)
serão os valores CSS originais, entretanto, de modo que não há como obter os valores invertidos verdadeiros de determinadas propriedades.
Mas se eu puder ler o valor original para background-color
ou color
e posso ler o valor para o filter
, então posso computar o valor invertido, o que significa que há uma maneira, afinal.
Bem, vamos ver como fazemos isso.
Inversão total para um fundo sólido
Antes de mais nada, precisamos entender como o invert
e começaremos com o caso específico da inversão total – o invert(100%)
. Para um valor original arbitrário rgb(red, green, blue)
, onde red
, green
e blue
podem assumir valores entre 0
e 255
aplicando um invert(100%)
vai produzir rgb(255 - red, 255 - green, 255 - blue)
.
Isso é bastante direto e fácil de codificar, especialmente porque os valores obtidos por meio do window.getComputedStyle(el)
são rgb()
ou rgba()
em todos os navegadores que suportam filtros CSS, dependendo do fato de o valor inicial ter um canal alfa menor que 1
ou não.
Observação: Filtros CSS com valores de função de filtro (não apenas url()
) são compatíveis com o Chrome 18+, Safari 6.1+, Opera 15+ e Firefox 34+. O Chrome, o Safari e o Opera precisam do parâmetro -webkit-
enquanto o Firefox não precisa e nunca precisou de um prefixo, mas precisa do layout.css.filters.enabled
definido como true
em about:config
. No entanto, o IE ainda não oferece suporte a filtros CSS eles estão listados como “Under Consideration” (em consideração). Se forem implementadas, provavelmente também não precisarão de um prefixo. Portanto, não use -moz-
ou -ms-
prefixos para filtros!
Portanto, se tivermos um elemento para o qual definimos um background-color
e depois aplicamos um invert(100%)
sobre ele:
.box { background-color: darkorange; -webkit-filter: invert(100%); filter: invert(100%); }
Em seguida, podemos ler seus estilos e calcular o valor invertido a partir do original:
var box = document.querySelector('.box'), styles = window.getComputedStyle(box), original = styles.backgroundColor, channels = original.match(/\d+/g), inverted_channels = channels.map(function(ch) { return 255 - ch; }), inverted = 'rgb(' + inverted_channels.join(', ') + ')';
Ele pode ser visto funcionando em esta caneta, onde a primeira caixa tem o original background-color
e nenhum filtro aplicado, e a segunda, o mesmo background-color
e um invert(100%)
enquanto o terceiro tem um fundo que é o valor invertido (conforme computado pelo código JavaScript acima) do filtro original background-color
, assumindo um invert(100%)
filtro.
Veja a caneta Obtendo o valor invertido para `background-color` via JS #1 por Ana Tudor (@thebabydino) em CodePen.
E quanto aos planos de fundo semitransparentes?
Mas isso é apenas para rgb()
e se o nosso background-color
não for totalmente opaco, então o valor que lemos com JavaScript será um rgba()
valor.
Por exemplo, o backgroundColor
lido via JavaScript será "rgb(255, 140, 0)"
se, no CSS, definirmos background-color
para qualquer uma das seguintes opções:
darkorange
#ff8c00
rgb(255, 140, 0)
rgb(100%, 54.9%, 0%)
rgba(255, 140, 0, 1)
rgba(100%, 54.9%, 0%, 1)
hsl(33, 100%, 50%)
hsla(33, 100%, 50%, 1)
Mas vai ser "rgba(255, 140, 0, .65)"
se definirmos background-color
para um dos seguintes valores:
rgba(255, 140, 0, .65)
rgba(100%, 54.9%, 0%, .65)
hsla(33, 100%, 50%, .65)
Desde que o invert
deixa o filtro alpha
inalterado e modifica apenas o canal red
, green
e blue
isso significa que nosso código JavaScript se torna:
var box = document.querySelector('.box'), styles = window.getComputedStyle(box), original = styles.backgroundColor.split('('), channels = original[1].match(/(0\.\d+)|\d+/g), alpha = (channels.length > 3)?(1*channels.splice(3, 1)[0]):1, inverted_channels = channels.map(function(ch) { return 255 - ch; }), inverted; if(alpha !== 1) { inverted_channels.splice(3, 0, alpha); } inverted = original[0] + '(' + inverted_channels.join(', ') + ')';
Ele pode ser visto funcionando em esta caneta.
Veja a caneta Obtendo o valor invertido para `background-color` via JS #2 por Ana Tudor (@thebabydino) em CodePen.
Observe que isso pressupõe que o valor do canal alfa do plano de fundo que definimos no CSS é sempre maior que 0
. Se o valor do canal alfa for 0
, então o valor retornado por styles.backgroundColor
será "transparent"
e a inversão realmente não tem mais sentido.
Tudo bem, mas isso foi tudo para um filter
valor de invert(100%)
. O que acontece se quisermos ter um valor menor que 100%
?
Caso geral
Bem, para um valor menor que 100%
, digamos 65%
, o [0, 255]
para cada um dos red
, green
e blue
é primeiro esmagado em torno de seu valor central. Isso significa que o limite superior do intervalo vai de 255
para 65%
do 255
e seu limite inferior vai de 0
para 100% - 65% = 35%
do 255
. Em seguida, calculamos o equivalente esmagado do nosso original background-color
o que significa simplesmente pegar o valor de cada canal, dimensioná-lo para o novo intervalo reduzido e, em seguida, adicionar o limite inferior. E, finalmente, a última etapa é obter o valor simétrico com relação ao valor central para cada um dos red
, green
e blue
do equivalente reduzido do nosso original background-color
.
O seguinte demonstração mostra visualmente tudo isso acontecendo, usando o green
como exemplo.
Veja a caneta Esmagando uma faixa de canais por Ana Tudor (@thebabydino) em CodePen.
A faixa original vai de 0
para 255
e o valor original para o green
é considerado como sendo 165
. Qualquer valor menor que 100%
para o argumento do invert
reduz o intervalo. Um valor de 50%
significa que o intervalo é reduzido a nada, pois o limite superior é igual ao inferior. Isso também significa que o valor final invertido do rgb()
final invertido é sempre rgb(128, 128, 128)
independentemente do valor inicial do background-color
possa ser.
O equivalente esmagado do valor original para o green
é aquele que tem um s
na frente de seu rótulo. O valor invertido desse equivalente esmagado em relação ao meio do intervalo tem um i
na frente de seu rótulo. E podemos ver que esse é o valor do green
para o fundo sólido da segunda caixa na parte inferior depois de aplicar um invert
sobre ela.
Isso significa que nosso código JavaScript agora se torna:
var box = document.querySelector('.box'), styles = window.getComputedStyle(box), invert_perc = (styles.webkitFilter || styles.filter).match(/\d+/), upper = invert_perc/100*255, lower = 255 - upper, original = styles.backgroundColor.split('('), channels = original[1].match(/(0\.\d+)|\d+/g), alpha = (channels.length > 3)?(1*channels.splice(3, 1)[0]):1, inverted_channels = channels.map(function(ch) { return 255 - Math.round(lower + ch*(upper - lower)/255); }), inverted; if(alpha !== 1) { inverted_channels.splice(3, 0, alpha); } inverted = original[0] + '(' + inverted_channels.join(', ') + ')';
No entanto, há um problema: isso pressupõe que o valor de estilo computado para a propriedade filter seja algo como "invert(65%)"
. Mas, na maioria das vezes, esse não é o caso. Se definirmos o valor do filtro como invert(65%)
no CSS, então o valor que obtemos dessa forma via JavaScript é "invert(0.65)"
nos navegadores WebKit e "invert(65%)"
no Firefox. Se definirmos o valor do filtro como filter(.65)
no CSS, o valor que obtemos via JavaScript é "invert(0.65)"
. O Firefox o retorna no mesmo formato em que o definimos no CSS, enquanto os navegadores WebKit sempre o retornam como um valor entre 0
e 1
. Portanto, precisamos lidar com isso também:
var box = document.querySelector('.box'), styles = window.getComputedStyle(box), filter = (styles.webkitFilter || styles.filter), invert_arg = filter.match(/(0\.\d+)|\d+/)[0], upper = ((filter.indexOf('%') > -1)?(invert_arg/100):invert_arg)*255, lower = 255 - upper, original = styles.backgroundColor.split('('), channels = original[1].match(/(0\.\d+)|\d+/g), alpha = (channels.length > 3)?(1*channels.splice(3, 1)[0]):1, inverted_channels = channels.map(function(ch) { return 255 - Math.round(lower + ch*(upper - lower)/255); }), inverted; if(alpha !== 1) { inverted_channels.splice(3, 0, alpha); } inverted = original[0] + '(' + inverted_channels.join(', ') + ')';
Como pode ser visto em esta canetaagora funciona corretamente para ambos os formatos, desde que o alpha
não seja o canal 0
.
Veja a caneta Obtendo o valor invertido para `background-color` via JS #3 por Ana Tudor (@thebabydino) em CodePen.
Palavras finais
Isso ainda lida apenas com o caso muito simples em que o valor do filter
é apenas um invert
função. Ela não funcionará quando tivermos funções de filtro encadeadas, por exemplo, algo como filter: hue-rotate(90deg) invert(73%)
.

Sobre Ana Tudor
Adora matemática, especialmente geometria. Gosta de brincar com códigos. Apaixonado por fazer experimentos e aprender coisas novas. Fascinado por astrofísica e ciência em geral. Grande fã do avanço tecnológico e de suas aplicações em todos os campos. Demonstra interesse por esportes motorizados, desenho, desenhos animados clássicos, música rock, brinquedos carinhosos e animais com garras afiadas e dentes grandes. Sonha em ter um tigre de verdade.