Como começar a usar as formas CSS

Como ajustar o conteúdo em torno de caminhos personalizados

Razvan Caliman
Razvan Caliman

Por muito tempo, os web designers foram forçados a criar dentro das restrições do retângulo. A maior parte do conteúdo na Web ainda está presa em caixas simples porque a maioria das tentativas criativas de layout não retangular termina em frustração. Isso está prestes a mudar com a introdução das formas CSS, disponíveis a partir do Chrome 37. As formas CSS permitem que os web designers envolvam o conteúdo em caminhos personalizados, como círculos, elipses e polígonos, libertando-se das restrições do retângulo.

As formas podem ser definidas manualmente ou inferidas de imagens.

Vamos analisar um exemplo bem simples.

Talvez você tenha sido tão ingênuo quanto eu ao flutuar uma imagem com partes transparentes pela primeira vez, esperando que o conteúdo se ajustasse e preenchesse as lacunas, mas se decepcionou com a forma retangular que persistiu ao redor do elemento. As formas CSS podem ser usadas para resolver esse problema.

Extrair uma forma de uma imagem
<img class="element" src="image.png" />
<p>Lorem ipsum…</p>

<style>
.element{
  shape-outside: url(image.png);
  shape-image-threshold: 0.5;
  float: left;
}
</style>

A declaração CSS shape-outside: url(image.png) instrui o navegador a extrair uma forma da imagem.

A propriedade shape-image-threshold define o nível mínimo de opacidade dos pixels que serão usados para criar a forma. O valor precisa estar no intervalo entre 0.0 (totalmente transparente) e 1.0 (totalmente opaco). Portanto, shape-image-threshold: 0.5 significa que apenas pixels com opacidade de 50% ou mais serão usados para criar a forma.

A propriedade float é fundamental aqui. Embora a propriedade shape-outside defina o formato da área em que o conteúdo será ajustado, sem o float, você não verá os efeitos do formato.

Os elementos têm uma área flutuante no lado oposto do valor float. Por exemplo, se um elemento com uma imagem de xícara de café estiver flutuando à esquerda, a área de flutuação será criada à direita da xícara. Mesmo que você possa criar uma imagem com lacunas em ambos os lados, o conteúdo só vai se ajustar à forma no lado oposto designado pela propriedade float, esquerda ou direita, nunca ambas.

No futuro, será possível usar shape-outside em elementos que não são flutuantes com as exclusões de CSS de introdução.

Como criar formas manualmente

Além de extrair formas de imagens, você também pode codificá-las manualmente. É possível escolher entre alguns valores funcionais para criar formas: circle(), ellipse(), inset() e polygon(). Cada função de forma aceita um conjunto de coordenadas e é pareada com uma caixa de referência que estabelece o sistema de coordenadas. Vamos falar mais sobre as caixas de referência em breve.

A função circle()

Ilustração do valor de forma circle()

A notação completa para um valor de forma de círculo é circle(r at cx cy), em que r é o raio do círculo, enquanto cx e cy são coordenadas do centro do círculo nos eixos X e Y. As coordenadas do centro do círculo são opcionais. Se você omitir esses valores, o centro do elemento (a interseção das diagonais) será usado como padrão.

.element{
  shape-outside: circle(50%);
  width: 300px;
  height: 300px;
  float: left;
}

No exemplo acima, o conteúdo vai se ajustar ao redor de um caminho circular. O argumento único 50% especifica o raio do círculo, que, neste caso específico, equivale à metade da largura ou altura do elemento. Mudar as dimensões do elemento vai influenciar o raio do formato de círculo. Este é um exemplo básico de como as formas CSS podem ser responsivas.

Antes de continuar, uma observação rápida: é importante lembrar que as formas CSS influenciam apenas a forma da área flutuante ao redor de um elemento. Se o elemento tiver um plano de fundo, ele não será cortado pela forma. Para conseguir esse efeito, use as propriedades de máscara CSS: clip-path ou mask-image. A propriedade clip-path é muito útil porque segue a mesma notação das formas CSS, então é possível reutilizar valores.

Ilustração da forma `circle()` + clip-path

As ilustrações neste documento usam o recorte para destacar a forma e ajudar você a entender os efeitos.

Volte para a forma de círculo.

Ao usar porcentagens para o raio do círculo, o valor é calculado com uma fórmula um pouco mais complexa: sqrt(width^2 + height^2) / sqrt(2). É útil entender isso porque ajuda a imaginar qual será o formato do círculo resultante se as dimensões do elemento não forem iguais.

