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#ff8c00rgb(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.