No. 224

Titolo originale: Axiomatic CSS and Lobotomized Owls

Pubblicato in: CSS, HTML

Scritto da Heydon Pickering

Heydon Pickering, CSS, HTML

Lo scorso Giugno, al CSS Day ho presentato, con un po' di trepidazione, un peculiare selettore CSS composto da tre caratteri. Detto “lobotomized owl selector” (selettore gufo lobotomizzato, ndr) per la sua somiglianza con lo sguardo vacuo di un gufo, si è dimostrata essere la sezione più popolare del mio talk.

Non so dirvi se il pubblico applaudiva il ragionamento dietro all'invenzione o se, al contrario, stavano ridendo nervosamente della mia audacia nell'includere un costrutto così strano e apparentemente inutile. Forse stavo inconsapevolmente parlando a un pubblico pagato di supporter di un santuario per gufi. Non lo so.

Il lobotomized owl selector ha questo aspetto:

* + *

Nonostante il suo nome irriverente e la sua forma precaria, il lobotomized owl selector non è un semplice esperimento per me. È il risultato di sperimentazioni continue nell'automazione del layout del flow content. L'owl selector è un selettore “assiomatico” con una portata vorace. Come tale, molti esiteranno ad usarlo e terrorizzerà qualcuno sapere che lo includo nel production code. Voglio dimostrare in che modo il selettore può ridurre gli eccessi, velocizzare lo sviluppo e aiutare ad automatizzare il contenuto dinamico arbitrario.

Stili da prescrizione

In maniera quasi universale, i web interface designer professionisti (ingegneri o qualunque cosa siano) si sono abituati a dare stili agli elementi HTML in maniera prescrittiva. Concepiamo un oggetto dell'interfaccia, poi creiamo gli stili per quell'oggetto che vengono scritti manualmente nel markup come “hook”.

Nonostante appartenga solo alla presentazione, non all'interoperabilità semantica, il selettore di class è ciò a cui ricorriamo più frequentemente. Mentre gli elementi e la maggior parte degli attributi sono predeterminati e standardizzati, le class sono dei segnaposti che ci regalano la libertà della authorship. Le class ci danno controllo.

.my-module {
	/* ... */
}

I framework CSS sono sostanzialmente delle librerie di codice non standard basate sulle class, intese a formare relazioni esplicite tra gli stili e i loro elementi. Sono celebrati per la loro abilità di aiutare i designer a produrre interfacce attraenti in maniera rapida e sono criticati per gli inevitabili limiti di accessibilità che risultano quando si parte dallo stile (forma) piuttosto che dal contenuto (funzione).

< !-- An unfocusable, semantically inaccurate "button" -->
<a class="ui-button">press me</a>

Sia che usiate un framework o la vostra metodologia, il modo di assegnare stili prescrittivo rende impossibile la creazione del contenuto agli editor non tecnici in quanto richiede non solo la conoscenza del markup presentazionale, ma anche l'accesso a quel markup per codificare gli stili prescritti. Agli editor WYSIWYG e ai tool come Markdown manca necessariamente questa complessità così che l'assegnamento degli stili non impedisca il processo editoriale.

Bloat

Indipendentemente se siate in grado o meno di creare e mantenere il markup presentational, la questione se dobbiate rimane. Aggiungere codice presentational al vostro markup precedentemente intonso, lo congestiona necessariamente, ma qual è il compromesso? Ci permette di ridurre l'eccesso e la congestione nel foglio di stile?

Scegliendo di assegnare stili interamente in termini di elementi identificati con nomi, facciamo l'errore di asserire che gli elementi HTML esistono nel vuoto, non soggetti all'ereditarietà o alla condivisione. Trattando l'elemento come “questa cosa a cui devono essere assegnare stili”, siamo responsabili dell'impostazione di alcuni valori per l'elemento in questione che avrebbero già dovuto essere definiti più in alto nel “cascade”. Aggiungere nuovi moduli a un progetto favorisce la congestione, che è una cosa difficile da tenere sotto controllo.

.module-new {
	/* So… what’s actually new here? */
}

