Pra quem trabalha com front end, sabe que a tarefa de criar folhas de estilos bem estruturadas e sem muitas repetições não é um trabalho simples. A tarefa fica mais difícil ainda quando precisamos criar diversos layouts onde cada página tem sua própria identidade visual, já que com o passar do tempo teremos mais e mais páginas.
Com a evolução do CSS veio algums linguagens que nos ajudam a escrever menos código e gerar mais conteúdo como o SCSS e o SASS, abordado nesse artigo.
Objetivo
Criar um esquema de template onde poderemos especificar as propriedades dinâmicas de forma fácil sem precisar reescrever todo o restante dos estilos.
Código base
Iremos trabalhar com 4 estruturas de código HTML identicas, porém cada uma dessas estruturas terá um stylesheet diferente. Cada estrutura contêm 3 itens: Github, Linkedin e Twitter que deverá ser estilizados utilizando as funções descrita acima de cada um deste:
<h2>none</h2>
<div class="none">
<div class="github">http://github.com/wbotelhos</div>
<div class="linkedin">http://linkedin.com/in/wbotelhos</div>
<div class="twitter">http://twitter.com/wbotelhos</div>
</div>
<h2>each + nth</h2>
<div class="each-nth">
<div class="github">http://github.com/wbotelhos</div>
<div class="linkedin">http://linkedin.com/in/wbotelhos</div>
<div class="twitter">http://twitter.com/wbotelhos</div>
</div>
<h2>each + nth + function</h2>
<div class="each-nth-function">
<div class="github">http://github.com/wbotelhos</div>
<div class="linkedin">http://linkedin.com/in/wbotelhos</div>
<div class="twitter">http://twitter.com/wbotelhos</div>
</div>
<h2>each + nth + function + index</h2>
<div class="each-nth-function-index">
<div class="github">http://github.com/wbotelhos</div>
<div class="linkedin">http://linkedin.com/in/wbotelhos</div>
<div class="twitter">http://twitter.com/wbotelhos</div>
</div>
Repare que para cada item temos descrito quais funções iremos utilizar para gerar o estilo.
Cada um desses items possuem propriedades específicas, assim como propriedades exclusivas. Iremos evoluir o primeiro estilo none, sendo que este já esta estilizado, veja:
.none
.github
background-color: #333
color: #FFF
height: 40px
padding-top: 20px
text-align: center
.linkedin
background-color: #1B86BC
color: #EEE
height: 40px
padding-top: 20px
text-align: center
.twitter
background-color: #00ABF0
color: #DDD
height: 40px
padding-top: 20px
text-align: center
O resultado deverá ser sempre o mesmo:
Análise
Podemos ver que há um padrão:
height
, padding-top
e text-align
são iguais para todos.
background-color
e color
cada um tem o seu.
Utilizando as funções each e nth
Vamos criar um outro arquivo each-nth.css.sass
para refatorarmos esse código utilizando principalmente as funções each
e nth
para nos ajudar.
Primeiramente iremos separar os valores utilizados em variáveis que sempre iniciam com um $
no início.
Variáveis
$default-height: 40px
$default-padding-top: 20px
$default-text-align: center
$github-background-color: #333
$github-color: #FFF
$linkedin-background-color: #1B86BC
$linkedin-color: #EEE
$twitter-background-color: #00ABF0
$twitter-color: #DDD
Até aqui nada demais. Criamos as propriedades default com o prefix default_
seguido do nome da propriedade e também criamos as cores do texto e do background para cada uma das redes sociais, então para o github temos o prefixo github_
seguido da propriedade e assim por diante.
Array de dados
Para declaramos um array é bem fácil, só se diferencia de todas as linguagens do mundo por não precisamors dos parenteses envolvendo os valores, então seria assim:
$variavel: item1, item2, item3
E da mesma forma podemos ter array de array com a diferença de que os arrays internos não são separados por vírgula, mas sim por espaço em branco, ficando algo como:
$variavel: item1-1 item1-2 item1-3, item2-1 item2-2 item2-3, item3-1 item3-2 item3-3
Mas por que isso é importante pra gente? Bem, se temos um array, também temos uma função para iterá-los, logo podemos colocar nesse array todos os nomes de classe que iremos utilizar em nosso css. E melhor ainda, podemos já declarar todas as propriedades que iremos utilizar para cada um das classes, veja:
$skins: github $github_background-color $github_color, linkedin $linkedin_background-color $linkedin_color, twitter $twitter_background-color $twitter_color
O nome dessa variável nossa será $skins
e cada item do array será um template. Em cada um desse template temos um outro array de dados onde a primeira posição é o nome do template, como github
, por exemplo, e as outras posições são as propriedades. Os arrays do SASS começam com o índice 1, logo temos:
$skins[1]: template do github $skins[2]: template do linkedin $skins[3]: template do twitter
$skins[x][1]: nome do template $skins[x][2]: cor do background $skins[x][3]: cor do texto
Ok, respira! SASS não é para fazer somente coisas básicas como usar seu neste> dy e remover chaves e ponto-e-vírgulas. A estrutura dá um trabalhinho, mas o resultado é ótima, aguarde! (:
Com tudo dentro da variável $skins
, vamos utilizar a função @each
para iterar os dados com a mesma sintáxe do Ruby, JavaScript e afins:
@each $skin in $skins
// utilizar a variável $skin aqui
A primeira coisa que queremos aqui é o nome de nossas classes, porém cada valor $skin
contêm um array de três itens, ou seja, para cada $skin
temos que pegar um posição específica. Para recuperarmos o índice de um array podemos utilizar a função nth(array, indice)
. Então para pegarmos o nome do nosso template que esta na posição 1 faríamos:
@each $skin in $skins
nth($skin, 1) // posição 1 do array da iteração == 'github'
Mas como queremos esse nome como um seletor class, temos que concatenar um ponto com o nome do template. Para fazer essa concatenação, temos que interpolar os dados, assim como utilizamos no Ruby:
@each $skin in $skins
.#{nth($skin, 1)} // .github, .linkedin, .twitter
Assim teremos:
.github {}
.linkedin {}
.twitter {}
Logo podemos já escrever as propriedades comuns a todos os template:
@each $skin in $skins
.#{nth($skin, 1)}
height: $default-height
padding-top: $default-padding-top
text-align: $default-text-align
Resultando em:
.github {
height: 40px
padding-top: 20px
text-align: center
}
.linkedin {
height: 40px
padding-top: 20px
text-align: center
}
.twitter {
height: 40px
padding-top: 20px
text-align: center
}
Cool! E as propriedades dinâmicas? Bem, elas estão nos arrays internos e assim como pegamos o nome do template na posição 1, podemos pegar a cor de background na posição 2 e a cor de texto na posição 3.
@each $skin in $skins
.#{nth($skin, 1)}
background-color: nth($skin, 2)
color: nth($skin, 3)
height: $default-height
padding-top: $default-padding-top
text-align: $default-text-align
Repare que não utilizamos interpolação nos outros itens, pois o valor não precisa de ser concatenado a nada, há um espaço entre os dois pontos e o valor.
Assim temos o nosso resultado final do primeiro refactor:
.github {
background-color: #333
color: #EEE
height: 40px
padding-top: 20px
text-align: center
}
.linkedin {
background-color: #1B86BC
color: #DDD
height: 40px
padding-top: 20px
text-align: center
}
.twitter {
background-color: #00ABF0
color: #CCC
height: 40px
padding-top: 20px
text-align: center
}
Código do refactor 1
$default-height: 40px
$default-padding-top: 20px
$default-text-align: center
$github-background-color: #333
$github-color: #FFF
$linkedin-background-color: #1B86BC
$linkedin-color: #EEE
$twitter-background-color: #00ABF0
$twitter-color: #DDD
$skins: github $github-background-color $github-color, linkedin $linkedin-background-color $linkedin-color, twitter $twitter-background-color $twitter-color
@each $skin in $skins
.#{nth($skin, 1)}
background-color: nth($skin, 2)
color: nth($skin, 3)
height: $default-height
padding-top: $default-padding-top
text-align: $default-text-align
Vantagens
- Não precisamos repetir o bloco de cada template (class), pois o
@each
já irá montar; - Não repetimos as propriedades estáticas, pois elas são referenciadas via variáveis;
- Ao mudar os valores estáticos, mudamos automáticamente para todos os templates;
- Para mudarmos os valores dinâmicos, basta alterar os valores das variáveis de cada template.
Desvantagens
- As declarações de variáveis são poluidas, temos o prefixo e o nome da propriedade para cada template;
- Para cada valor dinâmico adicionado temos que adicionar tal item no array interno das skins, onde se esquecermos um item ou adicionarmos um a mais em alguns desses arrays internos, teremos um erro de índice;
- A forma de escrita do array de array vai ficando cada vez mais complexo, pois deve ser escrito sempre em uma única linha;
- Para recuperarmos os valores dinâmicos somos obrigados a decorar sua posição e para quem esta lendo
nth($skin, 2)
não significa nada.
Utilizando as funções each, nth e function
Com a ajuda da função @function
podemos resolver a desvantagem 4, pois podemos isolar os valores dinâmicos em methodos com nomes de fácil relação. A forma de declarar uma função é semelhante ao do JavaScript, e como qualquer função podemos retornar valores utilizando @return
:
@function foo_bar($foo, $bar)
@return $foo
Um curiosidade é que o SASS não diferencia
_
de-
, então se você escrever sua variávelfoo-bar
você pode chamar porfoo_bar
que irá funcionar. Porém prefiro usar o padrão: métodos com underline e variáveis com hífen.
Agora podemos resolver o item 4 dando nomes para cada um dos valores dinâmicos ficando:
@function class_name_for($skin)
@return nth($skin, 1)
@function background_color_for($skin)
@return nth($skin, 2)
@function color-for($skin)
@return nth($skin, 3)
Agora ficou mais fácil, pois passamos a skin da iteração e recuperamos o valor:
@each $skin in $skins
.#{class_name_for($skin)}
background-color: background_color_for($skin)
color: color-for($skin)
height: $default-height
padding-top: $default-padding-top
text-align: $default-text-align
Veja, quem for dar manutenção não precisa saber qual a posição da cor do background, da cor do texto ou do nome do template, basta usar a função criada por quem deu manutenção no core do sistema de template.
Código do refactor 2
$default-height: 40px
$default-padding-top: 20px
$default-text-align: center
$github-background-color: #333
$github-color: #EEE
$linkedin-background-color: #1B86BC
$linkedin-color: #DDD
$twitter-background-color: #00ABF0
$twitter-color: #CCC
$skins: github $github-background-color $github-color, linkedin $linkedin-background-color $linkedin-color, twitter $twitter-background-color $twitter-color
@function class_name_for($skin)
@return nth($skin, 1)
@function background_color_for($skin)
@return nth($skin, 2)
@function color-for($skin)
@return nth($skin, 3)
@each $skin in $skins
.#{class_name_for($skin)}
background-color: background_color_for($skin)
color: color-for($skin)
height: $default-height
padding-top: $default-padding-top
text-align: $default-text-align
Vantagens
- Não precisamos repetir o bloco de cada template (class), pois o
@each
já irá montar; - Não repetimos as propriedades estáticas, pois elas são referenciadas via variáveis;
- Ao mudar os valores estáticos, mudamos automáticamente para todos os templates;
- Para mudarmos os valores dinâmicos, basta alterar os valores das variáveis de cada template;
- Não precisamos decorar o índice de cada propriedade sempre, pois durante a manutenção do core do template já criamos uma função para abstrair isso.
Desvantagens
- As declarações de variáveis são poluidas, temos o prefixo e o nome da propriedade para cada template;
- Para cada valor dinâmico adicionado temos que adicionar tal item no array interno das skins, onde se esquecermos um item ou adicionarmos um a mais em alguns desses arrays internos, teremos um erro de índice;
- A forma de escrita do array de array vai ficando cada vez mais complexo, pois deve ser escrito sempre em uma única linha;
- Ainda sim o desenvolvedor do core deve saber a posição de cada uma das variáveis durante a manutenção.
Utilizando as funções each, nth, function e index
Se você reparar bem, até então estamos declarando as variáveis para serem jogadas em um array de array. Esse array de array pode ser facilmente traduzido por uma matriz:
$skins: github linkedin twitter
$background-colors: #333 #1B86BC #00ABF0
$colors: #EEE #DDD #CCC
Menos código, mais legível. Veja que ficamos com os arrays internos separados por variáveis basicamente. Resolvemos o problema 1, pois não repetimos as declarações prefixadas. Resolvemos o problema 2, pois fica bem claro as posições dos valores por ser uma matriz, um a mais ou um a menos será facilmente visível. Resolve o problema 3, pois a proporção de crescimento é pequena e cresce para dois lados (horizontal e vertical), não mais somente para um.
Tanto faz separamos por vírgula ou por espaço os itens do array.
Agora nossa iteração $skin in $skins
irá retorna apenas o nome da skin (github, linkedin e twitter) que por sua vez corresponde a posição (1, 2 e 3). Então se quisermos a color
do linkedin (2) teremos #DDD
, certo?
Com a função index(array, value)
conseguimos recuperar qual a posição de determinado valor em um array e é com essa função que iremos pegar os valores correspondentes para cada skin. Desta forma resolvemos o problema número
@function index_for($skin)
@return index($skins, $skin) // index(github linkedin twitter, linkedin) == 2
A função acima servirá como um ponteiro para pegarmos os valores na matriz, podemos usá-la da seguinte forma:
@function background_color_for($skin)
@return nth($background-colors, index_for($skin)) // nth(#333 #1B86BC #00ABF0, 2) == #1B86BC
Como temos o número da coluna a buscar os dados graças ao método index_for()
, basta dizermos em qual linha queremos pegar o valor, neste caso na linha das cores de fundo. Se quisermos pegar a cor, basta fazer a mesma coisa para tal linha:
@function color_for($skin)
@return nth($colors, index_for($skin))
E por fim utilizar tais funções sem se preocupar com índices:
@each $skin in $skins
.#{$skin}
background-color: background_color_for($skin)
color: color_for($skin)
height: $default-height
padding-top: $default-padding-top
text-align: $default-text-align
Agora escrevemos o que desejamos: Quero um “background color para a skin linkedin”, resolvendo o problema número 4.
Código do refactor 3
$default-height: 40px
$default-padding-top: 20px
$default-text-align: center
$skins: github linkedin twitter
$background-colors: #333 #1B86BC #00ABF0
$colors: #EEE #DDD #CCC
@function index_for($skin)
// returns the index of the passed name of item on matrix
@return index($skins, $skin)
@function background_color_for($skin)
@return nth($background-colors, index_for($skin))
@function color_for($skin)
@return nth($colors, index_for($skin))
@each $skin in $skins
.#{$skin}
background-color: background_color_for($skin)
color: color_for($skin)
height: $default-height
padding-top: $default-padding-top
text-align: $default-text-align
Vantagens
- Não precisamos repetir o bloco de cada template (class), pois o
@each
já irá montar; - Não repetimos as propriedades estáticas, pois elas são referenciadas via variáveis;
- Ao mudar os valores estáticos, mudamos automáticamente para todos os templates;
- Para mudarmos os valores dinâmicos, basta alterar os valores na matriz que é bem fácil de visualizar;
- Não precisamos decorar o índice de cada propriedade, isso já é feito automáticamente;
- Com uma mesma função conseguimos recuperar diferentes valores dependendo da skin passada.
Desvantagens
- Conhece alguma? Comente e deixe a sua sugestão.
Lembre-se que criar um “core” do SASS é mais complicado, porém deve ser flexível o suficiente para abstrair operações para o restante da equipe utilizar.
Na experiência obtida até hoje utilizando SASS em um equipe rasoavelmente grande é que se você não seguir um padrão, terá muitos problemas e acabará caindo no mito do código mágico (extra) que o SASS gera. Criar uma estrutura forte e seguir um padrão como o do SMACSS é essencial.
Veja esse projeto no Github: https://github.com/wbotelhos/criando-templates-sass-com-function-nth-e-index