CSS shapes

CSS shapes

Sabe quando você vai ler uma revista, e no meio do texto há uma imagem irregular, mas o texto elegantemente a contorna, respeitando sua forma? Ou quando aparece por exemplo, um gráfico de pizza, e dentro de cada fatia tem um texto que se adequa a forma dela?

Pois é, eu não faço a mínima ideia de como eles fazem isso. Só sei que agora podemos fazer o mesmo com CSS!

Se você ainda não entendeu nada, dê uma boa olhada na imagem a seguir.

Penhasco com texto

Não estou falando das colunas, isso é assunto para outro post ;) Perceba como o texto da coluna direita acompanha a forma do penhasco.

Antes de continuar

Para visualizar os exemplos deste post corretamente, você deve estar utilizando o navegador Google Chrome e é preciso habilitar uma flag dele primeiro. Na versão estável (atualmente 27) basta digitar chrome://flags na barra de endereços, e habilitar a opção Experimental WebKit features, como na imagem abaixo. Depois reinicie o navegador.

Experimental WebKit features

CSS shapes

Antes de falar das aplicações das CSS shapes, vamos primeiramente entender do que se tratam estas entidades.

Shapes são formas geométricas que definem contornos os quais conteúdos inline flutuam – ou seja, conteúdo textual e inicialmente imagens e elementos definidos com display: inline ou display: inline-block.

Shapes define arbitrary geometric contours around which inline content flows.

W3C Public Working Draft 20 June 2013

A Adobe e a Microsoft estão por trás desta especificação – CSS Shapes Module Level 1 – cujo trabalho ainda está em andamento, porém já podemos testar e brincar com várias coisas graças ao Google Chrome.

Formas básicas

Inicialmente, há 4 formas básicas definidas:

  • Retângulo: através das funções rectangle() e inset-rectangle();
  • Círculo: através da função circle();
  • Elípse: através da função ellipse();
  • Polígono: através da função polygon().

Vale observar que todos os valores usados como parâmetros destas funções podem ser absolutos (px, in, pt, cm, etc.) ou relativos (%, em, rem, ex, etc.)

rectangle()

/* rectangle(x, y, width, height, rx, ry) */
   rectangle(0, 0, 100px, 80px, 20%, 40%)
  • x e y: coordenadas do ponto inicial nos eixos X e Y (horizontal e vertical);
  • width e height: largura e altura;
  • rx e ry: raio dos cantos nas direções horizontal e vertical (para bordas arredondadas) (opcional).

inset-rectangle()

/* inset-rectangle(top, right, bottom, left, rx, ry) */
   inset-rectangle(10%, 20%, 40px, 20%, 8px, 8px)
  • top, right, bottom e left: Define o retângulo em relação ao seu elemento ancestral;
  • rx e ry: raio dos cantos nas direções horizontal e vertical (para bordas arredondadas) – opcionais.

circle()

/* circle(cx, cy, radius) */
   circle(50%, 50%, 80px)
  • cx e cy: coordenadas do ponto central nos eixos X e Y (horizontal e vertical);
  • radius: raio do círculo;

ellipse()

/* ellipse(cx, cy, rx, ry) */
   ellipse(50%, 50%, 80px, 200px)
  • cx e cy: coordenadas do ponto central nos eixos X e Y (horizontal e vertical);
  • rx e ry: raios nos eixos X e Y (horizontal e vertical).

polygon()

/* polygon(x1 y1, x2 y2, ..., xn yn) */
   polygon(10px 10px, 20px 10px, 20px 20px) /* um triângulo */
  • xn e yn: tuplas com as coordenadas dos pontos do polígono no eixos X e Y (horizontal e vertical). O polígono será fechado automaticamente ligando-se o primeiro ao último ponto da lista.

Aplicando as shapes

Agora que conhecemos os 4 tipos de formas básicas, podemos começar a utilizá-las através das propriedades shape-outside e shape-inside. Há também outras propriedades relacionadas às shapes, como shape-margin, shape-padding e shape-image-threshold. Vamos a elas.

shape-outside

Com shape-outside é possível definir o contorno externo de um elemento. Atualmente apenas funciona em elementos flutuantes (com float: left ou float: right).

Por exemplo, podemos ter uma <div> de tamanho 100x100px que flutua à esquerda de um texto, e ainda ter a forma de um círculo (com a propriedade border-radius):