Dai pre-processori con la loro aggiunta di variabili a metodologie CSS basate sugli oggetti e la loro applicazione di classi “oggetto” riutilizzabili, siamo alle prese con i sacchi di sabbia per arginare questa marea di bloat. È l'ossessione del nostro settore. Comunque, pochi rimedi respingono la filosofia prescrittiva che richiama il bloat per prima cosa. Alcune interpretazioni del CSS object-oriented insistono anche nella gerarchia appiattita di stili, citando la specificità come problema da superare - riducendo in realtà il CSS a SS e negando una delle sue feature chiave.

Non sto scrivendo per condannare completamente questi approcci e tecnologie, ma ci sono altri metodi che possono essere più efficaci sotto certe condizioni. Tenetevi stretto il cappello!

Performance del selettore

Sono felice di ammettere che quando qualcuno tra voi ha visto i due asterischi in * + * all'inizio dell'articolo, avrà scosso la testa con vigorosa disapprovazione. C'è un precedente per questo. Il selettore universale è sicuramente uno strumento potente, ma può essere potente in maniera positiva, non solo negativa. Prima di addentrarci in questo, però, voglio parlare della questione della performance percepita.

Tutti gli studi che ho letto, inclusi quelli di Steve Souders e di Ben Frain, hanno concluso che la performance comparativa di diversi tipi di selettore CSS è insignificante. In effetti, Frain conclude che “impazzire sui selettori usati nei browser moderni è futile”. Non ho ancora letto alcuna prova convincente per smentire questi risultati.

Secondo Frain è al contrario, la quantità di selettori CSS, il bloat, che può causare problemi: egli cita nello specifico le dichiarazioni non utilizzate. In altre parole, adottare i selettori di class per la loro “velocità” è di poca utilità quando la loro proliferazione è in realtà la causa dei reali problemi di performance. Beh, quello e i JPEG giganti e i web font di cui non sono stati selezionati solo alcuni sottoinsiemi.

Al contrario, il controllo simultaneo da parte del selettore * di più elementi accresce la brevità, aiutando a ridurre la dimensione del file e a migliorare la performance.

Il vero problema con il selettore universale è che da solo non rappresenta un assioma molto convincente, nulla di più intelligente di “assegna uno stile a qualsiasi cosa”. Il trucco sta nell'imbrigliare questo selettore di base e formare espressioni più complesse che siano consapevoli del contesto.

Distribuire i margini

Il problema del confinare gli stili agli oggetti è che non tutto dovrebbe essere considerato come proprietà di un oggetto di per sé. Prendiamo in considerazione i margini. Questi sono un qualcosa che esiste tra gli elementi. Assegnare semplicemente un margin top ad un elemento non ha senso, indipendentemente dalla frequenza con cui lo fate. È come applicare la colla a un lato di un oggetto prima di aver determinato se volete che davvero si appiccichi a qualcosa o che cosa sarà quel qualcosa.

.module-new {
	margin-bottom: 3em; /* what, all the time? */
}

Ciò di cui abbiamo bisogno è un'espressione (un selettore) che colleghi solo gli elementi a cui serve un margin, ossia, solo gli elementi in relazione contestuale con altri elementi di pari livello (sibling elements). L'adjacent sibling combinator fa proprio questo: usando la forma x + n, possiamo aggiungere un margin top a qualunque n che sia preceduto da x.

Questo diventerebbe verboso molto rapidamente, come con lo styling prescrittivo standard, se dovessimo creare regole per ogni singolo elemento che si accoppia all'interno dell'interfaccia. Pertanto, adottiamo il sopracitato selettore universale, creando le nostre facce da gufo. L'assioma è il seguente: “Tutti gli elementi nel flusso del documento che precedono altri elementi devono ricevere un margin top di una linea.”

* + * {
	margin-top: 1.5em;
}

Completezza

