Depois de descobrir como obter o resultado do filtro filtro de inversão para um fundo sólidoA próxima ideia que me veio à mente foi, naturalmente, fazer isso com o Sass para reproduzir o efeito do filtro em navegadores que não oferecem suporte a filtros. O Sass já tem um invert mas esta apenas reproduz a função filter: invert(100%) efeito. O objetivo era escrever um que funcionasse para qualquer porcentagem.
Se o senhor se lembra da solução JavaScript que encontramos da última vez, ela era mais ou menos assim:
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(', ') + ')';
Portanto, o primeiro passo é que, dado o red, green, blue (que deve estar entre 0 e 255) e o argumento da função invert, que deve ser um valor entre 0 e 1 ou uma porcentagem entre 0% e 100% (valores fora da faixa são permitidos, mas tudo é limitado aos limites), calculamos os limites da faixa reduzida (se o senhor não sabe o que é isso, consulte o artigo anterior) e os valores invertidos para cada canal.
Portanto, seria algo assim:
$red: 255; $green: 165; $blue: 0; $percentage: 65%; $original: rgb($red, $green, $blue); $upper: ($percentage/100%)*255; $lower: 255 - $upper; $inverted-red: 255 - round($lower + $red*($upper - $lower)/255); $inverted-green: 255 - round($lower + $red*($upper - $lower)/255); $inverted-blue: 255 - round($lower + $red*($upper - $lower)/255); $inverted: rgb($inverted-red, $inverted-green, $inverted-blue);
Mas temos a mesma fórmula para os canais invertidos, portanto, faz sentido colocar isso em uma função:
@function invert-channel($channel, $upper, $lower) {
@return 255 - round($lower + $red*($upper - $lower)/255);
}
Portanto, agora podemos substituir nosso valor final invertido:
$inverted: rgb(invert-channel($red, $upper, $lower),
invert-channel(green, $upper, $lower),
invert-channel(blue, $upper, $lower));
Mas será que realmente precisamos expor todas essas chamadas ao invert-channel? Bem, não, então vamos criar outra função que lida com o cálculo dos limites esmagados e as chamadas para o invert-channel:
@function _invert($red, $green, $blue, $percentage) {
$upper: ($percentage/100%)*255;
$lower: 255 - $upper;
@return rgb(invert-channel($red, $upper, $lower),
invert-channel($green, $upper, $lower),
invert-channel($blue, $upper, $lower));
}
Tudo bem, mas talvez nem sempre queiramos que nosso plano de fundo original seja um rgb() valor. Talvez queiramos que ele seja uma palavra-chave ou um hsl() valor. É claro que poderíamos usar alguma ferramenta para fazer a conversão antes de usá-la. Mas será que não existe uma maneira de obter os canais independentemente do formato com o qual começamos? Bem, existe! O Sass tem red, green e blue funcionam exatamente para isso.
Isso significa que podemos simplificar nosso _invert função:
@function _invert($original, $percentage) {
$upper: ($percentage/100%)*255;
$lower: 255 - $upper;
@return rgb(invert-channel(red($original), $upper, $lower),
invert-channel(green($original), $upper, $lower),
invert-channel(blue($original), $upper, $lower));
}
Portanto, nosso código original se tornou:
$original: orange;
$percentage: 65%;
@function invert-channel($channel, $upper, $lower) {
@return 255 - round($lower + $red*($upper - $lower)/255);
}
@function _invert($original, $percentage) {
$upper: ($percentage/100%)*255;
$lower: 255 - $upper;
@return rgb(invert-channel(red($original), $upper, $lower),
invert-channel(green($original), $upper, $lower),
invert-channel(blue($original), $upper, $lower));
}
$inverted: _invert($original, $percentage);
Isso parece muito melhor! No entanto, podemos limpá-lo ainda mais com chamando dinamicamente o invert-channel. Isso significa que podemos reescrever nosso _invert desta forma:
@function _invert($original, $percentage) {
$upper: ($percentage/100%)*255;
$lower: 255 - $upper;
$inverted-channels: ();
@each $channel-name in 'red' 'green' 'blue' {
$channel: call($channel-name, $original);
$inverted-channel: invert-channel($channel, $upper, $lower);
$inverted-channels: append($inverted-channels, $inverted-channel);
}
@return rgb($inverted-channels...);
}
Isso é muito mais elegante, pois eliminamos todas as repetições. Há apenas mais uma coisa com a qual precisamos lidar: a possibilidade de nosso valor original ser semitransparente. Na verdade, isso é muito fácil, pois podemos extrair seu canal alfa usando a função alpha e, posteriormente, podemos acrescentar esse valor à lista de canais invertidos. Dessa forma, nosso código final será:
@function invert-channel($channel, $upper, $lower) {
@return 255 - round($lower + $channel*($upper - $lower)/255);
}
@function _invert($original, $percentage) {
$upper: ($percentage/100%)*255;
$lower: 255 - $upper;
$alpha: alpha($original);
$inverted-channels: ();
@each $channel-name in 'red' 'green' 'blue' {
$channel: call($channel-name, $original);
$inverted-channel: invert-channel($channel, $upper, $lower);
$inverted-channels: append($inverted-channels, $inverted-channel);
}
$inverted-channels: append($inverted-channels, $alpha);
@return rgba($inverted-channels...);
}
$inverted: _invert($original, $percentage);
O que é legal sobre o rgba é que ela produzirá um rgba() no CSS resultante somente se seu alfa for estritamente menor que 1.
O senhor pode vê-lo funcionando e brincar com ele nesta caneta:
Veja a caneta Emular o filtro invert() com a função Sass personalizada por Ana Tudor (@thebabydino) em CodePen.
Agradecimentos especiais ao assistente Sass Hugo Giraudel por revisar este texto.
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.