Todos os tipos de unidade CSS podem ser usados em coordenadas de função de forma: px, em, rem, vw, vh etc. Escolha a opção que seja flexível ou rígida o suficiente para suas necessidades.

É possível ajustar a posição do círculo definindo valores explícitos para as coordenadas do centro.

.element{
  shape-outside: circle(50% at 0 0);
}

Isso posiciona o centro do círculo na origem do sistema de coordenadas. O que é o sistema de coordenadas? É aqui que apresentamos as caixas de referência.

Caixas de referência para formas CSS

A caixa de referência é uma caixa virtual ao redor do elemento, que estabelece o sistema de coordenadas usado para desenhar e posicionar a forma. A origem do sistema de coordenadas fica no canto superior esquerdo, com o eixo X apontando para a direita e o eixo Y apontando para baixo.

Sistema de coordenadas para formas CSS

Lembre-se de que shape-outside altera a forma da área flutuante em que o conteúdo será disposto. A área flutuante se estende até as bordas externas da caixa definida pela propriedade margin. Isso é chamado de margin-box e é a caixa de referência padrão para uma forma se nenhuma for mencionada explicitamente.

As duas declarações CSS a seguir têm resultados idênticos:

.element{
  shape-outside: circle(50% at 0 0);
  /* identical to: */
  shape-outside: circle(50% at 0 0) margin-box;
}

Ainda não definimos uma margem no elemento. Neste ponto, é seguro presumir que a origem do sistema de coordenadas e o centro do círculo estão no canto superior esquerdo da área de conteúdo do elemento. Isso muda quando você define uma margem:

.element{
  shape-outside: circle(50% at 0 0) margin-box;
  margin: 100px;
}

A origem do sistema de coordenadas agora está fora da área de conteúdo do elemento (100 px para cima e 100 px para a esquerda), assim como o centro do círculo. O valor calculado do raio do círculo também aumenta para considerar a superfície maior do sistema de coordenadas estabelecido pela caixa de referência margin-box.

Sistema de coordenadas de caixa de margem com e sem margem
Há algumas opções de caixa de referência: `margin-box`, `border-box`, `padding-box` e `content-box`. Os nomes indicam os limites. Já explicamos o `margin-box`. O `border-box` é limitado pelas bordas externas do elemento, o `padding-box` é limitado pelo padding do elemento, enquanto o `content-box` é idêntico à área de superfície real usada pelo conteúdo em um elemento.
Ilustração de todas as caixas de referência

Só é possível usar uma caixa de referência por vez com uma declaração shape-outside. Cada caixa de referência influencia a forma de uma maneira diferente e, às vezes, sutil. Há outro artigo que se aprofunda e ajuda você a entender as caixas de referência para formas CSS.

A função ellipse()

Ilustração do valor de forma ellipse()

As elipses parecem círculos achatados. Eles são definidos como ellipse(rx ry at cx cy), em que rx e ry são os raios da elipse nos eixos X e Y, enquanto cx e cy são as coordenadas do centro da elipse.

.element{
  shape-outside: ellipse(150px 300px at 50% 50%);
  width: 300px;
  height: 600px;
}

Os valores percentuais serão calculados com base nas dimensões do sistema de coordenadas. Não é preciso fazer contas complicadas. É possível omitir as coordenadas do centro da elipse, que serão inferidas do centro do sistema de coordenadas.

Os raios nos eixos X e Y também podem ser definidos com palavras-chave: farthest-side gera um raio igual à distância entre o centro da elipse e o lado da caixa de referência mais distante dela, enquanto closest-side significa exatamente o oposto: use a menor distância entre o centro e um lado.

.element{
  shape-outside: ellipse(closest-side farthest-side at 50% 50%);
  /* identical to: */
  shape-outside: ellipse(150px 300px at 50% 50%);
  width: 300px;
  height: 600px;
}

Isso pode ser útil quando as dimensões do elemento (ou a caixa de referência) podem mudar de maneiras imprevisíveis, mas você quer que a forma da elipse se adapte.

As mesmas palavras-chave farthest-side e closest-side também podem ser usadas para o raio na função de forma circle().

A função polygon()

Ilustração do valor de forma polygon()

Se os círculos e as elipses forem muito limitantes, a função de forma de polígono abre um mundo de opções. O formato é polygon(x1 y1, x2 y2, ...), em que você especifica pares de coordenadas x e y para cada vértice (ponto) de um polígono. O número mínimo de pares para especificar um polígono é três, um triângulo.

