Primeiros passos com os formatos CSS

Delimitar o conteúdo em caminhos personalizados

Razvan Caliman
Razvan Caliman

Por muito tempo, 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 dos empreendimentos criativos em um layout não retangular acaba na frustração. Isso está prestes a mudar com o lançamento das formas CSS, disponíveis a partir do Chrome 37. Os Shapes 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 a partir de imagens.

Vamos conferir um exemplo muito simples.

Talvez você tenha sido tão simples quanto eu ao flutuar pela primeira vez uma imagem com partes transparentes esperando que o conteúdo encapsule e preencha as lacunas, mas se desapontou com a forma de borda retangular que persiste ao redor do elemento. Formas CSS podem ser usadas para resolver esse problema.

Como 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 o formato. 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 a forma da área ao redor do qual o conteúdo vai ser agrupado, sem o ponto flutuante, não é possível conferir os efeitos da forma.

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

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

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. Saiba mais sobre caixas de referência em breve.

A função round()

Ilustração do valor da forma round()

A notação completa para um valor de forma circular é 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 no eixo X e nos eixos Y. As coordenadas do centro do círculo são opcionais. Se forem omitidos, 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 será posicionado fora de um caminho circular. O argumento 50% especifica o raio do círculo, que, nesse caso específico, equivale à metade da largura ou altura do elemento. Alterar as dimensões do elemento influencia o raio da forma do círculo. Este é um exemplo básico de como os Shapes CSS podem ser responsivos.

Antes de continuar, um adendo: é importante lembrar que os Shapes 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á recortado pela forma. Para conseguir esse efeito, é necessário usar as propriedades de Máscara CSS: clip-path ou mask-image. A propriedade clip-path é muito útil porque segue a mesma notação de formas CSS. Portanto, é possível reutilizar valores.

Ilustração da forma `circle()` + trajetória de corte

As ilustrações ao longo deste documento usam recortes para destacar a forma e ajudar você a entender os efeitos.

De volta à forma de círculo.

Quando você usa 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 o 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 e assim por diante. Escolha aquele que seja flexível ou rígido o suficiente para suas necessidades.

Ajuste a posição do círculo definindo valores explícitos para as coordenadas do centro dele.

.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 introduzimos as caixas de referência.

Caixas de referência para formas CSS

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

Sistema de coordenadas para formas CSS

Não esqueça que o shape-outside muda a forma da área flutuante em torno da qual o conteúdo vai ser agrupado. A área flutuante se estende até as bordas externas do box definido pela propriedade margin. Isso é chamado de margin-box e é a caixa de referência padrão para uma forma se nenhuma for explicitamente mencionada.

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 para o 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 fica fora da área de conteúdo do elemento (100 px para cima e 100 px à esquerda), assim como o centro do círculo. O valor calculado do raio do círculo também cresce para considerar o aumento da superfície do sistema de coordenadas estabelecido pela caixa de referência margin-box.

Sistema de coordenadas da 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 deles implicam os limites. Explicamos anteriormente a "margin-box". A "border-box" é limitada pelas bordas externas do elemento, a "padding-box" é limitada pelo padding do elemento, enquanto a "content-box" é idêntica à área de superfície real usada pelo conteúdo dentro de um elemento.
Ilustração de todas as caixas de referência

Apenas uma caixa de referência pode ser usada por vez com uma declaração shape-outside. Cada caixa de referência influenciará a forma de uma maneira diferente e, às vezes, sutil. Há outro artigo que apresenta mais detalhes e ajuda você a entender as caixas de referência para formas CSS.

Função ellipse()

Ilustração do valor da forma ellipse()

As elipses parecem círculos. Eles são definidos como ellipse(rx ry at cx cy), em que rx e ry são os raios da elipse no eixo X e no eixo 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 cálculos engraçados. Você pode omitir as coordenadas para o centro da elipse e elas 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 longe dele, enquanto closest-side significa o oposto exato, usando 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 forma imprevisível, mas você quer que o formato 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().

Função polygon()

Ilustração do valor da forma polygon()

Se círculos e elipses forem muito limitantes, a função da forma do polígono abrirá um mundo de opções. O formato é polygon(x1 y1, x2 y2, ...), em que você especifica pares de coordenadas x 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 a considerar a "internidade" de um polígono no caso de caminhos que se cruzam ou formas fechadas. Joni Trythall explica muito bem como a propriedade da regra de preenchimento funciona no SVG. Se não for definido, o padrão de fill-rule será nonzero.

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