<article>
    <div class="flutua"></div>
    Vou mostrando como sou e vou sendo como posso (...)
</article>

E .flutua definido no CSS abaixo, vamos ter algo parecido como isto:

.flutua
Vou mostrando como sou e vou sendo como posso. Jogando meu corpo no mundo, andando por todos os cantos. E pela lei natural dos encontros, eu deixo e recebo um tanto. E passo aos olhos nus ou vestidos de lunetas. Passado, presente, participo sendo o mistério do planeta. O tríplice mistério do stop, que eu passo por e sendo ele no que fica em cada um. No que sigo o meu caminho e no ar que fez e assistiu. Abra um parênteses, não esqueça que independente disso eu não passo de um malandro. De um moleque do Brasil, que peço e dou esmolas. Mas ando e penso sempre com mais de um, por isso ninguém vê minha sacola.
.flutua {
    width: 100px;
    height: 100px;
    float: left;
    border-radius: 50%;
}

Perceba que, apesar do elemento .flutua ter sua aparência circular, o seu layout é retangular. Todos os elementos HTML são assim – no final das contas, tudo é um monte de quadrado pro navegador –, com exceção aos elementos gráficos SVG.

Como já foi dito, a propriedade shape-outside permite definir um contorno externo de maneira um pouco diferente. Vamos definir um círculo do mesmo tamanho e localização que o ilustrado por .flutua, e veremos o que acontece.

shape-outside: circle()

.flutua
Vou mostrando como sou e vou sendo como posso. Jogando meu corpo no mundo, andando por todos os cantos. E pela lei natural dos encontros, eu deixo e recebo um tanto. E passo aos olhos nus ou vestidos de lunetas. Passado, presente, participo sendo o mistério do planeta. O tríplice mistério do stop, que eu passo por e sendo ele no que fica em cada um. No que sigo o meu caminho e no ar que fez e assistiu. Abra um parênteses, não esqueça que independente disso eu não passo de um malandro. De um moleque do Brasil, que peço e dou esmolas. Mas ando e penso sempre com mais de um, por isso ninguém vê minha sacola.
.flutua {
    /* ... */
    shape-outside: circle(50px, 50px, 50px);
}

Voilà! Agora o texto acompanha a forma definida! Como o tamanho de .flutua é 100x100px, seu ponto central é (50px, 50px), e seu raio também é 50px, de modo a preencher toda a largura e altura do elemento. Hora de brincar com outras formas:

shape-outside: ellipse()

.flutua
Vou mostrando como sou e vou sendo como posso. Jogando meu corpo no mundo, andando por todos os cantos. E pela lei natural dos encontros, eu deixo e recebo um tanto. E passo aos olhos nus ou vestidos de lunetas. Passado, presente, participo sendo o mistério do planeta. O tríplice mistério do stop, que eu passo por e sendo ele no que fica em cada um. No que sigo o meu caminho e no ar que fez e assistiu. Abra um parênteses, não esqueça que independente disso eu não passo de um malandro. De um moleque do Brasil, que peço e dou esmolas. Mas ando e penso sempre com mais de um, por isso ninguém vê minha sacola.
.flutua {
    width: 200px;
    height: 60px;
    /* ... */
    shape-outside: ellipse(50%, 50%, 50%, 50%);
}

shape-outside: polygon()

Vou mostrando como sou e vou sendo como posso. Jogando meu corpo no mundo, andando por todos os cantos. E pela lei natural dos encontros, eu deixo e recebo um tanto. E passo aos olhos nus ou vestidos de lunetas. Passado, presente, participo sendo o mistério do planeta. O tríplice mistério do stop, que eu passo por e sendo ele no que fica em cada um. No que sigo o meu caminho e no ar que fez e assistiu. Abra um parênteses, não esqueça que independente disso eu não passo de um malandro. De um moleque do Brasil, que peço e dou esmolas. Mas ando e penso sempre com mais de um, por isso ninguém vê minha sacola.
.flutua {
    width: 100px;
    height: 88px;
    /* um triângulo: */
    shape-outside: polygon(0 0, 100% 100%, 0 100%);
}

shape-margin

Esta propriedade é bem simples, apenas define uma margem para a shape definida (o resultado é o mesmo da propriedade margin em elementos comuns).