.element{
  shape-outside: polygon(0 0, 0 300px, 300px 600px);
  width: 300px;
  height: 600px;
}

Os vértices são colocados no sistema de coordenadas. Para polígonos responsivos, é possível usar valores percentuais para algumas ou todas as coordenadas.

.element{
  /* polygon responsive to font-size*/
  shape-outside: polygon(0 0, 0 100%, 100% 100%);
  width: 20em;
  height: 40em;
}

Há um parâmetro fill-rule opcional, importado do SVG, que instrui o navegador sobre como considerar o "interior" de um polígono em caso de caminhos de autointerseção ou formas fechadas. Joni Trythall explica muito bem como a propriedade fill-rule funciona em SVG. Se não for definido, o fill-rule será padronizado como nonzero.

.element{
  shape-outside: polygon(0 0, 0 100%, 100% 100%);
  /* identical to: */
  shape-outside: polygon(nonzero, 0 0, 0 100%, 100% 100%);
}

A função inset()

A função de forma inset() permite criar formas retangulares para envolver o conteúdo. Isso pode parecer contra-intuitivo, considerando a premissa inicial de que as formas CSS liberam o conteúdo da Web de caixas simples. Pode ser que sim. Ainda não encontrei um caso de uso para inset() que não seja possível com floats e margens ou com um polygon(). No entanto, inset() fornece uma expressão mais legível para formas retangulares do que polygon().

A notação completa de uma função de forma de inserção é inset(top right bottom left border-radius). Os primeiros quatro argumentos de posição são deslocamentos para dentro das bordas do elemento. O último argumento é o raio da borda para a forma retangular. Ele é opcional, então você pode deixá-lo de fora. Ele segue a notação abreviada border-radius que você já usa no CSS.

.element{
  shape-outside: inset(100px 100px 100px 100px);
  /* yields a rectangular shape which is 100px inset on all sides */
  float: left;
}

Como criar formas com base em caixas de referência

Se você não especificar uma função de forma para a propriedade shape-outside, o navegador poderá derivar uma forma da caixa de referência do elemento. A caixa de referência padrão é margin-box. Nada de novo até agora, é assim que os números de ponto flutuante já funcionam. Mas, ao aplicar essa técnica, é possível reutilizar a geometria de um elemento. Vamos analisar a propriedade border-radius.

Se você usá-la para arredondar os cantos de um elemento flutuante, vai receber o efeito de recorte, mas a área flutuante vai continuar retangular. Adicione shape-outside: border-box para envolver o contorno criado pelo border-radius.

Extração de uma forma do border-radius de um elemento usando a caixa de referência border-box
.element{
  border-radius: 50%;
  shape-outside: border-box;
  float: left;
}

É claro que você pode usar todas as caixas de referência dessa maneira. Outro uso para formas derivadas são as citações deslocadas.

Como criar uma citação deslocada usando a caixa de referência content-box

É possível conseguir o efeito de citação deslocada usando apenas as propriedades float e margin. Mas isso exige que você posicione o elemento de citação na árvore HTML no ponto em que quer que ele seja renderizado.

Veja como conseguir o mesmo efeito de citação deslocada com mais flexibilidade:

.pull-quote{
  shape-outside: content-box;
  margin-top: 200px;
  float: left;
}

Definimos explicitamente a caixa de referência content-box para o sistema de coordenadas da forma. Nesse caso, a quantidade de conteúdo na citação em destaque define o formato em que o conteúdo externo será ajustado. A propriedade margin-top é usada aqui para posicionar (deslocar) a citação, independentemente da posição dela na árvore HTML.

Margem da forma

Você vai notar que, ao envolver o conteúdo em uma forma, ele pode ficar muito próximo do elemento. É possível adicionar espaçamento ao redor da forma com a propriedade shape-margin.

.element{
  shape-outside: circle(40%);
  shape-margin: 1em;
  float: left;
}

O efeito é semelhante ao que você está acostumado ao usar a propriedade margin normal, mas o shape-margin afeta apenas o espaço ao redor do valor shape-outside. Ele vai adicionar espaçamento ao redor da forma somente se houver espaço para isso no sistema de coordenadas. É por isso que, no exemplo acima, o raio do círculo está definido como 40%, e não 50%. Se o raio fosse definido como 50%, o círculo ocuparia todo o espaço no sistema de coordenadas, não deixando espaço para o efeito de shape-margin. Lembre-se de que a forma é limitada ao margin-box do elemento (o elemento mais o margin ao redor). Se a forma for maior e transbordar, ela será cortada para o margin-box, e você terá uma forma retangular.