Supponendo che il font-size dei paragrafi sia 1 em e la sua line-height sia 1.5, impostiamo semplicemente un margine di una riga tra tutti gli elementi successivi nel flusso, di qualunque tipo siano e in qualunque ordine. Né noi sviluppatori né le persone che creeranno il contenuto per il progetto dovranno preoccuparsi di dimenticare un qualsiasi elemento e di non adottare almeno un margine standard quando verranno visualizzati uno dopo l'altro. Per ottenere questo nel modo prescrittivo, dovremmo anticipare specifici elementi e dare loro valori individuali per il margin. Noioso, verboso e soggetto ad incompletezza.

Invece di scrivere stili, abbiamo creato un assioma di stile: un principio generale per il layout del flow content. Si può inoltre mantenere molto bene: se si cambia la line-height, basta cambiare semplicemente un singolo valore di margin-top per abbinarlo.

Consapevolezza del contesto

Comunque, è meglio di così. Applicando solo il margin tra gli elementi, non generiamo altro margin ridondante (colla esposta) destinato a combinarsi con il padding degli elementi padre. Confrontate la soluzione (a), che aggiunge un margin top a tutti gli elementi, con la soluzione (b), che usa l'owl selector.

Diagramma che mostra gli elementi con i margini, con e senza l'owl selector.

I diagrammi nella colonna di sinistra mostrano il margin in grigio scuro e i padding in grigio chiaro.

Ora, considerate come questo si comporta nei confronti dell'annidamento. Come mostrato, usando il selettore gufo e il solo valore di margin-top, nessun primo o ultimo elemento di un insieme presenterà mai un margine ridondante. Ogni volta che si crea un sottoinsieme di questi elementi, inserendoli in un genitore annidato, si applicheranno al sottoinsieme le stesse regole del super-insieme. Nessun margin, indipendentemente dal livello di annidamento, incontrerà mai il padding. Con una specie di eleganza algoritmica, ci proteggiamo dal "compound whitespace" in tutta la nostra interfaccia.

Diagramma che mostra gli elementi annidati con i margini usando l'owl selector.

Questo è considerevolmente meno verboso e più robusto dell'approcciare il problema senza gli assiomi e rimuovendo la colla rimasta dopo il fatto, come ha proposto Chris Coyier in maniera riluttante in “Spacing The Bottom of Modules”. Devo sottolineare che è stato questo articolo che ha contribuito a farmi elaborare l'idea del gufo lobotomizzato.

.module > *:last-child,
.module > *:last-child > *:last-child,
.module > *:last-child > *:last-child > *:last-child {
	margin: 0;
}

Notate che questo funziona solo se si è definito un contesto di “module” (una grande richiesta da parte di un content editor) e richiede la stima dei possibili livelli di annidamento: in questo caso ne supporta tre.

Design exception-driven

Finora, non abbiamo dato nomi ad alcun elemento, abbiamo semplicemente scritto una regola. Adesso possiamo trarre vantaggio dalla bassa specificità del selettore gufo e cominciare a costruire delle espressioni in maniera giudiziosa, sfruttando il cascade piuttosto che condannarlo come fanno altri metodi.

Paragrafi giustificati in stile libro

p {
	text-align: justify;
}

p + p {
margin-top: 0;
text-indent: 2em;
}

Notate che vengono indentati solo i paragrafi successivi, come da convenzione. Un'altra vittoria per l'adjacent sibling combinator.

Moduli compatti

.compact * + * {
	margin-top: 0.75em;
}

Potete utilizzare un pochino di orientamento agli oggetti basato sui nomi delle classi se volete, per creare uno stile riutilizzabile per moduli più compatti. In questo esempio, tutti gli elementi che hanno bisogno di un margine lo ricevono solo di metà riga.

Widget con il posizionamento

.margins-off > * {
	margin-top: 0;
}

L'owl selector è un selettore significativo e influenzerà widget come le mappe, in cui tutto è posizionato esattamente. Questo è un semplice interruttore di spegnimento. Sempre più spesso, widget di questo tipo occorreranno come web component in cui il nostro algoritmo per il margin non verrà comunque ereditato. Questo grazie alla feature di incapsulamento dello stile di Shadow DOM.

La bellezza degli em

