Recentemente atualizei os gráficos animados do meu site com um novo tema e um grupo de personagens pioneiros, colocando em prática muitas das técnicas que compartilhei nesta série. Algumas de minhas animações mudam de aparência quando alguém interage com elas ou em horários diferentes do dia.
As cores do gráfico no topo das páginas do meu blog mudam de manhã à noite, todos os dias. Depois, há o modo neve, que adiciona cores frias e um tema invernal, cortesia de uma camada de sobreposição e um modo de mesclagem.
Enquanto trabalhava nisso, comecei a me perguntar se os valores relativos de cores do CSS poderiam me dar mais controle e, ao mesmo tempo, simplificar o processo. Observação: neste tutorial, vou me concentrar nos valores relativos de cores e no espaço de cores OKLCH para temas gráficos e animações. Se você quiser se aprofundar nas cores relativas, Ahmad Shadeed criou um excelente guia interativo. Quanto aos espaços de cores, gamas e OKLCH, nosso próprio Geoff Graham escreveu sobre eles.
O uso repetido de elementos foi fundamental. Os planos de fundo foram reutilizados sempre que possível, com zooms e sobreposições ajudando a construir novas cenas a partir da mesma obra de arte. Nasceu da necessidade, mas também encorajou pensar em séries e não em cenas individuais. O problema com a atualização manual das paletas de cores Vamos direto ao meu desafio. Em títulos de Toon como este - baseado no episódio “Lullabye-Bye Bear” de 1959 do Yogi Bear Show - e em meu trabalho em geral, as paletas são limitadas a algumas cores selecionadas.
Eu crio sombras e matizes a partir do que chamo de minha cor “base” para expandir a paleta sem adicionar mais matizes.
No Sketch, trabalho no espaço de cores HSL, portanto esse processo envolve aumentar ou diminuir o valor de luminosidade da minha cor base. Honestamente, não é uma tarefa árdua – mas escolher uma cor de base diferente requer a criação de um novo conjunto de tons e tonalidades. Fazer isso manualmente, repetidas vezes, rapidamente se torna trabalhoso.
Mencionei o espaço de cores HSL – H (matiz), S (saturação) e L (luminosidade), mas essa é apenas uma das várias maneiras de descrever a cor. RGB — R (vermelho), G (verde), B (azul) — é provavelmente o mais familiar, pelo menos em sua forma hexadecimal. Há também LAB - L (luminosidade), A (verde-vermelho), B (azul-amarelo) - e o modelo mais recente, mas agora amplamente suportado LCH - L (luminosidade), C (croma), H (matiz) - em sua forma OKLCH. Com LCH – especificamente OKLCH em CSS – posso ajustar o valor de luminosidade da minha cor de base.
Ou posso alterar seu croma. O croma LCH e a saturação HSL descrevem a intensidade ou riqueza de uma cor, mas o fazem de maneiras diferentes. O LCH me oferece uma gama mais ampla e uma mistura mais previsível entre as cores.
Também posso alterar o matiz para criar uma paleta de cores que compartilhem os mesmos valores de luminosidade e croma. Tanto no HSL quanto no LCH, o espectro de matiz começa no vermelho, passa pelo verde e pelo azul e retorna ao vermelho.
Por que o OKLCH mudou a forma como penso sobre as cores O suporte do navegador para o espaço de cores OKLCH agora é generalizado, mesmo que as ferramentas de design – incluindo o Sketch – não tenham se atualizado. Felizmente, isso não deve impedir você de usar o OKLCH. Os navegadores terão prazer em converter valores Hex, HSL, LAB e RGB em OKLCH para você. Você pode definir uma propriedade personalizada CSS com uma cor base em qualquer espaço, incluindo Hex: /* Cor da base */ --fundação: #5accd6;
Quaisquer cores derivadas dele serão convertidas em OKLCH automaticamente: --foundation-light: oklch(from var(--foundation) [...]; } --fundação-mid: oklch(from var(--fundação) [...]; } --fundação-dark: oklch(from var(--fundação) [...]; }
Cor relativa como sistema de design Pense na cor relativa como se dissesse: “Pegue esta cor, ajuste-a e depois me dê o resultado”. Existem duas maneiras de ajustar uma cor: alterações absolutas e alterações proporcionais. Eles parecem semelhantes no código, mas se comportam de maneira muito diferente quando você começa a trocar as cores de base. Entender essa diferença é o que pode transformar o uso da cor relativa em um sistema. /* Cor da base */ --fundação: #5accd6;
Por exemplo, o valor de luminosidade da minha cor de base é 0,7837, enquanto uma versão mais escura tem um valor de 0,5837. Para calcular a diferença, subtraio o valor inferior do superior e aplico o resultado usando uma função calc(): --fundação-escuro: oklch(de var(--fundação) calc(l - 0,20) c h);
Para obter uma cor mais clara, adiciono a diferença: --fundação-luz: oklch(de var(--fundação) calc(l + 0,10) c h);
Cromaos ajustes seguem o mesmo processo. Para reduzir a intensidade da minha cor de base de 0,1035 para 0,0035, subtraio um valor do outro: oklch(de var(--fundação) eu calc(c - 0,10) h);
Para criar uma paleta de tons, calculo a diferença entre o valor do matiz da minha cor de base (200) e do meu novo matiz (260): oklch(de var(--fundação) l c calc(h + 60));
Esses cálculos são absolutos. Quando subtraio um valor fixo, estou efetivamente dizendo: “Sempre subtraia esse valor”. O mesmo se aplica ao adicionar valores fixos: calcular(c - 0,10) calcular(c + 0,10)
Aprendi os limites dessa abordagem da maneira mais difícil. Quando confiei na subtração de valores fixos de croma, as cores caíram para o cinza assim que mudei a base. Uma paleta que funcionava para uma cor desmoronou para outra. A multiplicação se comporta de maneira diferente. Quando multiplico o croma, estou dizendo ao navegador: “Reduza a intensidade desta cor em uma proporção”. A relação entre as cores permanece intacta, mesmo quando a base muda: calcular (c * 0,10)
Minhas regras para mover, dimensionar e girar
Mover leveza (adicionar ou subtrair), Escala de croma (multiplicação), Girar matiz (adicionar ou subtrair graus).
Eu dimensiono o croma porque quero que as mudanças de intensidade permaneçam proporcionais à cor base. As relações de matiz são rotacionais, portanto multiplicar o matiz não faz sentido. A leveza é perceptiva e absoluta – multiplicá-la geralmente produz resultados estranhos.
De uma cor a um tema inteiro A cor relativa me permite definir uma cor de base e gerar todas as outras cores necessárias – preenchimentos, traços, limites de gradiente, sombras – a partir dela. Nesse ponto, a cor deixa de ser uma paleta e passa a ser um sistema. Ilustrações SVG tendem a reutilizar as mesmas cores em preenchimentos, traços e gradientes. A cor relativa permite definir essas relações uma vez e reutilizá-las em qualquer lugar – da mesma forma que os animadores reutilizavam planos de fundo para criar novas cenas.
Altere a cor de base uma vez e cada cor derivada será atualizada automaticamente, sem recalcular nada manualmente. Fora dos gráficos animados, eu poderia usar essa mesma abordagem para definir cores para os estados de elementos interativos, como botões e links. A cor de base que usei no título do Toon “Lullabye-Bye Bear” é um azul de aparência ciano. O fundo é um gradiente radial entre minha base e uma versão mais escura.
Para criar versões alternativas com ambientes totalmente diferentes, só preciso mudar a cor da base: --fundação: #5accd6; --grad-end: var(--fundação); --grad-start: oklch(de var(--foundation) calc(l - 0,2357) calc(c * 0,833)h);
Para vincular essas propriedades personalizadas ao meu gradiente SVG sem duplicar os valores de cores, substituí os valores de stop-color codificados por estilos embutidos:
Em seguida, eu precisava garantir que meu Toon Text sempre contrastasse com qualquer cor de base que eu escolhesse. Uma rotação de matiz de 180 graus produz uma cor complementar que certamente se destaca - mas pode vibrar desconfortavelmente: .text-light { preencher: oklch(from var(--foundation) l c calc(h + 180)); }
Um deslocamento de 90° produz uma cor secundária vívida sem ser totalmente complementar: .text-light { preencher: oklch(from var(--foundation) l c calc(h - 90)); }
Minha recriação do título Toon de 1959 de Quick Draw McGraw, “El Kabong”, usa as mesmas técnicas, mas com uma paleta mais variada. Por exemplo, há outro gradiente radial entre a cor de base e um tom mais escuro.
O edifício e a árvore ao fundo são simplesmente tons diferentes da mesma cor de base. Para esses caminhos, precisei de duas cores de preenchimento adicionais: .bg-mid { preencher: oklch(from var(--foundation) calc(l - 0,04) calc(c * 0,91)h); }
.bg-escuro { preencher: oklch(from var(--foundation) calc(l - 0,12) calc(c * 0,64)h); }
Quando as fundações começam a se mover
Até agora, tudo que mostrei foi estático. Mesmo quando alguém usa um seletor de cores para alterar a cor da base, essa mudança acontece instantaneamente. Mas os gráficos animados raramente ficam parados – a pista está no nome. Portanto, se a cor faz parte do sistema, não há razão para que ela também não possa ser animada.
Para animar a cor de base, primeiro preciso dividi-la em canais OKLCH- luminosidade, croma e matiz. Mas há uma etapa extra importante: preciso registrar esses valores como propriedades personalizadas digitadas. Mas o que isso significa?
Por padrão, um navegador não sabe se o valor de uma propriedade personalizada CSS representa uma cor, comprimento, número ou algo totalmente diferente. Isso geralmente significa que eles não podem ser interpolados suavemente durante a animação e pular de um valor para outro.
O registro de uma propriedade customizada informa ao navegador o tipo de valor que ela representa e como deve se comportar ao longo do tempo. Nesse caso, quero que o navegador trate meus canais de cores como números para que possam ser animados sem problemas.
@propriedade --f-l {
sintaxe: "
@propriedade --fc {
sintaxe: "
@propriedade --f-h {
sintaxe: "
Depois de registradas, essas propriedades personalizadas se comportam como CSS nativo. O navegador pode interpolá-los quadro a quadro. Em seguida, reconstruo a cor de base desses canais: --fundação: oklch(var(--f-l) var(--f-c) var(--f-h));
Isso faz com que a cor base se torne animável, assim como qualquer outro valor numérico. Aqui está uma animação simples de “respiração” que muda suavemente a luminosidade ao longo do tempo: @keyframes respire { 0%, 100% { --f-l: 0,36; } 50% { --f-l: 0,46; } }
.toon-título { animação: respire 10s com facilidade de entrada e saída infinita; }
Como todas as outras cores em preenchimentos, gradientes e traços são derivadas de --foundation, todas elas são animadas juntas e nada precisa ser atualizado manualmente. Uma cor animada, muitos efeitos No início deste processo, me perguntei se os valores relativos de cores do CSS poderiam oferecer mais possibilidades e, ao mesmo tempo, torná-los mais simples de implementar. Recentemente, adicionei um novo fundo de mina de ouro à página de contato do meu site, e a primeira iteração incluiu lâmpadas a óleo que brilham e balançam.
Eu queria explorar como a animação de cores relativas do CSS poderia tornar o interior da mina mais realista, tingindo-o com as cores das lâmpadas. Eu queria que eles afetassem o mundo ao seu redor, da mesma forma que a luz real faz. Então, em vez de animar múltiplas cores, construí um pequeno sistema de iluminação que anima apenas uma cor.
Minha primeira tarefa foi inserir uma camada de sobreposição entre o fundo e minhas lâmpadas:
Usei mix-blend-mode: color porque tinge o que está abaixo dele, preservando a luminância subjacente. Como só quero que a sobreposição fique visível quando as animações estiverem ativadas, optei pela inclusão da sobreposição: .svg-mine #overlay { exibição: nenhum; }
@media (prefere movimento reduzido: sem preferência) { .svg-mine[data-animations=on] #overlay { exibição: bloco; opacidade: 0,5; } }
A sobreposição estava instalada, mas ainda não conectada às lâmpadas. Eu precisava de uma fonte de luz. Minhas lâmpadas são simples e cada uma contém um elemento circular que desfoquei com um filtro. O filtro produz um desfoque muito suave em todo o círculo.
Em vez de animar a sobreposição e as lâmpadas separadamente, eu animo um único token de cor de “chama” e derivo todo o resto dele. Primeiro, registro três propriedades customizadas digitadas para canais OKLCH:
@propriedade --fl-l {
sintaxe: "
Eu animei esses canais, empurrando deliberadamente alguns quadros para o laranja para que a cintilação parecesse claramente a luz do fogo:
@keyframes chama { 0%, 100% { --fl-l: 0,86; --fl-c: 0,12; --fl-h: 95; } 6% { --fl-l: 0,91; --fl-c: 0,10; --fl-h: 92; } 12% { --fl-l: 0,83; --fl-c: 0,14; --fl-h: 100; } 18% { --fl-l: 0,88; --fl-c: 0,11; --fl-h: 94; } 24% { --fl-l: 0,82; --fl-c: 0,16; --fl-h: 82; } 30% { --fl-l: 0,90; --fl-c: 0,12; --fl-h: 90; } 36% { --fl-l: 0,79; --fl-c: 0,17; --fl-h: 76; } 44% { --fl-l: 0,87; --fl-c: 0,12; --fl-h: 96; } 52% { --fl-l: 0,81; --fl-c: 0,15; --fl-h: 102; } 60% { --fl-l: 0,89; --fl-c: 0,11; --fl-h: 93; } 68% { --fl-l: 0,83; --fl-c: 0,16; --fl-h: 85; } 76% { --fl-l: 0,91; --fl-c: 0,10; --fl-h: 91; } 84% { --fl-l: 0,85; --fl-c: 0,14; --fl-h: 98; } 92% {--fl-l: 0,80; --fl-c: 0,17; --fl-h: 74; } }
Em seguida, coloquei essa animação no SVG, para que as variáveis compartilhadas fiquem disponíveis tanto para as lâmpadas quanto para minha sobreposição:
@media (prefere movimento reduzido: sem preferência) { .svg-mine[data-animations=on] { animação: chama 3,6s infinita linear; isolamento: isolar;
/* Construa uma cor de chama a partir de canais animados */ --flame: oklch(var(--fl-l) var(--fl-c) var(--fl-h));
/* Cor da lâmpada derivada da chama */ --lâmpada-núcleo: oklch(de var(--chama) calc(l + 0,05) calc(c * 0,70) h);
/* Tonalidade de sobreposição derivada da mesma chama */ --overlay-tint: oklch(de var(--flame) calc(l + 0,06) calc(c * 0,65) calc(h - 10)); } }
Por fim, apliquei essas cores derivadas às lâmpadas brilhantes e à sobreposição que elas afetam: @media (prefere movimento reduzido: sem preferência) { .svg-mine[data-animations=on] #mine-lamp-1 > círculo, .svg-mine[data-animations=on] #mine-lamp-2 > círculo { preencher: var(--lâmpada-núcleo); }
.svg-mine[data-animations=on] #overlay { exibição: bloco; preencher: var(--overlay-tint); opacidade: 0,5; } }
Quando a chama muda para laranja, as lâmpadas aquecem e a cena aquece com elas. Quando a chama esfria, tudo se acalma. A melhor parte é que nada é escrito manualmente. Se eu alterar a cor de base ou ajustar os intervalos de animação das chamas, todo o sistema de iluminação será atualizado simultaneamente. Você pode ver o resultado final no meu site. Reutilizar, reaproveitar, revisitar Aqueles animadores da Hanna-Barbera foram forçados a reaproveitar elementos por necessidade, mas eu reutilizo cores porque tornam meu trabalho mais consistente e fácil de manter. Os valores relativos de cores CSS me permitem:
Defina uma única cor de base, Descreva como outras cores se relacionam com ela, Reutilize esses relacionamentos em todos os lugares e Anime o sistema alterando um valor.
A cor relativa não apenas torna o tema mais fácil. Encoraja uma forma de pensar onde a cor, tal como o movimento, é intencional – e onde a alteração de um valor pode transformar uma cena inteira sem reescrever o trabalho por baixo dela.