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.

Ana Tudor

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.