Função inset()

A função de forma inset() permite criar formas retangulares em torno das quais o conteúdo é unido. Isso pode parecer pouco intuitivo, considerando a premissa inicial de que o CSS Shapes liberta conteúdo da Web a partir de caixas simples. Pode ser. Ainda não encontrei um caso de uso para inset() que ainda não seja possível com pontos flutuantes 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 para uma função de forma inserida é inset(top right bottom left border-radius). Os quatro primeiros argumentos de posição são deslocamentos internos em relação às bordas do elemento. O último argumento é o raio da borda para a forma retangular. Ele é opcional, portanto, você pode deixá-lo de fora. Ela segue a notação abreviada border-radius, já usada 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 a partir de caixas de referência

Se você não especificar uma função de forma para a propriedade shape-outside, poderá permitir que o navegador deriva uma forma da caixa de referência do elemento. A caixa de referência padrão é margin-box. Nada exótico até agora, é assim que os flutuantes já funcionam. No entanto, com 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 conseguir o efeito de recorte, mas a área flutuante vai continuar retangular. shape-outside: border-box foi adicionado para contornar o contorno criado pela border-radius.

Extrair uma forma do raio da borda de um elemento usando a caixa de referência da caixa de borda
.element{
  border-radius: 50%;
  shape-outside: border-box;
  float: left;
}

É claro que você pode usar todas as caixas de referência dessa maneira. Veja outro uso para formas derivadas: aspas deslocadas.

Criar uma citação de deslocamento usando a caixa de referência da caixa de conteúdo

É possível obter o efeito de aspas de deslocamento usando apenas as propriedades de flutuação e margem. No entanto, isso exige que você posicione o elemento de aspas na árvore HTML no ponto em que quer que ele seja renderizado.

Veja como conseguir o mesmo efeito de aspas de compensação 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 nas aspas define o formato ao redor do qual o conteúdo externo será exibido. A propriedade margin-top é usada aqui para posicionar (deslocar) as aspas, independentemente da posição na árvore HTML.

Margem da forma

Você notará que envolver o conteúdo em torno de uma forma pode fazê-lo atrito muito perto 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ê costuma usar na propriedade margin normal, mas o shape-margin afeta apenas o espaço ao redor do valor de shape-outside. Essa função só adiciona espaçamento ao redor da forma se houver espaço para ela no sistema de coordenadas. É por isso que, no exemplo acima, o raio do círculo está definido como 40%, 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 é restrita à margin-box do elemento (o elemento e o margin ao redor). Se a forma for maior e estourar, ela será recortada ao margin-box e você terá uma forma retangular.

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

Como animar formas

Você pode 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 durante a leitura. Preste muita atenção à experiência se decidir a favor da animação de formas.

Você pode animar os raios e os centros para formas circle() e ellipse(), desde que eles sejam definidos em valores que o navegador pode interpolar. É possível ir de circle(30%) para circle(50%). No entanto, animar entre circle(closest-side) e circle(farthest-side) vai sufocar o navegador.

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

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

Efeitos mais interessantes podem ser conseguidos ao animar formas polygon(), com a observação importante de que o polígono precisa ter o mesmo número de vértices entre os dois estados de animação. O navegador não poderá interpolar se você adicionar ou remover vértices.

Um truque é adicionar a quantidade máxima de vértices necessária e posicioná-los agrupados no estado de animação, onde você quer que menos arestas 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 um triângulo animado

Como unir o conteúdo dentro de uma forma

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

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

No entanto, com um pouco de esforço e um pouco de comprometimento, ainda é possível conseguir o efeito de ajustar o conteúdo dentro de uma forma personalizada. A dica é usar dois elementos flutuantes com shape-outside, posicionados em lados opostos de um contêiner. O compromisso é que você precisa usar um ou dois elementos vazios que não têm significado semântico, mas que servem como as estruturas 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 .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 ao lado do 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 de uma solução alternativa para a forma interna da demonstração de Alice

Esse estilo faz com que as duas estruturas flutuantes ocupem todo o espaço dentro do elemento, mas as propriedades shape-outside liberam espaço para o restante do conteúdo.

Se o navegador não oferecer suporte a formas CSS, isso produz efeitos ruins ao empurrar todo o conteúdo para baixo. Por isso, é importante usar o recurso de forma progressiva.

