Dans cette partie, nous allons ajouter un peu de style au site grâce aux CSS. On verra comment penser mobile first fait au final gagner du temps. On verra également comment prendre en compte les images retina et jongler avec leurs tailles pour le côté responsive. Contrairement aux autres articles de la série, je vais plus exposer ma méthodologie que faire du code pas à pas.

Les CSS c'est l'enfer

Les CSS font partie des choses que l'on apprend en premier avec le HTML. C'est très simple, on en comprend le fonctionnement en quelques minutes. Par contre je ne sais pas pour vous, mais je trouve l'apprentissage aussi rapide que la maîtrise en est longue.

On peut prendre les raccourcis et se dire que tant que cela s'affiche à peu près correctement dans le navigateur c'est suffisant et tant pis si on charge 300 ko de CSS, impossible à maintenir ou à faire évoluer.

On peut simplement utiliser des frameworks comme Bootstrap ou Foundation, en adopter les avantages et contraintes, les conventions et surcharger au besoin quelques styles pour mieux coller au projet.

On peut aussi avoir envie d'explorer d'autres voies, de mettre en pratique des concepts se rapprochant un peu plus de nos goûts ou de nos préférences en matière d'écriture de CSS et de mise en page pour le web.

C'est cette dernière solution que l'on va explorer ici. Et puisqu'il est question de mes préférences, les voici :

  • Sass
  • Grille responsive sémantique : Neat
  • Grille fluide
  • Approche par composants
  • Mobile First
  • Convention de nommage : BEM
  • Éviter les spécificités des sélecteurs
  • Éviter les pixels : full em
  • Garder une feuille de style légère

Disclamer : La méthodologie exposée ici ne tient compte que de mes propres choix et préférences, elle peut être autant détestable qu'appréciable. Elle est certainement contestable et améliorable, n'hésitez pas à me mailer vos remarques.

Convention de nommage

Un des avantages de pouvoir écrire ses CSS from scratch c'est d'être libre du choix d'une méthodologie particulière ou de conventions de nommage. Middleman ne fournissant aucun style, on conserve cette liberté. C'est moins simple avec des couples CMS + plugins où les conventions changent d'un développeur à l'autre : le core du CMS utilise telle convention, le développeur du thème une autre et chaque plugin la sienne.

C'est également moins simple d'appliquer telle ou telle méthodologie quand les CSS de base du projet jouent contre nous. Exemple : garder une spécificité basse quand on a ce type de règle #works section.filtercontainer > ul.filter

BEM OOCSS SMACSS et les autres.

Dès lors qu'on s'intéresse un peu aux CSS, on tombe rapidement sur différentes méthodologies et de nombreux articles en expliquant les tenants et aboutissants. Les ressources étant nombreuses, je vous laisse faire des recherches. Parmi les plus connues, on trouve :

Chaque méthodologie à ses avantages et ses inconvénients, libre à chacun de prendre uniquement ce qu'il trouve de bon dans l'une pour le mélanger dans l'autre.

Pour ma part voici ce que j'aime pour chacune :

BEM

D'abord dubitatif sur l’intérêt du nommage type block__element--modifier, j'en suis maintenant complètement fan! J'aime l'idée de garder la spécificité assez basse et éviter les casses-têtes quand il faut surcharger une règle. J'aime également cette approche par composants qui permet de scoper ses styles pour éviter des collisions et faciliter la réutilisation inter-projets.

Ce sont les seuls principes que j'utilise dans BEM.

SMACSS

Dans le cas de SMACSS (Scalable and modular architecture for CSS), je me suis un peu heurté au découpage des règles CSS mais j'en ai retiré plusieurs choses intéressantes comme l'idée ses états nommés .is-something qui peuvent très bien venir enrichir une règle BEM en restant à la fois réutilisables et facilement identifiables.

OOCSS