Sebbene alcune eccezioni siano inevitabili, imbrigliando l'unità em nel nostro valore di margin, i margini si sistemano già automaticamente in base a un'altra proprietà: font-size. In qualunque istanza in cui cambiamo il font-size, il margin si adatterà ad esso: gli spazi di una riga rimarranno spazi di una riga. Questo è particolarmente di aiuto quando si imposta un font-size aumentato o ridotto al body con una @media query.

Nel caso degli heading, abbiamo ancora più fortuna. Avendo impostato le dimensioni del testo dei titoli in em nel foglio di stile, è stato impostato un margine appropriato (leading whitespace) per ciascun heading senza che dovessimo scrivere una singola riga di codice in più.

Diagramma che mostra i margini aggiustati automaticamente basandosi sul font-size.

Phrasing element

Questa dichiarazione di stile è destinata ad essere ereditata. È il modo in cui essa, e CSS in generale, devono funzionare. Tuttavia, apprezzo che qualcuno si sentirà a disagio con la voracità di questo selettore, specialmente dopo che si è stati abituati ad evitare l'ereditarietà ogni volta che si può.

Ho già coperto alcune eccezioni che potrete usare, ma, se vi è di ulteriore aiuto, ricordatevi che i phrasing element con un tipico valore di display inline erediteranno il top margin ma in termini di layout non ne saranno influenzati. Gli elementi inline rispettano solo i margini orizzontali, che è il comportamento specificato e standard in tutti i browser.

Diagramma che mostra gli elementi inline con margin.

Se vi trovate a dover sovrascrivere il selettore gufo frequentemente, ci potrebbero essere dei problemi sistemici più profondi con il design. L'owl selector gestisce il flow content e il flow content dovrebbe costituire la maggior parte del vostro contenuto. Non suggerisco di dipendere pesantemente dal contenuto posizionato nella maggior parte delle interfacce perché questo rompe le relazioni implicite di flusso. Anche i grid system, con le loro colonne a cui è applicato float, dovrebbero richiedere non più di un semplice selettore .row > * che applica margin-top: 0 per resettarle.

Diagramma che mostra le colonne con float con i margini.

Conclusioni

Non sono per nulla un bravo matematico, ma mi piacciono moltissimo i postulati di Euclide: un insieme di regole irriducibili, o assiomi, che formano la base di geometrie complesse e bellissime. Grazie ad Euclide, comprendo che anche i sistemi più complessi devono dipendere da regole fondamentali e CSS non fa eccezione. Sebbene la modularizzazione di un'interfaccia complessa sia un passo necessario nella sua maturazione, qualunque interfaccia che non segue principi basilari mancherà di chiarezza.

L'owl selector vi permette di controllare il flow content ma è anche un modo per abbandonare il controllo. Dando stile agli elementi a seconda del contesto e delle circostanze, accettiamo che la struttura del contenuto è - e dovrebbe essere - mutevole. Invece di prescrivere l'aspetto di singoli item, costruiamo sistemi che li anticipano. Invece di prescrivere l'aspetto dell'interfaccia come un tutto, lasciamo che sia il contenuto a determinarlo. Ridiamo il controllo alle persone che dovrebbero crearlo.

Quando disattiviamo CSS per un'intera pagina web, dovremmo notare due cose. Primo, la pagina è risolutamente flessibile: il contenuto sta nella viewport indipendentemente dalle sue dimensioni. Secondo, supponendo che abbiate scritto markup standard e accessibile, è attraversabile. Gli stili dello user agent del browser si prendono cura anche di questo.

I nostri sforzi per rivendicare e migliorare l'innata indipendenza dei device offerta dagli user agents sono continui. È ora che cominciamo anche a ripristinare l'indipendenza del contenuto.

Illustrazioni: Carlo Brigatti

Share/Save/Bookmark
 

Discutiamone

Ti sembra interessante? Scrivi tu il primo commento


Cenni sull'autore

Heydon Pickering

Heydon Pickering è un designer ed interface developer di Norwich, UK. È lead designer in Neontribe e accessibility editor per Smashing Magazine, dove potete trovare anche il suo libro Apps For All.

Questo sito per poter funzionare utilizza i cookie. Per saperne di più visita la pagina relativa all' INFORMATIVA