Nos exemplos anteriores de animação de formas, a mudança do texto pode ser incômodo. Nem todos os casos de uso precisam de um formato animado. Mas você pode animar outras propriedades que interagem com formas de CSS para adicionar o efeito onde fizer sentido.

Na demonstração de Formas CSS em Alice no País das Maravilhas, usamos a posição de rolagem para alterar a margem superior do conteúdo. O texto é apertado entre dois elementos flutuantes. À medida que ele é movido para baixo, o layout precisa ser reformulado de acordo com o shape-outside dos dois elementos flutuantes. Isso dá a impressão de que o texto está entrando no buraco do coelho e contribui para a experiência da narrativa. Quase sem graça? Talvez. Mas parece legal.

Como o layout de texto é feito nativamente pelo navegador, o desempenho é melhor do que usar uma solução JavaScript. Mas mudar a margem superior na rolagem aciona muitos eventos de reordenação e pintura, e isso pode reduzir visivelmente o desempenho. Use com cuidado! No entanto, usar Shapes CSS sem animá-los não resulta em um impacto no desempenho perceptível.

Aprimoramento progressivo

Comece supondo que o navegador não tenha suporte a formas CSS e avance nisso quando você detectar o recurso. O Modernizr é uma boa solução para fazer a detecção de recursos. Há um teste para formas CSS na seção "Detecção não principal".

Alguns navegadores oferecem a detecção de recursos no CSS pela regra @supports sem a necessidade de bibliotecas externas. O Google Chrome, que também é compatível com Formas CSS, entende a regra @supports. É assim que você o usa para aprimorar progressivamente:

.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 das exclusões de CSS

O que conhecemos hoje como "Formas de CSS" era chamado de "Exclusões e Formas de CSS" no início das especificações. A mudança na nomenclatura pode parecer uma nuance, mas é muito importante. As exclusões CSS, agora uma especificação separada, permitem ajustar o conteúdo em torno de elementos posicionados arbitrariamente, sem a necessidade de uma propriedade flutuante. Imagine envolver o conteúdo em torno de um elemento absolutamente posicionado. Esse é um caso de uso para exclusões de CSS. As formas CSS apenas definem o caminho em que o conteúdo será ajustado.

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

Ferramentas para trabalhar com formas CSS

É possível criar caminhos em ferramentas de criação de imagens clássicas, mas nenhum deles, no momento deste artigo, exporta a sintaxe necessária para valores de formas em CSS. Mesmo que precisassem, trabalhar dessa forma não seria muito prático.

Formas CSS devem ser 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. Há algumas ferramentas para ajudar você nesse fluxo de trabalho:

Brackets: a extensão CSS Shapes Editor para colchetes 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 Google Chrome estende as Ferramentas para desenvolvedores do navegador com controles para criar e editar formas. Ele coloca um editor interativo em cima do elemento selecionado.

O inspetor do Google Chrome tem suporte integrado para destacar formas. Passe o cursor sobre um elemento com uma propriedade shape-outside, e ele será iluminado para ilustrar a forma.

Formas de imagens: se você preferir gerar imagens e fazer com que o navegador extraia formas delas, Rebecca Hauck escreveu um bom tutorial para o Photoshop.

Polyfill: o Google Chrome é o primeiro grande navegador a enviar Shapes CSS. Há suporte para esse recurso em breve no iOS 8 e no Safari 8 da Apple. Outros fornecedores de navegadores poderão considerar essa opção no futuro. Até lá, há um polyfill de formas CSS (link em inglês) para oferecer suporte básico.

Conclusão

Em uma Web onde o conteúdo fica preso em grande parte em caixas simples, o CSS Shapes oferece uma maneira de criar um layout expressivo, fazendo a ponte entre a fidelidade da Web e o design impresso. É claro que as formas podem ser abusadas e criar distrações. Mas, quando aplicadas com bom gosto e bom senso, as formas podem aprimorar a apresentação do conteúdo e focar a atenção do usuário de uma maneira única para ele.

Deixo a você uma coleção de trabalhos de outras pessoas, principalmente material impresso, que demonstra usos interessantes para o layout não rectagular. Espero que isso inspire você a experimentar as formas do CSS e novas ideias de design.

Agradecemos a Pearl Chen, Alan Stearns e Zoltan Horvath por revisar este artigo e fornecer informações valiosas.