Pour OOCSS, j'ai d'abord eu du mal à y trouver un intérêt car peu convaincu par les exemples vus. Puis j'ai compris la puissance de l'objet media et j'y ai trouvé une voie pour mieux factoriser certains éléments redondants.

Atomic Design

Ce que j'aime dans l'Atomic Design c'est la réutilisation de micro-composants pour créer de nouveaux ensembles. Les CSS deviennent des briques Lego qu'on arrange et qu'on emboite pour créer nos pages.

Organisation des fichiers et des règles

Les CSS vont être organisées dans différents dossiers contenant plusieurs fichiers. Utilisant le préprocesseur Sass, on profitera de sa fonction @import pour ne former qu'un seul fichier au final. Soit cette arborescence :

# Arborescence du dossier stylesheets/

+---- stylesheets/
        +---- base/
                        _normalize.scss
                        _typography.scss
        +---- components/
                        _blog.scss
                        _call-to-action.scss
                        _footer.scss
                        _gallery.scss
                        _header.scss
                        _module.scss
                        _navbar.scss
                        _section.scss
                        _testimonial.scss
        +---- config/
                        _grid.scss
                        _settings.scss
        +---- lib/
                        _buttons.scss
                        _forms.scss
                        _helpers.scss
        +---- pages/
                        _reservation.scss
                        _site.scss
        application.css.scss

Dans le dossier base/ on retrouve le bien connu normalize ainsi qu'un fichier dans lequel se trouve les règles typographiques principales, celles qui seront affectées à toute balise n'ayant pas de classe CSS de définie, comme les articles de blogs écrits en Markdown.

Le dossier components/ comprend tous les composants qui sont utilisés pour construire les pages à l'instar de la barre de navigation ou du footer. Chaque composant comporte un ensemble de règles CSS définissant sa présentation en se reposant un minimum sur les autres composants. Ce type d'encapsulation permet de faciliter la réutilisation inter-projets.

Dans config/ on a _settings.scss qui est le premier fichier qui est chargé et qui contient les variables principales. On y définit les couleurs, les polices et leurs tailles, les points de rupture pour les medias queries… Le fichier _grid.scss permet d'écraser les variables par défaut de Neat.

Le dossier lib/ contient des bibliothèques de micro-composants réutilisables. C'est dans le fichier _button.scss qu'on va par exemple définir les boutons utilisés sur le site.

Dans pages/ on va placer des fichiers contenant des règles spécifiques à certaines pages. Ces fichiers étant chargés en dernier, cela permet de pouvoir facilement écraser un règle d'un composant pour une page donnée.

Le fichier application.css.scss permet d'ordonner tous les imports.

Réutiliser ou réutiliser ?

Il est toujours de bon ton d'éviter les répétitions et de penser aux différentes manières de pouvoir factoriser son code et le rendre réutilisable. Mais lorsqu'il est question de réutilisation, j'essaye de différencier ce que je pense nécessaire de pouvoir réutiliser au sein du même projet et ce qui pourrait être réutiliser dans d'autres projets.

Si on jette un œil sur l'inventaire (voir plus bas), on remarque qu'il y des éléments visuels qui se ressemblent et qui peuvent être stylés par un seul composant avec de multiples classes CSS. C'est le cas des éléments de liste avec une image (page activités et page équipe), c'est le cas de la liste des articles de blog ou même des témoignages.

En agissant ainsi on éviterait quelques répétitions mais on aurait un composant générique assez lourd dans lequel il faudrait certainement jouer avec la cascade ou le poids des sélecteurs pour styler plus précisément certaines parties. Par contre, la richesse des classes crées permettrait de concevoir de nouveaux ensembles sans en ajouter d'autres. C'est un choix à faire en fonction du projet.

Pour ce site, j'ai préféré isoler certains éléments et éviter toute dépendance au projet. Comme pour les témoignages ou le blog dont le fichier CSS peut être copier/coller en 2 secondes dans un autre projet et être utiliser immédiatement.

