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.