Sviluppo frontend
Definire convenzioni ed un lessico condiviso per operazioni e pattern d'uso comune nello sviluppo frontend è fondamentale per facilitare uno scambio di idee più fluido tra sviluppatori, semplificando l'introduzione di nuovi sviluppatori e/o passaggi di consegne.
La struttura consigliata per un nuovo progetto Sass in partenza è la seguente:
app/assets/stylesheets/ ├── blocks │ ├── _block-name.css.sass │ └── ... ├── formatting │ ├── _formatting-category.css.sass │ └── ... ├── mixins │ ├── _mixin-name.css.sass │ └── ... ├── variables │ ├── _variables-category.css.sass │ └── ... ├── _base.css.sass ├── _shame.css.sass └── application.css.sass
Rootfile
Il Rootfile, application.css.sass
, si occupa esclusivamente di includere ordinatamente i partial:
@import "variables/**/*" @import "mixins/**/*" @import "base" @import "formatting/**/*" @import "blocks/**/*" @import "shame"
Per facilitare il mantenimento del file, nel caso di progetto Rails 3.1+, è possibile sfruttare il file globbing nella direttiva @import
.
Shamefile
Inserisci quick-fix, hack o tecniche “discutibili” solo all'interno del file_shame.css.sass
con l'obiettivo di:
- Renderli più semplici da identificare, isolare e risolvere in un secondo momento
- Mantenere il codebase “principale” pulito
- Rendere esplicito lo stato di salute dei fogli di stile
Documenta gli hack presenti nel file, specificando:
- A quale parte del codebase appartiene
- Perchè è stato necessario inserirlo
- Come si potrebbe fixare, se ci fosse più tempo
Basefile
Il partial _base.css.sass
contiene gli stili di default applicati ad ogni pagina. Tipicamente contiene una direttiva @import
che include un CSS reset e soli tag selectors.
E' importante fare in modo di aggiornare nel tempo il Basefile, con l'obiettivo di ridurre al minimo l'override in cascata all'interno di blocchi e stili di formattazione.
BEM (Block-Element-Modifier)
Il BEM è una convenzione di naming codificata da Yandex che ha come obiettivo principale quello di garantire una maggiore trasparenza e chiarezza di intenti.
Ad esclusione delle poche regole specificate nei fogli di stile già descritti, la stragrande maggioranza del codebase segue una notazione BEM, e risiede all'interno della directory blocks
.
La convenzione BEM è la seguente:
.block .block__element .block--modifier
Dove:
.block
rappresenta un nuovo componente/astrazione. Ogni blocco viene specificato in un partial Sass omonimo (es. <code>blocks/_block.css.sass)..block__element
rappresenta un elemento figlio di <code>.block, che aiuta a definirlo nel suo complesso..block--modifier
rappresenta una differente versione di <code>.block (in termini OOP, una sottoclasse).
Dato che i nomi delle classi dei sotto-elementi di un blocco sono prefissati univocamente dal nome del blocco stesso:
- in qualsiasi momento è possibile analizzare l'HTML e capire a quale blocco appartiene un dato elemento;
- si elimina completamente name-clashing involontario tra sotto-elementi di componenti differenti;
- a livello Sass/CSS non è necessario utilizzare descendant selectors o parent selectors (es.
.block .element
o <code>.block > .element), ottenendo quindi come side-effect positivo fogli di stile più performanti;
Ogni blocco ha il compito di specificare il layout dei propri sotto elementi, ed è possibile inserire blocchi all'interno di altri blocchi (nesting).
Stati
A differenza dei block-modifiers, gli stati rappresentano cambiamenti di stato dinamici/programmatici all'interno del layout della pagina (es. Javascript).
Per facilitarne l'identificazione a livello HTML, i nomi degli stati vengono prefissati con is-
:
.is-active .is-collapsed .is-hidden
E' possibile introdurre stati sia a livello di blocco, che di sotto-elemento di blocco.
Layout-blocks
Per favorire il riutilizzo di blocchi già esistenti nel codebase, particolare attenzione dev'essere posta nel non specificare dimensioni esplicite, margini o posizionamenti relativi a livello root del blocco: ogni blocco deve simulare il comportamento di default di un tag div
, dunque essere in <code>position: staticed espandersi a coprire il 100% della larghezza dell'elemento padre, senza margini.
Per ovviare alla necessità di spaziare tra di loro i differenti blocchi presenti all'interno di una pagina, è possibile ricorrere al nesting di blocchi; un blocco il cui scopo è unicamente quello di posizionare spazialmente i sotto-blocchi che contiene prende il nome di layout block. Esempi tipici di layout blocks si hanno in blocchi che organizzano i propri sotto-elementi in griglie o in colonne main-content/sidebar.
Per facilitare l'identificazione di layout blocks all'interno dell'HTML, i nomi vengono prefissati con l-
:
.l-grid .l-two-cols .l-spacing
Ma il BEM esteticamente è brutto!
Ad oggi sfortunatamente il CSS non ci consente molta scelta in termini di caratteri non alfanumerici da poter utilizzare all'interno delle classi come “delimitatori”. La ragione dei doppi trattini ed underscores sta nella possibilità di poter continuare ad utilizzare lo spinal-case classico per dare un nome a blocchi, elementi e modificatori:
.site-search .site-search__sub-element
In definitiva sì, la notazione BEM è più verbosa e al primo sguardo può sembrare “strana”, ma i vantaggi in termini di chiarezza ed espressività del codice risultante superano di molto possibili critiche sull'aspetto estetico.
Quando termina un blocco e comincia un nuovo sotto-blocco?
La scelta è soggettiva e dipendente dal contesto. L'obiettivo è il maggior riutilizzo possibile di codice e regole già presenti nel codebase, evitando over-engineering e flessibilità non necessaria.
Un flusso di lavoro ottimale è separare un sotto-elemento da un blocco, trasformandolo in blocco a se' stante, solo quando un nuovo caso concreto lo rende effettivamente necessario.
Quando utilizzare child+tag selectors all'interno di blocchi
Supponiamo di voler realizzare un layout block che permetta di rendere floattanti i suoi sotto-blocchi:
<ul class="l-floating"> <li class="l-floating__item">...</li> <li class="l-floating__item">...</li> <ul> La convenzione BEM ci costringe a dover aggiungere una classe esplicita (`.l-floating__item`) ad ogni sotto- elemento. Confrontiamo questa scelta con un possibile selettore non-BEM: .l-floating > li
Sebbene meno verboso a livello di HTML che richiede, il secondo selettore non permette di intuire istantaneamente per ogni <li>
il blocco che ha la responsabilità di stilizzarlo:
<section class="another-block"> ... <ul class="l-floating"> <li>...</li> <li>...</li> <ul> ... </section>
Rende inoltre meno riutilizzabile lo stile, non consentendone la sua applicazione ad esempio in caso di cambio di tag dettato da motivi non prettamente estetici (semantica, SEO, accessibilità, etc):
<div class="l-floating"> <div>...</div> <div>...</div> <ul>
Presupposto del BEM è un separation of concerns netto tra estetica e semantica in grado di garantire il massimo riutilizzo di blocchi già presenti, senza richiedere modifiche a codice già presente nel codebase.
Si sconsiglia dunque l'uso di child+tag selectors all'interno di blocchi quando non si può escludere un cambiamento di tag nel futuro (es. heading tags).
E' vietato invece l'utilizzo di descendant selectors, in quanto troppo generici e suscettibili a carpet-bombing.
Block modifiers ed @extend
In alternativa allo stile BEM classico di applicazione di block-modifiers:
<div class="block block--modifier"></div>
E' possibile sfruttare la direttiva Sass @extend
:
.block--modifier @extend .block ...
Riducendo il classitis HTML:
<div class="block--modifier"> </div>
BEMO
Sulla base delle convenzioni BEM, abbiamo realizzato una libreria di blocchi che ci permette di iniziare lo stile di un sito senza alcuna velleità grafica. BEMO fornisce variabili, blocchi di stile come bottoni, blocchi di layout come wrapper e griglia.
http://cantierecreativo.github.io/bemo/
BEMO non è una libreria aggiornabile come vuole essere bootstrap; la si importa ad inizio progetto e la si modifica sulla base delle esigenze. È solo un acceleratore nella fase iniziale che ci permette di avere delle basi comuni condivise.
Class css per Javascript
Cerchiamo per quanto possibile di evitare l'uso di classi CSS per gestire elementi tramite Javascript. A volte risulta necessario. Si definisce quindi una convenzione per cui usiamo solo nomi di classi che siano ben riconoscibili attraverso un prefisso .js- e separare in modo netto ciò che definisce la presentazione di un elemento dalla manipolazione js di un elemento.
In questo modo uno designer frontend sa che non deve toccare o stilare la classe che inizia con .js- mentre uno sviluppatore javascript sa che può rinominare e manipolare quelle classi senza rompere niente dal unto di vista dello stile.
È fortemente consigliato usare più classi con nomi simili piuttosto che una classe sola che abbia usi ambigui:
.slider.js-slider
.slider__prev.js-slider__prev
.slider__next.js-slider__next
.slider__item
img src="#"
Qui sappiamo che la classe .slider servirà al grafico a gestire la presentazione dell'oggetto, mentre le classi .js-slider e .js-slider__next serviranno al programmatore per fare in modo che il plugin funzioni.
In questo modo si otterranno vantaggi nella manutenzione e nella chiarezza della gestione, a discapito della pulizia dell'html. Nel tempo, questo non è mai risultato un problema.
Best practices CSS
- Definisci tutte le classi CSS in
spinal-case
. → - Definisci i colori con sintassi esadecimale (
#fff
) ed <code>rbga() - Limita l'utilizzo del modificatore
!important
a selettori di stato - Non utilizzare ID selectors (
#header
) - Non definire sotto-elementi di modulo con selector-depth superiore a 3.
- Evita un depth of applicability superiore a 3. →
- Sfrutta i
rem
con fallback a <code>px per il font-sizing e il vertical-spacing - Favorisci il riutilizzo producendo una pattern library HTML che mostri i diversi moduli e layout esistenti, ed un loro utilizzo pratico. →
Best practices Sass
- Usa Sass, non Scss
- Prediligi Bourbon a Compass
- Definisci mixin e variabili in
spinal-case
. → - Limita il numero di possibili varianti di colore, font-family, font-size, spaziatura verticale tra i blocchi e breakpoint all'interno del sito, parametrizzandole all'interno di appositi file di variabili (
_colors.css.sass
,_font-families.css.sass
, etc.) - Produci dei mixin che rinforzino le convenzioni in uso all'interno del codebase (es. per l'utilizzo dei
rem
o per applicare una tra le possibili varianti di font-size) - Definisci i colori con nomi semantici (
$primary-color
invece che$red-color
) - Quando il numero di blocchi presenti all'interno del codebase aumenta, procedi splittando i blocchi in namespaces, spostandoli in sotto-directory (es.
blocks/blog
,blocks/homepage
, etc.) - Non definire mixin senza parametri (sfrutta la direttiva
@extend
) - Limita il numero di sotto-elementi per ogni blocco, per facilitare il riutilizzo autonomo di parti del blocco.
- Prediligi l'uso di classi di primo livello anche per gli elementi.
- Specifica direttive
@media
al termine della definizione del blocco, ed evita override dei selettori: specifica fuori dalle direttive@media
solo le regole condivise a tutti i breakpoint