Penser mobile first

Chaque composant est pensé avec un affichage sur mobile en priorité. Si on enlevait les media queries, on afficherait la version mobile étendue sur toute la largeur du navigateur.

On part donc d'une version dimensionnée pour mobile et on adapte le contenu pour les autres tailles d'écran avec les media queries. La plupart des composants n'ont pas de largeur de définie et leur hauteur est fluide. L'avantage de cette technique c'est que les éléments sont élastiques.

CSS anatomy

Les composants utilisent un nommage de type BEM. chaque classe cible un élément dans le fichier HTML. Aucun tag HTML n'est utilisé dans les règles CSS, uniquement des classes type .ma-classe.

L'intérêt d'éviter l'usage des tags c'est de supprimer la dépendance à une structure HTML donnée. Cela permet d'intervenir sur la sémantique du balisage HTML sans modifier l'affichage de la page. Prenons un exemple :

Un SEO intervient sur votre site et vous annonce qu'il va changer des balises titre type h1 / h2. Si vous utilisez des tags dans vos règles vous avez peut-être quelque chose du genre .header > h1. Si il change votre h1 en h2 dans votre fichier HTML vous perdez le style. En BEM on va avoir une classe .header__title qui est indépendante du balisage HTML, on peut le changer sans crainte.

L'inconvénient de BEM c'est qu'on peut vite se retrouver avec un grand nombre de classes à gérer et des noms assez longs à taper. On y regagne dès qu'il faut aller déboguer quelque chose dans la feuille de style.

Voyons comment est écrit un composant. On va prendre module.scss qui gère la mise en page des éléments des listes d'équipiers et d'activités :

// module
.mod{
  // styles de base du bloc parent

      @include media($small-to-medium){
        // styles pour les écrans de small à medium
        // pour la classe .mod uniquement
      }

      @include media($medium){
        // styes pour les écrans à partir de medium
        // pour la classe .mod uniquement
      }

      &__title{
        // styles pour la class mod__title
      }

      &__img{
        // styles pour la class mod__img
      }

      &__img--rounded{
        // styles pour la class mod__img--rounded
      }

      &__img--square{
        // styles pour la classe mod__img--square 
      }
}

Ce qui une fois compilé nous donnera ceci :

// module
.mod {}