É importante entender que shape-margin aceita apenas um valor positivo. Ele não tem uma notação longa. Afinal, o que é uma margem superior de forma para um círculo?

Como animar formas

É possível misturar formas CSS com muitos outros recursos CSS, como transições e animações. No entanto, é importante ressaltar que os usuários acham muito irritante quando o layout do texto muda enquanto estão lendo. Preste muita atenção à experiência se decidir animar formas.

É possível animar os raios e os centros das formas circle() e ellipse(), desde que sejam definidos em valores que o navegador possa interpolar. É possível passar de circle(30%) para circle(50%). No entanto, a animação entre circle(closest-side) e circle(farthest-side) vai sobrecarregar o navegador.

.element{
  shape-outside: circle(30%);
  transition: shape-outside 1s;
  float: left;
}

.element:hover{
  shape-outside: circle(50%);
}
GIF de círculo animado

É possível conseguir efeitos mais interessantes ao animar formas polygon(), mas é importante observar que o polígono precisa ter o mesmo número de vértices entre os dois estados de animação. O navegador não pode interpolar se você adicionar ou remover vértices.

Um truque é adicionar a quantidade máxima de vértices necessários e posicioná-los agrupados no estado de animação em que você quer menos bordas percebidas na forma.

.element{
  /* four vertices (looks like rectangle) */
  shape-outside: polygon(0 0, 100% 0, 100% 100%, 0 100%);
  transition: shape-outside 1s;
}

.element:hover{
  /* four vertices, but second and third overlap (looks like triangle) */
  shape-outside: polygon(0 0, 100% 50%, 100% 50%, 0 100%);
}
GIF de triângulo animado

Como ajustar o conteúdo dentro de uma forma

Captura de tela da demonstração de Alice no País das Maravilhas usando formas CSS para ajustar o conteúdo

O rascunho inicial da especificação de formas CSS incluía uma propriedade shape-inside que permitia que o conteúdo fosse ajustado dentro de uma forma. Houve até implementações no Chrome e no Webkit por um tempo. No entanto, envolver conteúdo posicionado arbitrariamente em um caminho personalizado exige muito mais esforço e pesquisa para cobrir todos os cenários possíveis e evitar bugs. Por isso, a propriedade shape-inside foi adiada para o CSS Shapes Level 2, e as implementações dela foram retiradas.

No entanto, com um pouco de esforço e algumas concessões, ainda é possível conseguir o efeito de ajustar o conteúdo dentro de uma forma personalizada. O truque é usar dois elementos flutuantes com shape-outside, posicionados em lados opostos de um contêiner. A desvantagem é que você precisa usar um ou dois elementos vazios que não têm significado semântico, mas servem como suportes para criar a ilusão de uma forma interna.

<div>
  <div class='left-shape'></div>
  <div class='right-shape'></div>

  Lorem ipsum...
</div>

A posição dos elementos de suporte .left-shape e .right-shape na parte de cima do contêiner é importante porque eles vão flutuar para a esquerda e para a direita para flanquear o conteúdo.

.left-shape{
  shape-outside: polygon(0 0, ...);
  float: left;
  width: 50%;
  height: 100%;
}

.right-shape{
  shape-outside: polygon(50% 0, ...);
  float: right;
  width: 50%;
  height: 100%;
}
Ilustração da solução alternativa para shape-inside na demonstração da Alice

Esse estilo faz com que os dois struts flutuantes ocupem todo o espaço dentro do elemento, mas as propriedades shape-outside reservam espaço para o restante do conteúdo.

Se as formas CSS não forem compatíveis com o navegador, isso vai gerar efeitos desagradáveis, empurrando todo o conteúdo para baixo. Por isso, é importante usar o recurso de maneira progressivamente aprimorada.

Nos exemplos anteriores de animação de forma, você vai notar que o texto se movendo pode ser incômodo. Nem todos os casos de uso justificam uma forma animada. Mas você pode animar outras propriedades que interagem com as formas CSS para adicionar efeitos onde fizer sentido.

Na demonstração de formas CSS de Alice no País das Maravilhas, usamos a posição de rolagem para mudar a margem superior do conteúdo. O texto é comprimido entre dois elementos flutuantes. À medida que se move para baixo, ele precisa ser redefinido de acordo com o shape-outside dos dois elementos flutuantes. Isso dá a impressão de que o texto está entrando em um buraco sem fundo e contribui para a experiência de narrativa. Quase sem custo financeiro? Talvez. Mas é legal.