OBS.: Tentei testar esta propriedade no Chrome Stable 27 e no Chrome Canary 30, mas o resultado ainda não é o esperado nas implementações atuais. O exemplo abaixo é apenas uma representação do que deveria acontecer.

.flutua
Vou mostrando como sou e vou sendo como posso. Jogando meu corpo no mundo, andando por todos os cantos. E pela lei natural dos encontros, eu deixo e recebo um tanto. E passo aos olhos nus ou vestidos de lunetas. Passado, presente, participo sendo o mistério do planeta. O tríplice mistério do stop, que eu passo por e sendo ele no que fica em cada um. No que sigo o meu caminho e no ar que fez e assistiu. Abra um parênteses, não esqueça que independente disso eu não passo de um malandro. De um moleque do Brasil, que peço e dou esmolas. Mas ando e penso sempre com mais de um, por isso ninguém vê minha sacola.
.flutua {
    /* ... */
    shape-outside: circle(50%, 50%, 50%);
    shape-margin: 15px;
}

Contornando imagens

Uma ótima aplicação das CSS Shapes é contornar imagens. Da mesma maneira que fizemos há pouco, a diferença é que o elemento em questão é uma imagem – e imagens são quadradas como qualquer outro elemento.

Childhood living is easy to do
The things you wanted I bought them for you
Graceless lady, you know who I am
You know I can't let you slide through my hands
Wild horses couldn't drag me away
Wild, wild horses couldn't drag me away
I watched you suffer a dull aching pain
Now you decided to show me the same
No sweeping exits or offstage lines
.rolling-stones {
    float: left;
    shape-outside: polygon(0 0, 123px 0, 134px 36px, 155px 56px, 134px 78px, 109px 129px, 62px 164px, 0 164px);
}

Contornando imagens automaticamente

A especificação define também que deve ser possível realizar o contorno de uma imagem de maneira automática apenas passando a URL da imagem como valor da propriedade shape-outside.

O problema disso é que o navegador vai usar heurísticas e algoritmos para processamento de imagens que não são perfeitos. Os resultados podem ser bastante divergentes dependendo da imagem e da sua qualidade gráfica. Por isso, foi preciso deixar isso um pouco no controle do desenvolvedor, que pode utilizar-se da propriedade shape-image-threshold para controlar a aplicação da shape.

OBS.: Esta funcionalidade ainda não foi implementada em nenhum navegador até o momento.

.rolling-stones {
    float: left;
    shape-outside: url('rolling_stones.png');
    shape-image-threshold: 0.3;
}

shape-inside

Com shape-inside é possível realizar o oposto de shape-outside, ou seja, é possível definir limites internos ao elemento. Podemos dizer que uma shape definida em shape-inside vai “empacotar” o conteúdo do elemento em si. Dois exemplos:

Childhood living is easy to do. The things you wanted I bought them for you. Graceless lady, you know who I am. You know I can't let you slide through my hands. Wild horses couldn't drag me away. Wild, wild horses couldn't drag me away. I watched you suffer a dull aching pain. Now you decided to show me the same. No sweeping exits or offstage lines.
Childhood living is easy to do. The things you wanted I bought them for you. Graceless lady, you know who I am. You know I can't let you slide through my hands. Wild horses couldn't drag me away. Wild, wild horses couldn't drag me away. I watched you suffer a dull aching pain. Now you decided to show me the same. No sweeping exits or offstage lines.
.inside-circle {
    shape-inside: circle(50%, 50%, 50%);
}

.inside-hexagon {
    shape-inside: polygon(25% 0, 75% 0, 100% 50%, 75% 100%, 25% 100%, 0 50%);
}

shape-padding

Simplesmente aplica um padding interior à shape. Funcionando no Chrome.

Lindo, né? Mas…

Apesar de já estar implementada com prefixo no Google Chrome (-webkit-shape-inside), esta propriedade foi removida da Especificação Level 1, pois optou-se por apenas incluí-la em especificações futuras. O mesmo se aplica à propriedade shape-padding.

A future level of CSS Shapes will define a shape-inside property, which will define a shape to wrap content within the element.

W3C Public Working Draft 20 June 2013

Suporte

shape-outside25 *1
-webkit-
--------
shape-margin----------
shape-image-threshold----------
shape-inside25 *1*2
-webkit-
--------
shape-padding25 *1*2
-webkit-
--------
*1 – Features habilitadas através de uma flag
*2 – Propriedades removidas da spec por hora
#46