@media screen and (min-width: 40em) and (max-width: 49em) {
      .mod {}
@media screen and (min-width: 50em) {
      .mod {}

.mod__title {}

.mod__img {}

.mod__img--rounded {}

.mod__img--square {}

Je ne regroupe pas les medias queries par type genre mobile, tablette, écran large. Je préfère les placer directement dans la règle à laquelle elles se rapportent. A l'usage c'est beaucoup plus rapide pour s'y retrouver! Une fois le fichier compilé c'est vrai que cela fait beaucoup de répétitions mais gzip compresse ça très bien. Et si vraiment il faut les regrouper, il existe des outils pour gulp ou grunt qui agiront en post-processing.

Inventaire de ressources et style guide

Voilà, arrêtons un peu la théorie pour un peu plus de pratique. Avant de commencer à écrire des classes à tout va, je trouve toujours utile de créer un inventaire visuel des éléments constituant le site ainsi qu'un guide de style à partir duquel je peux extraire des informations comme la taille des polices, la taille des images, les couleurs..

Cela évite par ailleurs d'aller rechercher ça dans le gros fichier contenant toutes les vues :)

Cliquez sur les images pour les agrandir :

Inventaire visuel

Inventaire css

Style guide

Style guide css

De retour dans Middleman

Comme dit en début d'article, il n'est pas question ici de reprendre chaque page pour y ajouter des classes CSS. Consulter la source sur Github, tout y est. Mais puisque nous utilisons Middleman, revenons un peu vers lui pour installer de nouvelles gems et activer quelques fonctionnalités.

Bourbon et Neat

Si vous n'êtes familier avec Bourbon et Neat, je vous encourage à parcourir leurs sites. Bourbon est une librairie de mixins Sass et Neat est une grille responsive.

Les outils évoluant, j'utilise moins de mixins Bourbon qu'avant, notamment les mixins pour automatiser l'écriture des vendor-prefixes préférant des solutions comme Autoprefixer. Mais Neat étant dépendant de Bourbon, il est forcément de la partie.

Neat est donc une grille qui se veut légère et sémantique. A l'usage cela permet d'éviter de charger les pages HTML de multiples classes pour définir des colonnes genre : small-col-12 medium-col-6 large-col-4. C'est un parti pris mais je préfère que tout ça soit gérer dans la feuille de style. Avec Neat, on utilisera un mixin pour dimensionner un élément en nombre de colonnes et des medias queries pour adapter les dimensions en fonction des tailles d'écrans.

// Neat style
.mon-element{
      @include span-columns(6);

      @include media($medium){
              @include span-columns(4);
      }
} 

Et dans notre fichier HTML:

// HTML
<div class="mon-element">le contenu</div>

J'ai suivi de nombreux débats animés sur les deux solutions, je pense que c'est une histoire de goûts. J'apprécie néanmoins l'usage des classes type x-col-2 dans les phases de prototypage dans le navigateur. Pour installer Bourbon et Neat, rien de plus simple, on ajoute les gems dans le Gemfile et on en profite pour ajouter la gem middleman-autoprefixer qui se chargera d'ajouter les vendor-prefixes en fonction du support navigateur choisi.

# Gemfile
gem 'bourbon'
gem 'neat'
gem 'middleman-autoprefixer'

Comme d'habitude, retour à la console…

$ bundle install

Pour Bourbon et Neat, il faut ensuite les inclure le plus en amont du fichier CSS principal, ici application.css.scss

// application.css.scss
// Import Bourdon and Neat
@import "bourbon";
@import "neat";

Autoprefixer s'active dans config.rb, on peut lui passer différentes options. Ici on lui indique d'ajouter les préfixes pour les 2 dernières versions des navigateurs et le support d'Internet Explorer à partir de la version 9. Une fois le tout installé / activé, pensez à redémarrer le serveur.

# config.rb
activate :autoprefixer do |config|
    config.browsers = ['last 2 versions', 'Explorer >= 9']
end

Activer le Live-reload

Middleman dispose d'une fonction Live Reload de manière native. A chaque fois que vous sauvegarder un fichier votre navigateur rafraichit la page pour voir les changements, ce qui est évidement très pratique quand on fait des CSS, là encore c'est dans config.rb que cela se joue :

# config.rb
# Reload the browser automatically whenever files change
configure :development do
    activate :livereload
end

Ajouter des Google Fonts

Le site utilise deux Google Fonts, Bitter et Open Sans. Elles sont déclarées dans le fichier _settings.scss mais doivent être chargées par le navigateur, on ajoute donc le lien dans layout.slim:

/ layout.slim
link href='http://fonts.googleapis.com/css?family=Bitter:400,700|Open+Sans:400italic,400,700' rel='stylesheet' type='text/css'

Quelques lignes de Javascript pour le menu

Pour le moment, on n'utilise pas de Javascript, mais il s'avère que notre menu en a besoin pour basculer de sa version mobile à sa version bureau. La visibilité du bouton menu est gérée en CSS avec une media query, on se sert de quelques lignes de Javascript pour cacher ou montrer le menu quand on clique sur le bouton. Pour cela on crée un fichier navbar.js qu'on range dans le dossier javascripts/:

// navbar.js
// Declare breakpoint
var breakpoint = 800;

// Targeting toggle button
var toggle = document.getElementById("main-menu-toggle");

// Targeting main menu
var menu = document.getElementById("main-menu");

// Toggle function
function menuToggle() {

    // Show main menu on big screen
    if (window.matchMedia("(min-width:" + breakpoint +"px)").matches) {
        menu.classList.remove("is-hidden");
    } else {
        menu.classList.add("is-hidden");
    }

    // Toggle main-menu on click
    toggle.onclick=function(e){
        e.preventDefault();
        menu.classList.toggle('is-hidden');
    };
}

menuToggle();

// Reload function on resize
window.addEventListener('resize', menuToggle);

Rien de particulier dans ce script, les commentaires parlent d'eux même. Par contre, il faut penser à ajouter ce script dans notre layout, juste après le footer :

= javascript_include_tag 'all'

Les images retina / responsive

Il va sans dire qu'à l'heure actuelle c'est quand même mieux de gérer d'une manière ou d'une autre l'affichage d'images haute résolution pour les écrans à haute densité de pixels. Il y a des décisions à prendre pour définir quelles images doivent être accompagnées de leurs versions HD ou pas et comment les images doivent se comporter quand la largeur de l'écran se réduit dans le cadre d'un site responsive.

Tout n'a pas forcement besoin d'être retina/hdpi ready mais il y a quand même un minimum, les logos par exemple!

Pour ce site la stratégie adoptée est simple, toutes les images existent en version 1x et 2x à l'exception de 2 types d'images. Les images postées dans le blog ne sont qu'en version 1x pour des raisons de syntaxe. On part du principe qu'un auteur n'a pas forcément les connaissances pour déclarer un chemin vers l'image 2x en Markdown (ce qui ouvre la porte pour un prochain article sur un moyen d'automatiser ça).

Les secondes images non retina/hdpi sont celles utilisées en en-tête. L'explication est simple : les images sont de grandes tailles donc les versions hd seraient beaucoup trop lourdes pour l'usage qui en est fait. Si on garde à l'esprit qu'elles disparaissent du viewport au premier coup de molette de souris, pas besoin de trop en faire.

On verra par contre quelle stratégie adopter pour charger différentes versions en fonction de la largeur du viewport afin d'éviter de charger la grande image si on est sur mobile.

Un helper pour les retina

Quand on veut afficher une image retina, il y a deux solutions principales : utiliser des medias queries en CSS et placer l'image en background-image ou utiliser, en HTML, srcset dans une balise img.

Cette dernière solution s'avère beaucoup plus pratique quand l'image doit exister au sein de la page en tant qu'image avec ses attributs. Le support de srcset pour les images retina est assez bon, Internet Explorer est à la traine mais le pourcentage d'utilisateurs avec un écran retina/hdpi doit être insignifiant.

Dans Middleman on a l'helper image_tag pour afficher des images et il comprend très bien srcset ! Il suffit de lui indiquer le chemin vers chaque fichier :

= image_tag "/images/camp/tente-bivouac.jpg",
            srcset: "/images/pages/camp/tente-bivouac.jpg 1x,
                         /images/pages/camp/tente-bivouac@2x.jpg 2x",
            alt: "Tente bivouac"

Si ça marche très bien comme ça, manuellement, pour la plupart des pages du site, certaines pages plus "automatisées" vont poser problème. On a vu précédemment que pour les pages équipe et activités, une boucle s'occupait d'afficher les images en allant chercher le nom des images dans le bloc Frontmatter.

Le problème c'est qu'on ne va pas y retrouver de nom type mon-image@2xjpg

Comme souvent, il y a plusieurs possibilités, la plus simple tient dans l'organisation des images dans les dossiers. On crée un dossier 2x qui ne contient que les images hd et on pointe dessus dans notre boucle :

= image_tag "/images/members/#{member.picture}", 
          srcset:   "/images/members/#{member.picture} 1x,
                         /images/members/2x/#{member.picture} 2x"

Pour le plaisir de compliquer la chose, et pour la démo, on va plutôt créer un helper dans config.rb dont le seul but sera d'ajouter @2x à la fin d'un fichier et de retourner ce nouveau nom:

#config.rb
def retina_image(file_name)
    if file_name
        file_name.sub(/\.(jpg|png|gif)/, "@2x.\\1")
    end
end

De ce qui nous donnera :

# Pour member.picture : pierre.jpg
retina_image(member.picture)

=> pierre@2x.jpg

Stratégie responsive pour les images de fond

Revenons aux images en en-tête, j'ai dit plus haut qu'il fallait trouver une stratégie pour afficher une image adaptée à la largeur du viewport.

Les images en arrière-plan n'ont aucune valeur sémantique, elles ne sont là qu'en "décoration", leur place est donc en background-image dans la feuille de style.

L'avantage de les afficher de cette manière c'est de pouvoir utiliser des medias queries pour inter-changer les versions. On pourrait donc facilement créer quelques classes supplémentaires pour chaque image d'en-tête. Mais cette option monterait rapidement ses limites, comment faire pour les images en en-tête des articles de blog ? Si on a 200 articles on ajoute 200 classes ?

Le nom des images utilisées pour chaque page ou article est défini dans le bloc FrontMatter, on a donc un moyen très simple d'accéder à cette information pour écrire des styles CSS à la volée, directement dans la page HTML.

Pour ça on va créer un nouvel helper dans config.rb :

def set_hero_image(image)
    styles = %{<style>.header{background-image: url('/images/backgrounds/small/#{image}');}
    @media screen and (min-width: 25em){.header{background-image: url('/images/backgrounds/medium/#{image}');}}
    @media screen and (min-width: 50em){.header{background-image: url('/images/backgrounds/#{image}');}}</style>}
    return styles
end

Donc, on a un helper qui nous retourne une balise <style> dans laquelle on écrase la classe .header existante avec l'image définie dans le bloc Frontmatter de la page. Comment l'utiliser ce helper ? D'adord dans le partial _header.html.slim:

/ _header.html.slim
/ Set the background image
- content_for :hero_image do
    - set_hero_image(current_page.data.hero_image)

Ce qui permet d'introduire une petite chose qu'on n'avait pas encore vu : content_for. content_for permet de définir un bloc de contenu dans une page (ici c'est la balise <style> avec son contenu) et d'en faire le rendu à un autre endroit.

Ici ce bloc à besoin d'être rendu dans le <head> de chacune des pages du site et cette partie de trouve dans le fichier layout.slim qu'on va modifier.

On ajoute ce qui suit juste après le lien vers la feuille de style:

/ layout.slim
= stylesheet_link_tag 'application'
= yield_content :hero_image

Pour résumer

Quand Middleman va générer une page, il va tomber sur content_for et notre helper set_hero_image auquel on va passer le nom de notre image contenu dans Frontmatter soit current_page.data.hero_image puis il va encapsuler la page dans le layout et en profiter pour rendre le bloc :hero_image à l'endroit souhaité grâce à = yield_content.

On a donc au final 3 images différentes qui seront affichées en fonction de la largeur du viewport.

C'est fini ?

Pour aujourd'hui oui! Mais il y a encore quelques améliorations à apporter qui ont été volontairement omises pour faire le sujet de prochains articles. C'est le cas de la galerie qui est rudimentaire et qu'on pourrait enrichir avec des effets au survol et une lightbox / fancybox pour l'ouverture des images. C'est aussi le cas des animations ou des icônes qui sont absentes pour le moment mais pourraient être ajoutées…

En attendant on a un site fonctionnel qui ne demande plus qu'à être mis en ligne, il ne reste que deux, trois petites choses à régler avec de faire le déploiement final.

Article précédent : Tutoriel Middleman, ajouter un blog
Article suivant : Tutoriel Middleman, déployer son site sur Github Pages

Je suis disponible pour des missions longues / CDD / CDI Cliquez pour en savoir plus