Como o layout do texto é feito de forma nativa pelo navegador, o desempenho é melhor do que usando uma solução em JavaScript. Mas mudar a margem superior na rolagem aciona muitos eventos de novo layout e pintura, o que pode reduzir o desempenho de forma perceptível. Use com cuidado! No entanto, usar formas CSS sem animação não afeta o desempenho de maneira perceptível.

Aprimoramento progressivo

Comece presumindo que o navegador não é compatível com as formas CSS e desenvolva com base nisso quando detectar o recurso. O Modernizr é uma boa solução para fazer a detecção de recursos, e há um teste para formas CSS na seção Non-core detects.

Alguns navegadores oferecem detecção de recursos em CSS pela regra @supports sem a necessidade de bibliotecas externas. O Google Chrome, que também é compatível com as formas CSS, entende a regra @supports. Veja como usá-lo para fazer um aprimoramento progressivo:

.element{
  /* styles for all browsers */
}

@supports (shape-outside: circle(50%)){
  /* styles only for browsers which support CSS Shapes */
  .element{
    shape-outside: circle(50%);
  }
}

Lea Verou escreveu mais sobre como usar a regra @supports do CSS.

Desambiguação de exclusões de CSS

O que hoje conhecemos como CSS Shapes costumava ser chamado de CSS Exclusions and Shapes nos primórdios da especificação. A mudança de nome pode parecer uma nuance, mas na verdade é muito importante. As exclusões de CSS, que agora são uma especificação separada, permitem que o conteúdo seja posicionado ao redor de elementos de forma arbitrária, sem a necessidade de uma propriedade float. Imagine envolver o conteúdo em torno de um elemento posicionado de forma absoluta. Esse é um caso de uso para exclusões de CSS. As formas CSS apenas definem o caminho em torno do qual o conteúdo será ajustado.

Portanto, formas e exclusões não são a mesma coisa, mas se complementam. As formas CSS estão disponíveis nos navegadores atualmente, mas as exclusões CSS ainda não foram implementadas com a interação de formas.

Ferramentas para trabalhar com formas CSS

É possível criar caminhos em ferramentas clássicas de criação de imagens, mas nenhuma delas, até o momento, exporta a sintaxe necessária para os valores de formas CSS. Mesmo que fizessem, trabalhar assim não seria muito prático.

As formas CSS são usadas no navegador, onde reagem a outros elementos na página. É muito útil visualizar os efeitos da edição da forma no conteúdo ao redor dela. Há algumas ferramentas para ajudar você com esse fluxo de trabalho:

Brackets: a extensão CSS Shapes Editor para Brackets usa o modo de visualização em tempo real do editor de código para sobrepor um editor interativo e editar valores de formas.

Google Chrome: a extensão CSS Shapes Editor para o Google Chrome amplia as Ferramentas para Desenvolvedores do navegador com controles para criar e editar formas. Ele coloca um editor interativo sobre o elemento selecionado.

O inspetor do Google Chrome tem suporte integrado para destacar formas. Passe o cursor sobre um elemento com uma propriedade shape-outside para que ele se acenda e ilustre a forma.

Formas de imagens: se você prefere gerar imagens e deixar que o navegador extraia as formas delas, Rebecca Hauck escreveu um bom tutorial para o Photoshop (em inglês).

Polyfill: o Google Chrome é o primeiro navegador importante a oferecer suporte a formas CSS. Em breve, o recurso será compatível com o iOS 8 e o Safari 8 da Apple. Outros fornecedores de navegadores podem considerar isso no futuro. Até lá, há um polyfill de formas CSS para fornecer suporte básico.

Conclusão

Em uma Web em que o conteúdo é, em grande parte, preso em caixas simples, as formas CSS oferecem uma maneira de criar um layout expressivo, preenchendo a lacuna de fidelidade entre o design da Web e o de impressão. É claro que as formas podem ser usadas em excesso e criar distrações. No entanto, quando aplicadas com bom gosto e discernimento, as formas podem melhorar a apresentação do conteúdo e focar a atenção do usuário de uma maneira única.

Deixo aqui uma coleção de trabalhos de outras pessoas, principalmente de impressão, que demonstra usos interessantes para layouts não retangulares. Espero que isso inspire você a testar as formas CSS e experimentar novas ideias de design.

Agradecemos a Pearl Chen, Alan Stearns e Zoltan Horvath por revisarem este artigo e fornecerem insights valiosos.