CSS3 - Pratique Du Design Web-Eyrolles (2015)
CSS3 - Pratique Du Design Web-Eyrolles (2015)
CSS3 - Pratique Du Design Web-Eyrolles (2015)
Vingt ans après sa conception, le langage CSS n’en est plus à ses balbutiements et
n’est plus optionnel en ce qui concerne la conception web moderne. Sans le moindre
concurrent en vue, CSS a encore de belles années devant lui. Et pour cause, il est
toujours en perpétuelle évolution !
Ce livre n’a pas pour prétention d’être le guide ultime de l’intégrateur dans la mesure où il
ne reprend pas les bases. Il offre simplement une mise à niveau en levant le voile sur tous
les modules CSS, afi n d’offrir dès aujourd’hui les connaissances nécessaires à la
réalisation de sites et d’applications web. En effet, les enjeux comme les objectifs ne sont
plus les mêmes qu’il y a quelques années, aussi est-il important que les intégrateurs,
designers et développeurs s’arment face aux nouvelles problématiques que sont le
Responsive Web Design, le rétrécissement de l’écart entre le Web et le natif, et la course à
la performance.
Qu’il s’agisse de mise en page avec Flexbox ou Grid Layout, d’embellissement des
interfaces, d’élaboration d’animations ou même de design fl uide avec les Media Queries,
vous devriez être capable de maîtriser tous ces sujets au sortir de votre lecture.
Au-delà de l’aspect purement didactique de l’ouvrage, vous trouverez un grand nombre
d’exemples et de mises en pratique, ainsi que tout ce que vous devez savoir vis-à-vis du
support des fonctionnalités par les navigateurs. Pour fi nir, vous découvrirez dans les
annexes la liste des valeurs par défaut des propriétés CSS, celle des propriétés que l’on
peut animer et une bibliographie pour aller plus loin.
À qui s’adresse cet ouvrage ?
– Aux intégrateurs désireux d’aller plus loin avec CSS
– Aux designers souhaitant se mettre au design in the browser
– À tous les concepteurs de sites et d’applications voulant se mettre à niveau vis-à-vis des
nouveautés du langage
Au sommaire
L’état actuel du W3C et des standards CSS • Une évolution implacable • Un tour d’horizon des navigateurs
d’aujourd’hui • L’état actuel des standards • L’aventure des préfixes constructeurs • La standardisation des CSS •
Les sélecteurs : l’accès au DOM • Opérateurs • Sélecteurs d’attribut • Pseudo-classes de position • Pseudo-classes
de contexte • Pseudo-classes de formulaire • Pseudo-éléments • Positionnement et layout : les nouvelles
techniques de mise en page • Modèle de boîte et box-sizing • Multicolonne • Flexbox • Grid Layout • Position «
sticky » • Régions • Masques de formes • Interfaces graphiques et amélioration visuelle • Couleurs • Opacité •
Bords arrondis • Ombres avec box-shadow • Ombres de texte avec text-shadow • Dégradés • Meilleur contrôle des
arrière-plans • Filtres CSS • Pointer-events • Images comme bordures • De nouvelles unités et valeurs • calc • rem •
ch • Unités relatives au viewport • Dimensions intrinsèques • Contrôle du texte • Gestion des débordements avec
overflow-wrap • Gestion des espaces avec white-space • Débordements de texte et text-overflow • Césures avec
hyphens • Césures agressives avec word-break • Gestion des tabulations avec tab-size • Ponctuation plus élégante
avec hanging-punctuation • Meilleurs alignements avec text-align-last • Restriction de caractères avec unicode-range
• Variables natives • Comment ça marche ? • La syntaxe • Les variables invalides • Les valeurs de recours en cas
d’invalidité • Cas particuliers et clarifications • Styles conditionnels • Feature Queries • Media Queries •
Transformations : un nouveau monde en 2D et en 3D • À quoi servent les transformations CSS ? • Les
transformations 2D • L’origine de transformation • L’ordre des transformations • Les transformations 3D •
Animations et transitions : pour des interfaces moins statiques • À quoi servent les animations ? • Animation ou
transition ? • À propos de l’accélération matérielle • JavaScript ou CSS ? • Les transitions • Les animations.
Biographie auteur
H. Giraudel
Développeur front-end passionné par CSS et auteur du site Browserhacks, Hugo
Giraudel fait part de son expertise sur son propre blog ainsi que sur les sites
SitePoint, CSS-Tricks, The Sass Way et Tuts+, entre autres. Enthousiasmé par le
préprocesseur Sass, il a su s’imposer comme référence mondiale sur le sujet. Il est
d’ailleurs l’auteur de SassDoc, un outil de documentation pour Sass.
R. Goetter
Webdesigner et gérant d’une agence web strasbourgeoise, Raphaël Goetter partage
ses connaissances à travers son site Alsacréations.com, et s’intéresse de près aux
domaines des normes du Web et de l’accessibilité. Il fait partie du collectif
Openweb.eu.org, référence francophone en matière de standards du Web.
www.editions-eyrolles.com
CSS3
Pratique du design web
Hugo Giraudel
Raphaël Goetter
Préface de Chris Coyier
ÉDITIONS EYROLLES
61, bd Saint-Germain
75240 Paris Cedex 05
www.editions-eyrolles.com
En application de la loi du 11 mars 1957, il est interdit de reproduire intégralement ou partiellement le présent ouvrage,
sur quelque support que ce soit, sans l’autorisation de l’Éditeur ou du Centre Français d’exploitation du droit de copie,
20, rue des Grands Augustins, 75006 Paris.
© Groupe Eyrolles, 2015, ISBN : 978-2-212-14023-1
Crédits photographiques : © Alexandra Lucas
DANS LA MÊME COLLECTION
C. DELANNOY. – Le guide complet du langage C.
N°14012, 2014, 844 pages.
K. AYARI. – Scripting avancé avec Windows PowerShell.
N°13788, 2013, 358 pages.
W. BORIES, O. MIRIAL, S. PAPP. – Déploiement et migration Windows 8.
N°13645, 2013, 480 pages.
W. BORIES, A. LAACHIR, D. THIBLEMONT, P. LAFEIL, F.-X. VITRANT. – Virtualisation du poste de travail Windows 7
et 8 avec Windows Server 2012.
N°13644, 2013, 218 pages.
J.-M. DEFRANCE. – jQuery-Ajax avec PHP.
N°13720, 4e édition, 2013, 488 pages.
L.G. MORAND, L. VO VAN, A. ZANCHETTA. – Développement Windows 8 - Créer des applications pour le Windows
Store.
N°13643, 2013, 284 pages.
Y. GABORY, N. FERRARI, T. PETILLON. – Django avancé.
N°13415, 2013, 402 pages.
P. ROQUES. – Modélisation de systèmes complexes avec SysML.
N°13641, 2013, 188 pages.
CSS occupe une place étrange dans le monde du développement. Il y a des développeurs
qui dédaignent ce langage, ne le jugeant pas digne de leur temps ou de leurs compétences.
Et, en même temps, ils en ont peut-être peur. Voyez-vous, CSS est à la fois très simple et
très compliqué. Après tout, c’est essentiellement une suite de déclarations composées de
paires de clés/valeurs : color: red, width: 50%, display: inline-block. Rien de bien compliqué
en soi. Mais la façon dont ces déclarations interagissent les unes avec les autres peut être
aussi complexe et casse-tête que n’importe quel autre langage de programmation. Ajoutez
à cela les problèmes d’incompatibilité entre les navigateurs, et vous pourriez presque en
venir à dire que le rôle d’intégrateur est l’un des plus difficiles qui soit !
Il y a aussi le fait que CSS évolue à une vitesse sans précédent. Ce que nous connaissons
sous le terme de « CSS 3 » est à la fois amusant et puissant, mais rend aussi les choses
plus complexes. Il existe désormais de nouveaux systèmes de mise en page comme
Flexbox, des possibilités d’animation, et même de la 3D entièrement réalisée avec CSS !
Êtes-vous parfaitement au fait de position: sticky ? Qu’en est-il des unités de
dimensionnement vis-à-vis du viewport ? Savez-vous que l’on peut tester le support des
propriétés directement au sein des feuilles de styles ? C’est un nouveau monde plein de
possibilités qui s'offre à vous !
Hugo Giraudel est la personne idéale pour vous présenter ces nouveaux concepts. Il
parvient en effet à appréhender ces idées, puis à les expliquer de manière pédagogique et
compréhensible. Je peux vous l’affirmer, non seulement parce que c’est ce qu’il fait dans
ce livre, mais aussi parce que je lis ses articles techniques depuis des années. Il est parfait
quand il s’agit d’expliquer des concepts bien spécifiques, et parvient à couvrir tous les
détails nécessaires à la compréhension de ceux-ci. J’ai même fait appel à ses services pour
m’aider à rechercher et à écrire des contenus pour mon propre site web (CSS-Tricks) !
C’est une chance que d’avoir Hugo à la tête de ce livre, réunissant autant d’informations
intéressantes et pertinentes en un même ouvrage.
Chris Coyier,
auteur du site CSS-Tricks,
développeur en chef à CodePen,
podcasteur à Shop Talk Show
Table des matières
Avant-propos
CSS, un langage en perpétuelle évolution
Pourquoi cet ouvrage ?
À qui s’adresse cet ouvrage ?
La structure de l’ouvrage
Compléments vidéo à consulter en ligne
Remerciements
CHAPITRE 1
L’état actuel du W3C et des standards CSS
Une évolution implacable
Un tour d’horizon des navigateurs d’aujourd’hui
L’état actuel des standards
L’aventure des préfixes constructeurs
La standardisation des CSS
Étape 1. Trouver une idée
Étape 2. Editor’s Draft (ED)
Étape 3. Working Draft (WD)
Étape 4. Candidate Recommendation (CR)
Étape 5. Recommendation (REC)
CHAPITRE 2
Les sélecteurs : l’accès au DOM
Les opérateurs
Opérateur d’adjacence directe (CSS 2)
Compatibilité des navigateurs pour l’opérateur d’adjacence directe
Opérateur d’adjacence générale
Compatibilité des navigateurs pour l’opérateur d’adjacence générale
Les sélecteurs d’attribut
Sélecteur d’attribut simple (CSS 2)
Compatibilité des navigateurs pour les sélecteurs d’attribut
Sélecteur d’attribut modulé
Attribut dont la valeur est comprise dans une liste séparée par des
espaces avec [attr~=“value”] (CSS 2)
Attribut dont la valeur est exacte ou démarre par une chaîne précédée
du caractère - avec [attr|=“value”] (CSS 2)
Attribut dont la valeur débute par une chaîne avec [attr^=“value”]
Attribut dont la valeur termine par une chaîne avec [attr$=“value”]
Attribut dont la valeur contient une chaîne avec [attr*=“value”]
Les pseudo-classes de position
Premier et dernier enfant avec :first-child & :last-child
Compatibilité des navigateurs pour :first-child
Compatibilité des navigateurs pour :last-child
Énièmes enfants avec :nth-child et :nth-last-child
Styler en fonction du nombre d’éléments dans le parent
Compatibilité des navigateurs pour :nth-child et :nth-last-child
Enfant unique avec :only-child
Compatibilité des navigateurs pour :only-child
Propriétés *-of-type
Compatibilité des navigateurs pour les pseudo-classes :*-of-type
Les pseudo-classes de contexte
Ciblage par ancre avec :target
Compatibilité des navigateurs pour :target
Éléments vides avec :empty et :blank
Compatibilité des navigateurs pour :empty
Gestion de la langue avec :lang (CSS 2)
Compatibilité des navigateurs pour :lang
Élément racine avec :root
Compatibilité des navigateurs pour :root
Négation avec :not
Compatibilité des navigateurs pour :not
Simplification des sélecteurs avec :matches
Compatibilité des navigateurs pour :matches
Les pseudo-classes de formulaire
Focus avec :focus (en toute simplicité)
Compatibilité des navigateurs pour :focus
État des champs de formulaire avec :enabled et :disabled
Compatibilité des navigateurs pour :enabled et :disabled
Modes d’écriture avec :read-write et :read-only
Compatibilité des navigateurs pour :read-write et :read-only
Validité des champs de formulaire avec :valid et :invalid
Compatibilité des navigateurs pour :valid et :invalid
Statut des champs de formulaire avec :optional et :required
Compatibilité des navigateurs pour :optional et :required
Précisions sur les checkboxes et les boutons radio avec :checked
et :indeterminate
Compatibilité des navigateurs pour :checked
Compatibilité des navigateurs pour :indeterminate
Valeur par défaut des boutons radio avec :default
Compatibilité des navigateurs pour :default
Gestion de l’amplitude des champs de type number avec :in-range et :out-of-
range
Compatibilité des navigateurs pour :in-range et :out-of-range
Les pseudo-éléments
Évolution de la syntaxe
Contrôle de la sélection du texte avec ::selection
Compatibilité des navigateurs pour ::selection
CHAPITRE 3
Positionnement et layout : les nouvelles techniques de mise en page
Le modèle de boîte : retour aux sources avec box-sizing
Cas pratique : simplifier les calculs de dimensions
Compatibilité des navigateurs pour box-sizing
Le multicolonne
Comment ça marche ?
Syntaxe
Largeur des colonnes avec column-width
Nombre de colonnes avec column-count
Déclaration raccourcie avec columns
Gestion de la gouttière avec column-gap
Séparation des colonnes avec column-rule
Interruption des colonnes avec break-*
Envahissement des colonnes avec column-span
Équilibrage des colonnes avec column-fill
Cas pratique : alléger une liste chargée
Cas pratique : utiliser les colonnes comme grille
Compatibilité des navigateurs pour le multicolonne
Les modèles de boîtes flexibles avec Flexbox
Comment ça marche ?
Syntaxe
Initialisation avec display: flex | inline-flex
Direction générale avec flex-direction
Gestion des retours à la ligne avec flex-wrap
Déclaration raccourcie avec flex-flow
Justification avec justify-content
Alignement du contenu avec align-items
Répartition des lignes avec align-content
Gestion de l’ordre d’apparition avec order
Accroissement avec flex-grow
Rétrécissement avec flex-shrink
Dimensions par défaut avec flex-basis
Déclaration raccourcie avec flex
Alignement particulier avec align-self
Cas pratique : un menu responsive
Cas pratique : un layout mobile first
Cas pratique : centrage absolu
Cas pratique : un formulaire fluide
Compatibilité des navigateurs pour Flexbox
Mettre en place une solution pour tous les navigateurs
Un préprocesseur pour simplifier les préfixes (Sass)
Le Grid Layout
Comment ça marche ?
Une introduction par l’exemple
Initialiser une grille
Définir la grille
Simplifier les définitions avec la fonction repeat()
Fractions de l’espace restant avec l’unité fr
Nommage
Nommer les lignes
Nommer les zones
Placer les éléments
Le placement avec grid-row-start, grid-row-end, grid-column-start et
grid-column-end
Le positionnement simplifié avec grid-row et grid-column
Le positionnement encore plus simplifié avec grid-area
Placement automatique des éléments avec grid-auto-flow
Gestion des erreurs de placement
Le terme subgrid
Cas pratique : réaliser une galerie d’images
Simplifier les choses avec un préprocesseur (Sass)
Cas pratique : créer un layout simple
Compatibilité des navigateurs pour Grid Layout
La position « sticky »
Cas pratique : un header fixe
Compatibilité des navigateurs pour position: sticky
Une solution de repli en JavaScript
Les régions
Terminologie
Comment ça marche ?
Injecter du contenu dans un flux
Réclamer le contenu d’un flux
Gérer la fin d’un flux
Quelques informations complémentaires
Boucles infinies
Arborescence HTML spécifique
Cas pratique : une gestion des encarts publicitaires différente selon la taille de
l’écran
Compatibilité des navigateurs pour les régions CSS
Les masques de forme en CSS
Comment ça marche ?
Zone de flottement
Formes de base
circle
ellipse
inset
polygon
Syntaxe
Déclaration d’une forme avec shape-outside
Seuil d’opacité avec shape-image-threshold
Marge avec shape-margin
Cas pratique : une bio avec un avatar circulaire
Compatibilité des navigateurs pour les masques de forme
CHAPITRE 4
Interfaces graphiques et amélioration visuelle
Des couleurs plus colorées
De nouvelles couleurs
La notation rgba
Compatibilité des navigateurs pour rgba
La notation hsla
Compatibilité des navigateurs pour hsla
L’opacité
opacity et l’héritage
opacity et les contextes d’empilement
Compatibilité des navigateurs pour opacity
Émuler le support sur Internet Explorer grâce aux filtres propriétaires de
Microsoft
Des bords arrondis en CSS
La version simple
Dissocier les axes X et Y
Bordures et bords arrondis
Compatibilité des navigateurs pour border-radius
Des ombres avec box-shadow
À propos des performances
Cas pratique : donner du sens aux interfaces
Compatibilité des navigateurs pour box-shadow
Des ombres de texte avec text-shadow
Compatibilité des navigateurs pour text-shadow
Des dégradés
Syntaxe
Dégradés linéaires
Dégradés radiaux
Les dégradés au quotidien
Compatibilité des navigateurs pour les dégradés
Un meilleur contrôle des arrière-plans
Plusieurs arrière-plans sur un même élément
Compatibilité des navigateurs pour les arrière-plans multiples
De nouvelles valeurs pour background-repeat
Cas pratique : placeholders dans une galerie d’images
Compatibilité des navigateurs pour background-repeat: space et
background-repeat: round
De nouvelles valeurs pour background-size
Compatibilité des navigateurs pour background-size: cover et
background-size: contain
Émuler le support sur Internet Explorer grâce aux filtres propriétaires
de Microsoft
Un nouveau système de valeurs pour background-position
Compatibilité des navigateurs pour le nouveau système de background-
position
Origine d’arrière-plan avec background-origin
Compatibilité des navigateurs pour background-origin
Fixation de l’arrière-plan avec background-attachment: local
Cas pratique : effet d’ombre sur un élément scrollable
Compatibilité des navigateurs pour background-attachment: local
Zone d’arrière-plan avec background-clip
Cas pratique : pop-up et bordure semi-transparente
Compatibilité des navigateurs pour background-clip
Les filtres CSS
Comment fonctionnent-ils ?
Syntaxe
blur
brightness
contrast
drop-shadow
grayscale
hue-rotate
invert
opacity
saturate
sepia
Cas pratique : différents coloris d’image
Compatibilité des navigateurs pour les filtres CSS
Pointer-events
Cas pratique : améliorer les performances durant le scroll
Compatibilité des navigateurs pour les pointer-events
Des images comme bordures
Comment ça marche ?
Syntaxe
border-image-source
border-image-slice
border-image-width
border-image-outset
border-image-repeat
Compatibilité des navigateurs pour border-image
CHAPITRE 5
De nouvelles unités et valeurs
Le Saint-Graal des calculs : calc
À propos de la syntaxe
Pourquoi pas un préprocesseur ?
Cas pratique : utiliser calc pour le layout
Cas pratique : position de l’arrière-plan
Cas pratique : centrage absolu
Compatibilité des navigateurs pour calc
Root em : l’évolution de l’unité em
Pourquoi rem et pas simplement em ?
Cas pratique : une grille typographique
Compatibilité des navigateurs pour l’unité rem
Une solution de repli avec un préprocesseur (Sass)
Démystification : à propos de 62.5 %
Une unité pour la largeur d’un caractère : ch
Compatibilité des navigateurs pour l’unité ch
Les unités relatives au viewport
Pourcentage de la largeur/hauteur avec vw et vh
Cas pratique : limiter la hauteur des images à la hauteur du viewport
Cas pratique : typographie responsive
Cas pratique : typographie responsive et rem
Cas pratique : conserver un ratio relatif au viewport
Compatibilité des navigateurs pour les unités vw et vh
Pourcentage de la largeur/hauteur avec vmin et vmax
Cas pratique : ratio 16:9 occupant toute la largeur du viewport
Compatibilité des navigateurs pour les unités vmin et vmax
Les dimensions intrinsèques
Largeur minimale avec min-content
Cas pratique : figure dimensionnée selon la largeur de l’image
Largeur maximale avec max-content
Comportement de type block avec fill
Largeur optimale avec fit-content
Cas pratique : liste centrée mais dimensionnée selon son contenu
Compatibilité des navigateurs pour les dimensions intrinsèques
Faciliter l’utilisation des valeurs intrinsèques avec un préprocesseur
(Sass)
CHAPITRE 6
Contrôle du texte
La gestion des débordements avec overflow-wrap
Compatibilité des navigateurs pour overflow-wrap
La gestion des espaces avec white-space
Cas pratique : affichage de code
Compatibilité des navigateurs pour white-space
Les débordements de texte et text-overflow
Cas pratique : des lignes de tableau de même hauteur
Compatibilité des navigateurs pour text-overflow
Les césures avec hyphens
Syntaxe
none
manual (valeur initiale)
auto
Compatibilité des navigateurs pour hyphens
Les césures agressives avec word-break
Compatibilité des navigateurs pour word-break
La gestion des tabulations avec tab-size
Compatibilité des navigateurs pour tab-size
Une ponctuation plus élégante avec hanging-punctuation
Compatibilité des navigateurs pour hanging-punctuation
De meilleurs alignements avec text-align-last
Compatibilité des navigateurs pour text-align-last
La restriction de caractères avec unicode-range
Cas pratique : embellir les esperluettes
Compatibilité des navigateurs pour unicode-range
Cas pratique : des blocs de code qui donnent envie
CHAPITRE 7
Variables natives
Comment ça marche ?
La syntaxe
Les variables invalides
Les valeurs de recours en cas d’invalidité
Cas particuliers et clarifications
Variables qui se référencent mutuellement
Propriété all et propriétés personnalisées
Animations
La compatibilité des navigateurs pour les variables natives
CHAPITRE 8
Styles conditionnels
Feature Queries
De la notion de « support »
Syntaxe
API JavaScript
@supports et préfixes constructeurs
Cas pratique : header fixe à compter d’une certaine position avec position:
sticky
Cas pratique : carrousel de Bootstrap 3
Compatibilité des navigateurs pour les Feature Queries
Media Queries
Syntaxe
Conjonction
Condition
Négation
Restriction
Que peut-on détecter ?
Les dimensions : width et height
L’orientation de l’appareil
Le ratio hauteur/largeur de l’écran
La résolution de l’écran
Futur : davantage de contrôle
Présence de JavaScript
Luminosité ambiante
Système de pointage
Capacité de survol
Fréquence de mise à jour
Gestion des débordements
Futur : Media Queries et variables
Cas pratique : le design Mobile First
Compatibilité des navigateurs pour les Media Queries
CHAPITRE 9
Transformations : un nouveau monde en 2D et en 3D
À quoi servent les transformations CSS ?
Les transformations 2D
Rotation
Cas pratique : réaliser des flèches
Émulation de rotation avec les filtres propriétaires
Conversion d’angle avec un préprocesseur (Sass)
Translation
À propos des offsets
Cas pratique : centrage absolu
Mise à l’échelle
Cas pratique : agrandissement au survol
Émulation de mise à l’échelle avec zoom sur Internet Explorer 8
Bug d’Android 2.3
Inclinaison
Cas pratique : un menu incliné
matrix
Compatibilité des navigateurs pour les transformations 2D
L’origine de transformation
Tester et débugger transform-origin
L’ordre des transformations
Les transformations 3D
Compatibilité des navigateurs pour les transformations 3D
Environnement 3D avec perspective
Origine de perspective
En résumé
Contexte et transform-style
Notion de faces avec backface-visibility
Cas pratique : effet card flip
CHAPITRE 10
Animations et transitions : pour des interfaces moins statiques
À quoi servent les animations ?
Animation ou transition ?
À propos de l’accélération matérielle
Compatibilité des navigateurs pour will-change
JavaScript ou CSS ?
Les transitions
« Quoi ? » avec transition-property
« Comment ? » avec transition-timing-function
« Combien de temps ? » avec transition-duration
« Quand ? » avec transition-delay
Utilisation
Quelques informations complémentaires
Cas pratique : une sidebar plus légère
Cas pratique : changement de vue sur mobile
Cas pratique : animation d’une pop-up
Compatibilité des navigateurs pour les transitions
Transitions et pseudo-éléments
Les animations
« Quoi ? » avec animation-name
« Comment ? » avec animation-timing-function
« Combien de temps ? » avec animation-duration
« Combien de fois ? » avec animation-iteration-count
« Dans quel sens ? » avec animation-direction
« Quel état ? » avec animation-play-state
« Quand ? » avec animation-delay
« Quel impact ? » avec animation-fill-mode
La définition d’une animation : @keyframes
Animations et performance
Cas pratique : animation d’un loader
Cas pratique : animation d’un carrousel
Cas pratique : animation de l’affichage d’une galerie d’images
Faciliter la génération des styles avec un préprocesseur (Sass)
Cas pratique : animations comme feedbacks
Cas pratique : mise en place d’un système anti-spoiler
Compatibilité des navigateurs pour les animations
ANNEXE A
Liste des propriétés CSS et de leur valeur par défaut
ANNEXE B
Liste des propriétés CSS qui peuvent être animées
Notes
Ce que l’on aimerait pouvoir animer
ANNEXE C
Ressources et liens
Sites francophones
Sites anglophones
Newsletters
Comptes Twitter
Bibliographie
Index
Avant-propos
Inventé au début des années 1990, CSS a connu une route longue et parsemée d’embûches
pour arriver au langage de styles tel qu’on le connaît aujourd’hui. Débutons avec un état
des lieux et une mise en situation.
Une évolution implacable
En 2005, lors de la sortie du livre CSS 2 – Pratique du design web, la conception de sites
en CSS n’en était qu’à ses balbutiements et la mise en page en tableaux HTML était
monnaie courante. Pour faire simple, « passer aux standards » demeurait à cette époque
une mission pour le moins périlleuse.
En ces temps troubles, les doctypes XHTML, les framesets, spacer.gif et Internet Explorer
6 avaient le vent en poupe : imaginez-vous dans un monde où Internet Explorer 7 et
Google Chrome n’existaient pas encore ! Si, comme moi, vous avez connu cette époque
de la « bidouille », vous ne pouvez que vous réjouir de sa disparition et constater le
chemin parcouru en près de dix années.
Si je devais résumer la période web actuelle en seulement quatre mots-clés, je citerais :
• HTML5CSS3 (d’accord, j’ai triché !) ;
• Web mobile ;
• industrialisation ;
• stabilisation.
HTML 5 et CSS 3, véritables fleurons du Web moderne, apportent de nouvelles couches et
améliorations considérables aux versions précédentes. Voici les principales innovations
apportées par ces technologies :
• de multiples possibilités graphiques (arrondis, ombrages, opacité, dégradés, arrière-plans
multiples, pour ne citer que les plus connus) ;
• de nouveaux types de positionnements (Flexbox, Grid Layout, multicolonne) ;
• de nombreuses façons de cibler les éléments ;
• des transformations (rotation, zoom, déformation, translation) ;
• enfin, des animations pour que les interfaces deviennent un peu moins figées.
Et je ne viens d’évoquer que des fonctionnalités CSS 3 ! En vérité, ce sont tous les
domaines du Web qui ont bénéficié de spectaculaires avancées techniques : HTML 5, mais
aussi WebGL, SVG, Canvas, etc.
En parallèle, cette décennie marque l’avènement d’un monde « mobile ». En 2007 en
effet, l’iPhone d’Apple débarque et provoque un raz-de-marée : il est enfin possible (et
pratique) de se servir d’un téléphone portable pour surfer sur le Net. Le smartphone est né.
Avec le Web mobile s’est ouvert un formidable nouveau marché à conquérir, avec son lot
de combats de titans sans merci dans lesquels certains, tels que Google et Apple, en sont
sortis grandis et où d’autres, tels que BlackBerry, Motorola et Nokia, y ont laissé des
plumes. Ou pire…
Notre activité professionnelle se doit d’accompagner cette révolution mobile :
« Responsive Web Design » a été élu mot-clé buzz de l’année 2013, il en découle que les
notions de performance et de temps d’affichage deviennent essentielles et, enfin, une
remise en question radicale de l’ergonomie d’un site web est nécessaire dans un monde
connecté que l’on peut tenir dans sa main.
L’industrialisation apparaît elle aussi telle une évidence depuis plusieurs années. Le métier
d’intégrateur s’est en effet radicalement transformé en peu de temps, agrégeant des
contraintes jusqu’alors inédites. Aujourd’hui, il ne suffit plus de rédiger du code HTML et
CSS valide, mais bien de réaliser de nombreuses tâches simultanément :
• produire du code lisible et maintenable dans la durée ;
• tester sur plusieurs périphériques et formats différents ;
• ajouter les préfixes nécessaires aux propriétés CSS 3 non finalisées ;
• versionner ses fichiers de travail ;
• gagner en performance en compressant les images ainsi que CSS et JavaScript ;
• ne pas réinventer la roue à chaque projet et automatiser le maximum de tâches.
Autant dire que les outils mis à notre disposition pour pallier les lacunes de CSS ou
accélérer notre méthode de production sont très rapidement devenus une béquille
indispensable à notre quotidien. Je pense notamment aux préprocesseurs, dont Sass est
l’un des plus célèbres porte-parole, ou encore aux planificateurs de tâches (task runners)
tels que Grunt ou Gulp. Et, chaque jour, on peut assister à la naissance (ou la mort) de l’un
de ces outils.
La bonne nouvelle est que tout cela s’accompagne d’une relative mais générale
stabilisation du côté des standards qui régissent le Web. La présence de nouveaux acteurs
dans le monde des navigateurs web, notamment Chrome de Google né en 2008, ainsi que
la fin de l’hégémonie d’Internet Explorer et la mort programmée de Windows XP
favorisent en réalité l’action menée par des organismes mondiaux régulateurs tels que le
W3C ou le WHATWG. Les standards, quoi.
D’une manière générale, nous pouvons observer que certains consensus s’établissent plus
facilement qu’auparavant entre les forces en présence (par exemple, l’idée de laisser
tomber les préfixes constructeurs au profit des flags – nous y reviendrons un peu plus
loin).
Autre point extrêmement positif dans ce constat : le « dinosaure » Internet Explorer
rattrape rapidement son retard et offre enfin la possibilité de concevoir des pages web sans
bricolages farfelus et aléatoires. La preuve en est qu’Internet Explorer a désormais un
developer channel public.
Le design web est dorénavant une affaire de règles HTML et CSS établies, qui
s’appliquent à la grande majorité des navigateurs actuels.
Un tour d’horizon des navigateurs d’aujourd’hui
Dans les années 2000, la suprématie d’Internet Explorer était alors établie depuis
longtemps et rien ne paraissait pouvoir la faire fléchir. À peine quelques années plus tard,
Mozilla Firefox, promu par une communauté libre exceptionnelle, commençait déjà à faire
de l’ombre au mastodonte de Microsoft.
Puis Google Chrome débarque en 2008. Aujourd’hui, il emporte tout sur son passage.
Rapidement propulsé au troisième rang des navigateurs, il occupe actuellement la tête du
classement sans que son hégémonie ne puisse être discutée.
Il ne reste plus guère de place pour les autres navigateurs « alternatifs », ce qui n’empêche
pas certains méconnus de s’illustrer de belle manière, par exemple Maxthon (basé sur
WebKit) qui détient le plus haut score au classement HTML5Test.
RESSOURCE HTML5Test
HTML5Test est un outil en ligne qui évalue la capacité d’un navigateur (le vôtre) à
supporter les fonctionnalités HTML 5 et CSS 3. Il dispose également d’un classement
des meilleurs navigateurs en termes de support.
http://bit.ly/html5-test
Les navigateurs mobiles ont également bénéficié d’un large essor durant ces dernières
années. Certains, à l’instar de Safari et Chrome (encore lui !), se sont taillé la part du lion,
tandis que de nouveaux challengers, comme UC Browser, Silk d’Amazon, Dolphin ou
encore Coast d’Opera, débarquent fraîchement dans l’arène.
Figure 1–1
UC Browser
L’état actuel des standards
Le W3C ( World Wide Web Consortium) est l’organisation mondiale qui fait tourner les
standards en coulisses depuis sa création en 1994. C’est à travers son impulsion que se
sont construits HTML, XHTML, SVG, PNG et bien d’autres règles d’accessibilité
numérique.
Cependant, ce qui fait à la fois la force et la faiblesse du W3C est que ses spécifications
sont soumises à un accord entre l’ensemble de ses membres influents. Cela lui confère sa
neutralité indispensable, mais c’est également une source de lenteurs pesantes.
Les enjeux économiques sont parfois tels que se mettre au diapason entre des acteurs
comme Microsoft, Apple, Adobe ou Google nécessite des processus et des négociations
très rigoureux, chronophages, voire carrément figés.
Critiquant ouvertement l’inertie du W3C ainsi que certains de ses choix stratégiques (celui
de laisser mourir HTML au profit de XHTML, notamment), certains membres d’Apple,
Mozilla et Opera ont décidé en 2004 de collaborer officieusement sur des spécifications
qui leur paraissaient « implémentables » rapidement dans les navigateurs web.
Cette nouvelle organisation, le WHATWG ( Web Hypertext Application Technology
Working Group), est composée, entre autres, de membres actifs du W3C. Elle œuvre
parallèlement à celui-ci sur des standards HTML 5, Web Workers, Microdata, Web Forms
et applications web.
Les résultats du groupe de travail HTML 5 du WHATWG furent si prometteurs qu’ils ont
été adoptés officiellement en 2007 par le W3C comme structure de base pour la future
version d’HTML. Les deux organismes collaborent depuis sur ce standard, apportant
chacun leur pierre à l’édifice.
Figure 1–2 Site du WHATWG (http://whatwg.org)
L’aventure des préfixes constructeurs
Les désormais célèbres préfixes CSS (-moz-, -webkit-, -ms-, -o-, etc.) ont été élaborés par le
W3C à l’époque des spécifications CSS 2.1, il y a donc plus de dix ans.
L’objectif était d’offrir aux constructeurs de navigateurs un moyen de tester des propriétés
ou des valeurs CSS encore non finalisées et de les proposer à leurs utilisateurs, ce qui
semblait être une bénédiction au vu de la lenteur du processus interne de standardisation
du W3C.
Chaque navigateur dispose par conséquent de sa propre variante de propriété et peut
l’implémenter à sa guise à condition qu’elle soit préfixée comme il se doit. Ainsi -moz-
animation pourrait en théorie différer complètement de la propriété animation officiellement
encore en brouillon. Il s’agit de l’interprétation de la propriété animation par le moteur de
rendu de Mozilla (Gecko).
Avec le recul, il est indubitable que l’existence des préfixes CSS a rétrospectivement fait
plus de mal que de bien, et ce, pour de multiples raisons.
Tout d’abord, les développeurs se sont rués sur cet eldorado sans forcément en
comprendre les implications, usant et abusant de propriétés non finalisées sans même le
savoir.
Par ailleurs, une tendance globale vise à ne préfixer que pour les navigateurs les plus en
vogue et les plus représentatifs. Par exemple, une multitude d’articles et tutoriels exposent
des exemples de code où les propriétés ne ciblent que -webkit- (notamment sur mobile où
ce moteur de rendu est roi), laissant en plan tous les autres navigateurs, soit par oubli, soit
par pure fainéantise. À un tel point qu’Opera s’est vu contraint de devoir reconnaître lui
aussi le préfixe -webkit- pour ne pas être considéré comme un « mauvais navigateur » (le
navigateur a par la suite abandonné le moteur de rendu Presto pour joindre Chrome et son
moteur Blink) !
Pour éviter de telles dérives mais aussi une maintenabilité complexe, un ensemble de
constructeurs a décidé d’abandonner petit à petit les préfixes CSS au profit d’une autre
solution plus robuste selon eux : la possibilité d’activer – ou non – certaines
fonctionnalités directement au sein du navigateur (via ce que l’on appelle les « flags »).
Cela signifie que si vous souhaitez tester un module tel que les Régions CSS (que nous
étudierons plus en détail dans ce livre), vous devez l’indiquer à votre navigateur et non
plus dans une surcharge de feuille de styles destinée à d’autres usagers.
L’activation de flags est une démarche pour le moins pertinente. En effet, dans l’idéal, ce
n’est pas au développeur de tester les propriétés en brouillon, mais bien au navigateur.
Cette opération devient alors totalement transparente pour l’utilisateur, mais réduit la
portée des toutes nouvelles fonctionnalités. En effet, il n’est dorénavant plus envisageable
de « forcer » vos visiteurs à les reconnaître (car vous n’allez pas demander à tous vos
clients d’aller activer un flag dans la configuration de leur navigateur).
Nous ne savons pas encore ce que l’avenir nous réserve dans ce vaste et complexe monde
de la standardisation : aujourd’hui, les préfixes n’ont pas complètement disparu et les flags
navigateurs ne sont encore exploités qu’avec parcimonie.
Une tierce solution vient d’ailleurs de voir le jour récemment. Elle est basée sur la règle
conditionnelle @supports (que l’on peut comparer au fameux Modernizr), permettant de
savoir si une propriété ou une valeur est implémentée par un navigateur. Nous reviendrons
bien évidemment sur cette fonctionnalité dans le chapitre 8 dédié aux styles conditionnels.
Figure 1–3 Page dédiée aux flags dans Google Chrome (chrome://flags)
La standardisation des CSS
Je vais vous avouer quelque chose : le titre de ce livre est quelque peu mensonger. En
réalité, CSS 3 n’existe pas, et CSS 4 non plus. Il s’agit là d’un abus de langage qui
caractérise « tout ce qui vient après CSS 2.1 ».
Voyez-vous, CSS 2.1 est la dernière version monolithique du langage. À compter de celle-
ci, les organismes chargés des spécifications CSS se sont accordés sur le fait qu’il devenait
très difficile et très long de produire et de maintenir des versions de cette ampleur.
Aussi a-t-on décrété que CSS serait désormais découpé en modules indépendants et
susceptibles d’avancer à leur propre vitesse. Beaucoup de modules ont démarré au niveau
3 parce qu’ils étaient dans la continuité des fonctionnalités présentes dans CSS 2.1. Mais
les modules émergents tels que Flexbox débutent bien évidemment au niveau 1 alors que
d’autres modules sont déjà au niveau 4 (notamment celui des sélecteurs).
En somme, il est temps d’abandonner la notion de version de CSS. CSS 2.1 est dépassé, et
CSS 3 n’existe pas. Je suggère simplement d’utiliser CSS, en toute simplicité.
À LIRE There is no such thing as CSS 4
L’article « There is no such thing as CSS 4 » par Tab Atkins Jr., éditeur principal des
spécifications CSS, explique très bien les raisons pour lesquelles le CSSWG (CSS
Working Group) a décidé de livrer CSS en modules indépendants.
http://bit.ly/css-4
Mais comment un module change-t-il de niveau ? Pour répondre à cette question, il faut
lorgner du côté de son processus de standardisation, qui est long et parsemé d’embûches.
Et pour cause, celui-ci passe par cinq étapes majeures.
Ici, seuls les éléments input placés directement à la suite d’un élément label se verront
ciblés par le sélecteur. Si un autre élément vient s’installer entre les deux, par exemple un
span, alors le sélecteur ne ciblera pas l’input. De même, si c’est un label à la suite d’un input,
le sélecteur ne fonctionnera pas.
Il est possible d’utiliser plusieurs fois le sélecteur d’adjacence directe dans un même
sélecteur afin d’augmenter la précision de celui-ci. Par exemple, admettons qu’on veuille
cibler les div enfants d’un conteneur à partir de la troisième, on peut envisager un sélecteur
tel que :
/* Les div successives à partir de la troisième, dans le conteneur */
.container div + div + div {
background: hotpink;
}
En réalité, on cible les div enfants de .conteneur dont les deux éléments voisins précédents
sont également des div. Naturellement, les div 1 et 2 ne peuvent donc pas être ciblées par
ce sélecteur, et il ne fonctionnera de toute façon que dans le cas où les div se suivent sans
élément entre elles.
*/
input:invalid ~ [type="submit"] {
opacity: .3;
pointer-events: none;
Figure 2–1
L’e-mail est invalide : le bouton de soumission est désactivé.
padding: 2px;
}
Je suis certain que vous avez au moins une fois déjà utilisé une règle similaire. Celle-ci
définit un ciblage des éléments input qui sont affublés d’un attribut type ayant pour valeur
text. On aurait pu tout aussi bien retirer input pour ne garder que la seconde partie
[type="text"]. Notez toutefois qu’il n’est nécessaire de mettre la valeur de l’attribut entre
guillemets que si celle-ci contient des caractères spéciaux tels que ., /… Cependant, on le
fait aussi dans un souci qualitatif.
Comme je l’expliquais, on peut aussi cibler les éléments simplement parce qu’ils ont un
certain attribut, peu importe la valeur assignée. Par exemple :
[disabled] {
cursor: not-allowed;
}
Cette règle a pour effet de redéfinir l’apparence du curseur sur tous les éléments ayant
l’attribut disabled. En effet, vu que l’attribut peut être déterminé en écrivant soit disabled,
soit disabled="disabled", il est préférable de simplement tester la présence de celui-ci plutôt
que de vérifier la valeur.
PERFORMANCE Quid de la performance des sélecteurs d’attribut
Il est de coutume de dire que les sélecteurs d’attribut sont coûteux en termes de
performance. On peut le comprendre dans la mesure où le DOM parser doit vérifier
chaque attribut pour le comparer à celui demandé par le sélecteur.
Néanmoins, les navigateurs progressent vite et bien. Si bien que ces remarques
autrefois pertinentes tendent aujourd’hui à être peu à peu obsolètes. Des tests de
performance ont été menés, y compris sur de vieux navigateurs tels qu’Internet
Explorer 8, et la différence se ressent assez peu (de l’ordre de la milliseconde). Donc,
à moins que vous n’utilisiez que des sélecteurs d’attribut, ne vous restreignez pas.
De plus, lorsqu’il s’agit d’optimisation CSS, il est préférable de se pencher sur les
sélecteurs morts (c’est-à-dire non utilisés) et les propriétés gourmandes plutôt que sur
le type des sélecteurs utilisés. C’est là de la suroptimisation, éventuellement
envisageable lorsque tout le reste a été traité.
Celui-ci cible tous les éléments de type a ayant un attribut rel contenant le mot author. C’est
gagné !
INTERNET EXPLORER 7 ET 8 Pas de support
Les versions d’Internet Explorer 7 et 8 ne supportent pas ce modulateur d’attribut.
Ce sélecteur ne va pas seulement cibler les liens avec un attribut hreflang égal à en ; les
valeurs en-US, en-EN ou encore en-licorne seront également prises en compte.
INTERNET EXPLORER 7 ET 8 Pas de support
Les versions d’Internet Explorer 7 et 8 ne supportent pas ce modulateur d’attribut.
Prenons un autre exemple : il est fort probable que vous ayez déjà mis en place une feuille
de styles dédiée à l’impression dans un de vos projets. Afin de donner du sens aux liens
une fois imprimés, on utilise souvent la règle suivante :
a[href]::after {
Celle-ci a pour effet d’afficher la source des liens entre parenthèses juste après eux. C’est
très pratique, néanmoins on se retrouve parfois avec des aberrations de type (#) ou pire
encore (javascript:return false). Certes, on ne devrait pas, mais pourtant ça arrive ! Du
coup, on va annuler la précédente règle pour ces cas de figure uniquement, grâce à notre
nouveau modulateur :
a[href^="#"]::after,
a[href^="javascript:"]::after {
content: "";
}
On se sert d’un sélecteur d’attribut pour détecter les liens vers des fichiers PDF, et du
pseudo-élément after pour afficher le terme (PDF) après le lien. Simple et efficace !
}
Les pseudo-classes de position
Les pseudo-classes ont toujours existé en CSS. D’abord sévèrement réduites avec
seulement :hover, :active et :focus, elles font désormais partie du quotidien de l’intégrateur
tant elles sont nombreuses. Et les spécifications de niveau 3 des sélecteurs n’y sont pas
pour rien puisqu’elles apportent des dizaines de nouvelles pseudo-classes pour rendre les
sélecteurs plus performants que jamais.
Une des brillantes nouveautés de ce module est de pouvoir sélectionner un élément en
fonction de sa position (comprendre, de son index) au sein de son parent. Est-il le premier
élément du parent ? le dernier ? le énième de son type ? le seul enfant ? Toutes ces
questions, dont les réponses ont longtemps été détenues par JavaScript, sont désormais
simples à traiter via les CSS uniquement.
Il est donc possible de cibler un élément selon s’il est le premier enfant du parent, ou le
dernier, ou encore s’il se trouve à une position bien particulière. Mais commençons par le
commencement, voulez-vous ?
La façon dont fonctionne cette pseudo-classe peut être un peu déroutante de prime abord,
car il ne faut pas l’appliquer au parent mais bel et bien à un enfant. En effet, l’exemple
suivant ne produit pas le même effet :
/* Une liste étant le premier enfant de son parent, quel qu’il soit */
ul:first-child {
color: red;
Et si vous désirez cibler le premier enfant d’un élément, quel que soit son type, vous
pouvez le faire tout aussi facilement en insérant un espace avant la pseudo-classe, qui fera
office de sélecteur universel (*).
/* Le premier enfant de .container, quel qu’il soit */
.container :first-child {
margin-top: 0;
Cela vient du fait que le navigateur va dérouler le DOM de manière linéaire. Lorsqu’il
rencontre une div, il l’ouvre et lit le premier enfant. S’il s’agit d’un paragraphe, il lui
applique alors la première règle, c’est-à-dire la couleur rouge. Seulement, à cet instant
précis, il est aussi le dernier enfant paragraphe de la div ; il lui applique donc
également la couleur bleue.
Maintenant, si le navigateur rencontre un nouveau paragraphe après le premier, alors il
doit appliquer la couleur bleue à celui-ci, car il est le dernier paragraphe du parent. Il
faut aussi qu’il recalcule la couleur du premier paragraphe qui n’est plus le dernier et
donc qui ne doit plus être bleu mais rouge. Et ainsi de suite jusqu’à fermer l’élément
pour de bon.
Sans parler de difficulté d’implémentation, on comprend néanmoins pourquoi Internet
Explorer a été capable de rapidement implémenter :first-child mais pas :last-child, qui
demande beaucoup plus de ressources.
background: deepskyblue;
}
Vous pouvez bien évidemment passer le nombre que vous désirez à ces fonctions. En fait,
elles acceptent même davantage d’arguments que simplement des nombres, comme nous
allons le voir immédiatement. Admettons que vous souhaitiez cibler tous les multiples de
3.
/**
* Tous les enfants d’un ul
*/
ul > li:nth-child(3n) {
background: deepskyblue;
Le paramètre 3n passé à la fonction indique que seuls les li dont l’index (démarrant à 0,
comme dans beaucoup de langages) est un multiple de trois seront ciblés par le sélecteur
(3, 6, 9, 12…).
Figure 2-3
li:nth-child(3n)
Si, en revanche, vous voulez cibler un élément sur trois tout en partant du premier, voici
comment procéder :
/* Éléments 1, 4, 7, 10, etc. */
li:nth-child(3n + 1) {
background: deepskyblue;
Figure 2-4
li:nth-child(3n + 1)
On peut tout à fait utiliser une soustraction pour déclarer un décalage vers l’arrière. Par
exemple, cibler un li sur trois avec un décalage en arrière de un :
/* Éléments -1 (inexistant), 2, 5, 8, 11, etc. */
li:nth-child(3n - 1) {
background: deepskyblue;
}
Figure 2-5
li:nth-child(3n - 1)
Et enfin, il est possible de passer un mot-clé à la fonction afin de cibler tous les éléments
pairs :
/* Éléments pairs */
li:nth-child(even) {
background: deepskyblue;
}
Figure 2-6
li:nth-child(even)
li:nth-child(odd) {
background: deepskyblue;
}
Figure 2-7
li:nth-child(odd)
Ces deux mots-clés sont extrêmement pratiques lors de la réalisation d’une table dite
« zebra », c’est-à-dire dont une ligne sur deux se démarque de la précédente dans le but de
faciliter la lecture. Plus besoin de recourir à JavaScript !
table tr {
background: white;
}
table tr:nth-child(even) {
background: #EFEFEF;
Figure 2-8
table tr:nth-child(even)
À part cela, tout autre type de valeur passé à la fonction rend le sélecteur nul car non
compris par le parser CSS du navigateur. En résumé, les fonctions :nth-child et :nth-last-
child acceptent :
li:first-child:nth-last-child(1) {
width: 100%;
}
/**
*/
li:first-child:nth-last-child(2),
li:first-child:nth-last-child(2) ~ li {
width: 50%;
}
/* Trois éléments */
li:first-child:nth-last-child(3),
li:first-child:nth-last-child(3) ~ li {
width: 33.3333%;
}
/* Quatre éléments */
li:first-child:nth-last-child(4),
li:first-child:nth-last-child(4) ~ li {
width: 25%;
}
/* Et ainsi de suite… */
Figure 2–9
Une façon astucieuse de dimensionner les éléments selon leur nombre au sein du parent
On peut donc appliquer des styles spécifiques aux éléments en fonction du nombre
d’enfants dans le parent sans recourir à JavaScript. Néanmoins, le code est relativement
dense et surtout fastidieux. On peut le simplifier avec un préprocesseur CSS, tel que Sass :
$max: 10;
Ce simple code Sass génère le code CSS que nous avons écrit précédemment sans avoir à
calculer manuellement la largeur (width), ni à recopier les mêmes lignes à plusieurs
reprises.
Ceci étant dit, parce qu’Internet Explorer 8 ne supporte pas le sélecteur :nth-last-child et
que le code CSS pour un tel effet est assez dense, il peut être préférable d’utiliser d’autres
méthodes de positionnement, tel que le modèle tabulaire.
PLUS D’INFOS
Pour plus d’informations sur cette technique de ciblage, veuillez vous référer à
l’article original en anglais de Lea Verou :
http://bit.ly/lea-verou-nth
Compatibilité des navigateurs pour :nth-child et :nth-last-
child
Tableau 2–11 Navigateurs desktop
width: 100%;
}
Cette pseudo-classe est intéressante dans certains cas où on désire appliquer une règle de
gestion particulière à un élément s’il n’a aucun voisin. Par exemple, un tel sélecteur peut
être pratique si vous manipulez un nombre d’éléments dynamiques et désirez mettre en
place une règle de gestion spéciale dans le cas où il n’y a qu’un seul élément.
CLARIFICATION
Cette pseudo-classe est strictement équivalente à la combinaison :first-child:last-child.
h2:first-of-type {
margin-top: 0;
/* Dernier li */
ul > li:last-of-type {
border: none;
}
/* Unique li */
ol > li:only-of-type {
width: 100%;
}
Si l’URL présente le hash #kiwis, toutes les règles CSS déclarées pour la pseudo-classe
:target sur ce titre seront alors interprétées.
h2:target {
color: hotpink;
La pseudo-classe :target est particulièrement utilisée dans les documentations, où elle met
en valeur une section lorsque celle-ci est « ciblée » par l’URL via un hash (déroulant ainsi
la page jusqu’à atteindre la partie désignée). De manière générale, elle est efficace
lorsqu’il s’agit de styler une partie de la page quand celle-ci est dans un état particulier.
Ces quelques lignes de CSS sont suffisantes pour appliquer les bons guillemets en
fonction du langage du document.
--margin: 1em;
Le support pour :not est excellent si on omet Internet Explorer 8. Malheureusement, je sais
bien que ce n’est pas toujours envisageable. Heureusement, il est souvent facile de faire la
même chose avec un peu plus de CSS (et de spécificité). Dans notre cas, nous pourrions,
par exemple, cibler tout d’abord les liens ayant un attribut target, puis les autres.
a[target='_blank'] {
a {
/* Comportement pour les autres liens */
}
/* Au niveau 0 */
h1 {
font-size: 30px;
/* Au niveau 1 */
section h1, article h1, aside h1, nav h1 {
font-size: 25px;
}
/* Au niveau 2 */
section section h1, section article h1, section aside h1, section nav h1,
article section h1, article article h1, article aside h1, article nav h1,
aside section h1, aside article h1, aside aside h1, aside nav h1,
nav section h1, nav article h1, nav aside h1, nav nav h1, {
font-size: 20px;
}
/* Au niveau 3 */
/* … finalement peut-être pas. */
h1 {
font-size: 30px;
}
/* Au niveau 1 */
font-size: 25px;
}
/* Au niveau 2 */
font-size: 20px;
}
/* Au niveau 3 */
font-size: 15px;
}
Comme vous pouvez le constater, le code est propre et lisible, mais ce n’est pas là une
révolution. En effet, cela ne permet pas d’effectuer quelque chose qui n’était auparavant
pas faisable avec CSS. On peut ranger ce sélecteur dans la catégorie des fonctionnalités
faisant office de sucre syntaxique.
select:disabled {
background: silver;
cursor: not-allowed;
}
background: white;
}
Autre fait intéressant, un élément a, link ou area avec un attribut href non nul est considéré
comme :enabled. Ainsi, pour éviter de sélectionner des éléments de manière involontaire, je
suggère de toujours bien qualifier la pseudo-classe, par exemple avec input, select et
textarea.
</fieldset>
Un champ de formulaire enfant d’un élément fieldset qui possède l’attribut disabled
(oui, c’est tout à fait possible !) sera ciblé par le sélecteur input:disabled, mais pas par
, dans la mesure où ce n’est pas l’élément qui est affublé de l’attribut
input[disabled]
disabled. Quoi qu’il en soit, dans la majorité des cas, le résultat est le même.
* et champs de formulaire
* avec attribut readonly
:read-only {
background: #EEE;
opacity: 0.8;
/**
* Tout élément avec attribut contenteditable
* et champs de formulaire
* sans attribut readonly
:read-write {
background: white;
}
Bien que certains navigateurs clament supporter ces pseudo-classes, sachez qu’il y a de
sérieuses différences entre les spécifications et ce qui est réellement implémenté. Si on
s’en tient aux spécifications, un élément pouvant être altéré par l’utilisateur mais désactivé
(disabled ou readonly), ou tout autre élément non altérable par lui, doit être considéré comme
:read-only.
Tableau 2–35 Différences d’interprétation des spécifications des pseudo-classes :read-write et :read-only
Tout d’abord, aucun navigateur ne considère un champ désactivé (soit parce qu’il a
l’attribut disabled, soit parce qu’il est enfant d’un fieldset qui l’a) comme :read-only. De
plus, Chrome et Safari ne traitent ni du cas des éléments affublés de contenteditable, ni de
celui d’un élément non éditable classique. De son côté, Opera considère un élément
éditable via contenteditable comme :read-only.
Bref, les navigateurs s’emmêlent, et c’est finalement Firefox qui s’en sort le mieux, avec
une seule erreur.
input:invalid {
background: red;
}
On peut effectuer la même chose avec les champs valides, même si cela présente bien
évidemment moins d’intérêt.
/* On bascule les champs valides en vert pour montrer à l’utilisateur que tout va bien */
input:valid {
background: green;
}
}
/* Une fois la checkbox cochée, on décale l’image d’arrière-plan pour afficher la partie
correspondant à une checkbox cochée */
input[type="checkbox"]:checked {
background-position: 0 16px;
Dans notre exemple, on applique une image d’arrière-plan aux checkboxes pour leur
donner une apparence personnalisée. C’est en réalité un sprite constitué de deux images de
16 × 16 pixels, l’une pour l’effet normal et l’autre pour l’effet coché. Lorsque la checkbox
est cochée, on décale l’image d’arrière-plan pour afficher la partie correspondant à une
checkbox cochée.
Il est même possible d’ajouter une troisième image dans le sprite pour l’effet de survol
(:hover) et de faire la même chose dans les CSS. Ainsi, grâce à la pseudo-classe :checked, on
peut facilement appliquer des styles en fonction de l’état d’une checkbox ou d’un bouton
radio. Notons que la pseudo-classe :checked s’applique également à l’option sélectionnée
d’un select.
De son côté, la pseudo-classe :indeterminate permet de cibler une checkbox si elle a sa
propriété indeterminate assignée à true. Néanmoins, il n’existe pas d’attribut HTML pour
cela ; le seul moyen pour qu’une checkbox ait l’état indéterminé est de passer par
JavaScript :
var checkbox = document.getElementById("checkbox");
checkbox.indeterminate = true;
Comme pour les états checked et standard, les navigateurs ont tous leur façon bien à eux de
présenter une checkbox indéterminée. Ceci étant dit, cette dernière est soit cochée, soit
décochée. L’état indéterminé n’est que visuel et, par conséquent, il remplace l’apparence
de la véritable valeur de la checkbox. En d’autres termes, une checkbox peut être
indéterminée (via JavaScript) tout en étant cochée ou décochée.
Du coup, dans le cas où le JavaScript serait en mesure d’éditer l’attribut indeterminate d’une
checkbox, CSS peut la cibler lors de son état indéterminé via la pseudo-classe éponyme :
input[type="checkbox"]:indeterminate {
/* La checkbox est indéterminée */
}
Un cas d’usage présenté par Ryan Seddon serait celui de la conception d’une arborescence
à l’aide de checkboxes.
• Si une checkbox parent a tous ses enfants cochés, alors elle est cochée.
• Si elle a tous ses enfants décochés, alors elle est décochée.
• Si elle a des enfants cochés et décochés, elle est indéterminée.
POUR EN SAVOIR PLUS
Pour plus d’informations sur la réalisation d’une arborescence à l’aide de checkboxes
par Ryan Seddon, référez-vous à son article original en anglais.
http://bit.ly/css-tree-checkboxes
Figure 2–10
On se sert de :default pour afficher l’option choisie par défaut.
Concrètement, cette règle utilise le pseudo-élément :after d’un label directement adjacent à
l’option par défaut d’un groupe de boutons radio pour afficher à l’écran Mon label (défaut).
Cette pseudo-classe s’emploie également sur les boutons de soumission de formulaire afin
d’indiquer quel est celui par défaut quand il y en a plusieurs.
border-color: green;
}
input[type="number"]:out-of-range {
border-color: red;
}
Évolution de la syntaxe
Bien que les spécifications CSS 3 n’apportent pas de nouveau pseudo-élément, elles
appliquent un changement important à la syntaxe en demandant à ce que les pseudo-
éléments soient préfixés par un double caractère deux-points (::), afin de les différencier
des pseudo-classes qui s’écrivent avec un seul caractère :.
Tableau 2–48 Évolution de la syntaxe des pseudo-éléments
background: hotpink;
color: #333;
Les plus grandes avancées qu’a connu CSS tournent toutes plus ou moins autour du sujet
de la mise en page des documents. Finis les float et inline-block, bonjour Flexbox, Grid et
compagnie !
Initialement, CSS a été inventé pour habiller des documents textuels, tels que des rapports
ou des documentations. L’idée principale était de pouvoir mettre des choses en évidence
via les feuilles de styles, comme des parties en exergue, des termes en couleurs, en gras ou
en italique, mais c’est à peu près tout.
Et nous voilà, presque vingt ans plus tard, à utiliser toujours le même langage pour mettre
en page des sites entiers cette fois, des applications, tout en tenant compte de critères aussi
divers que les tailles d’écrans, les capacités des navigateurs, et encore bien d’autres
facteurs.
Aussi la survie de CSS n’est-elle pas seulement due à son manque de concurrents
(rappelons toutefois la tentative échouée de Netscape pour faire adopter les JSSS, les
JavaScript Style Sheets) mais aussi à ses capacités d’adaptation. Les spécifications se sont
précisées, les fabricants de navigateurs rapprochés, afin de proposer de nouvelles
techniques de mise en page.
Jadis, les tableaux HTML servaient à dessiner la structure d’une page, technique qui a été
stigmatisée lorsque séparer la forme du contenu est devenu la bonne pratique, il y a bien
des années. C’est pourquoi les développeurs ont trouvé une autre technique pour mettre en
page des structures complexes : la propriété float.
Malheureusement, cette propriété n’a jamais été conçue pour cela et l’utiliser à des fins
structurelles ressemble davantage à un hack qu’à une véritable bonne idée. Plus tard, on a
vu arriver display: inline-block, qui a permis de nouvelles choses mais, dans le fond, le
problème n’était toujours pas résolu. En effet, une propriété ne suffit pas ; il faut de
nouveaux modules entiers pour accompagner les designers et développeurs dans la
construction de leurs applications.
Ces nouveaux modules, ils existent déjà. Certes, ils ne sont pas tous utilisables partout ou
dans leur intégralité, mais on peut dès aujourd’hui s’attacher à comprendre leur
fonctionnement afin d’être en mesure de les employer quand c’est possible. Parmi eux, il y
a le très populaire module Flexbox, alias « module de boîtes flexibles », mais aussi Grid,
le système de grilles natif, qui traîne un peu plus à se faire adopter par les navigateurs. On
parlera également de la distribution de contenus dans différentes zones avec les modules
de multicolonne et de régions CSS, sans oublier quelques autres outils annexes, mais non
moins efficaces, comme la propriété box-sizing ou la règle position: sticky.
Tout un programme donc pour ce chapitre qui est, selon moi, le pan de CSS le plus
important à l’heure actuelle. Mettre en page le contenu est un challenge chaque jour plus
difficile dans la mesure où les différents appareils mobiles, résolutions et tailles d’écrans
se font toujours plus nombreux.
Le modèle de boîte : retour aux sources avec box-
sizing
Ceux d’entre vous qui développaient des sites il y a de nombreuses années déjà se
souviendront sûrement que le modèle de boîte n’a pas toujours été ce qu’il est aujourd’hui.
Mais, pour ne léser personne, je vous propose une petite piqûre de rappel sur ce qu’est le
modèle de boîte – souvent appelé box model – et comment il fonctionne.
Le modèle de boîte, c’est ce qui définit la taille d’un élément. Parce que, finalement, quel
que soit l’élément que vous manipulez ou son apparence, cela reste, dans le fond, une
boîte. Un rectangle. Tous les éléments d’une page sont des rectangles. Pour agencer des
éléments, les navigateurs s’appuient sur ce fameux modèle de boîte. C’est grâce à lui
qu’ils peuvent assigner des dimensions aux éléments, travailler leurs marges, leurs
bordures et ainsi composer une page entière.
Une boîte en CSS se structure de la façon suivante : la zone de contenu est entourée par
les marges intérieures (padding), puis les bordures (border), puis les marges extérieures
(margin).
Les dimensions d’une boîte sont calculées comme ceci :
• largeur : width + padding-left + padding-right + border-left + border-right ;
• hauteur : height + padding-top + padding-bottom + border-top + border-bottom.
Figure 3–1
Modèle de boîte selon les spécifications officielles du W3C
Comme vous pouvez le constater, les marges extérieures (margin) sont exclues lorsqu’il
s’agit de calculer les dimensions d’un élément. En effet, elles serviront à séparer les
éléments les uns des autres, mais pas à déterminer leurs dimensions.
Si la largeur d’une boîte de type block n’est pas déterminée via la propriété width, elle va
s’étendre sur toute la largeur disponible. Dans le cas d’un élément de type inline, celui-ci
sera dimensionné en fonction de son contenu.
Si vous appliquez width: 100% à une boîte, ainsi qu’un padding à gauche et/ou à droite, et/ou
une bordure à gauche et/ou à droite, la boîte va « exploser » hors de son conteneur. En
d’autres termes, elle va déborder. Par exemple :
/**
*/
.element {
width: 200px;
height: 200px;
padding: 10px;
border: 5px solid;
Cet élément ne mesure pas 200 × 200 pixels comme on pourrait le croire en lisant les
propriétés width et height, mais 230 × 230. En effet, on ajoute 10 pixels de padding et 5 pixels
de border de chaque côté, soit 2 × 15 pixels. Et c’est là un problème important, notamment
dans le cas de réalisations fluides (Responsive Web Design) : on ne peut visiblement pas
assigner une largeur à un élément ainsi que des marges internes (padding) et/ou bordures
sans que celui-ci ne déborde de son conteneur.
C’est pour cela que la propriété box-sizing fait son entrée dans les spécifications CSS 3.
Elle permet de redéfinir l’algorithme utilisé pour calculer les dimensions des boîtes
(comme vu précédemment). Cette propriété accepte trois valeurs :
• la première est content-box, à savoir sa valeur par défaut et donc le comportement que l’on
vient de voir ;
• la deuxième, très peu utilisée, est padding-box. Elle retire les bordures du calcul des
dimensions des éléments. Autrement dit, si vous assignez une largeur à un élément,
ainsi qu’une marge intérieure et une bordure, cette dernière seulement ne sera pas prise
en compte dans la largeur de l’élément. Cette valeur n’a finalement que peu d’intérêt
puisqu’elle ne résout pas nos soucis. De plus, son support est assez faible et les
spécifications CSS remarquent ceci : « Note: The ‘padding-box’ value is at risk. »
Autrement dit, ce n’est pas une valeur recommandée, et je pense qu’à terme elle sera
dépréciée complètement.
• Ce qui nous amène à la troisième et la plus connue des trois valeurs de la propriété box-
sizing : border-box (qui restaure le modèle de boîte tel qu’il l’était sur Internet Explorer 6,
sous Quirks Mode). Celle-ci retire les marges internes (padding) et les bordures (border)
des calculs de dimensions afin que la largeur ne soit plus déterminée que par width et la
hauteur que par height.
Aussi les padding et les border sont-ils compris dans les dimensions de l’élément. Si on
reprend l’exemple précédent, notre élément mesure donc bien 200 × 200 pixels malgré ses
marges internes et ses bordures, puisque celles-ci sont déduites de la taille totale. On se
rend vite compte que c’est un système de calcul bien plus facile à appréhender.
*::after,
*::before {
box-sizing: border-box;
Cette règle peut sembler un peu agressive, mais l’appliquer à tous les éléments assure une
cohérence générale et beaucoup moins de maux de tête pour calculer les dimensions des
boîtes.
Vous remarquerez cependant qu’il est nécessaire d’ajouter manuellement les pseudo-
éléments dans la règle afin qu’ils bénéficient eux aussi du modèle de boîte approprié. En
effet, le sélecteur universel * ne comprend pas les pseudo-éléments.
JOURNÉE INTERNATIONALE DU BOX-SIZING 1er février
Suite à cet article de Paul Irish, qui demeure un des plus célèbres articles sur CSS à ce
jour, Chris Coyier a déclaré que le 1er février (jour de parution de l’article) de chaque
année serait la journée internationale du box-sizing (International box-sizing
Awareness Day).
http://bit.ly/paul-irish-border-box
http://bit.ly/css-tricks-border-box
Courant 2014, Chris Coyier a proposé une alternative un peu moins nucléaire qui consiste
à s’appuyer sur la cascade naturelle plutôt que sur le sélecteur universel :
html {
box-sizing: border-box;
}
*,
*::after,
*::before {
box-sizing: inherit;
Cette technique, bien que très semblable à celle de Paul Irish, a le mérite de permettre à
des composants d’employer un modèle de boîte différent de border-box sans devoir
réappliquer cette propriété à tous leurs éléments. Par exemple :
.component {
box-sizing: content-box;
Parce que tous les éléments héritent de la propriété box-sizing de leur parent, redéfinir celle
du composant suffit pour que tous ses enfants emploient ce modèle de boîte plutôt que
celui fixé pour l’élément html.
Compatibilité des navigateurs pour box-sizing
Tableau 3–1 Navigateurs desktop
Comment ça marche ?
Traditionnellement, le contenu d’un élément tient dans ce qu’on appelle sa content box.
Lorsqu’un élément utilise le module de colonage, on introduit un nouveau type de
conteneur entre la content box et le contenu : la column box. À savoir que ce conteneur est
l’affaire du navigateur et ne devrait donc pas du tout impacter vos développements,
d’autant qu’il est impossible de le manipuler, ni avec CSS, ni avec JavaScript. De fait, le
contenu d’un élément multicolonne s’écoule dans des colonnes, dont le nombre peut varier
en fonction des propriétés column-count et column-width.
Ces colonnes forment une rangée : comme des cellules de tableau, par exemple, les
colonnes sont arrangées dans le sens de lecture de l’élément « colonné ». Toutes les
colonnes ont la même largeur (potentiellement définie par column-width) : il n’est pas
possible d’avoir des colonnes de différentes largeurs au sein d’un élément colonné. De
même, toutes les colonnes sont de même hauteur et, vu la difficulté qu’on peut avoir à
maintenir des colonnes de même hauteur en CSS, c’est là une bonne nouvelle !
Les colonnes sont séparées par une gouttière déterminée par la propriété column-gap, qui
peut elle-même contenir une bordure définie par column-rule. Là encore, toutes les
gouttières et toutes les bordures sont identiques ; on ne peut pas différencier une gouttière
ou une bordure d’une autre. De plus, une règle ne sera visible que si les deux colonnes
qu’elle sépare ont du contenu.
Enfin, il n’est pas possible d’appliquer des styles à une colonne ; il s’agit d’un élément
invisible qu’on ne peut cibler. Autrement dit, on ne peut pas appliquer une couleur de fond
à une colonne en particulier, et une colonne n’est pas liée aux concepts du modèle de boîte
(padding, margin, border).
Syntaxe
Comme vous l’avez compris des explications précédentes, le module de colonage intègre
un certain nombre de propriétés pour ajuster le rendu selon les besoins : nombre de
colonnes, taille de la gouttière, lignes de séparation, etc., autant de caractéristiques sur
lesquelles vous avez la main afin de mettre en page vos contenus textuels.
Le nombre de colonnes dans une rangée est déterminé par la propriété column-count et/ou
column-width, ou encore par la propriété columns qui est un raccourci des deux précédentes.
Dans cet exemple, l’élément fait 100 pixels de large, ce qui ne laisse la place que pour
deux colonnes. Comme celles-ci occupent nécessairement toute la largeur de l’élément
colonné, chacune des deux colonnes sera légèrement plus grande que prévu : 50 pixels,
pour compléter l’espace restant.
Dans le cas contraire :
.columns {
width: 100px;
column-width: 105px;
}
La largeur des colonnes est plus grande que celle de l’élément, ce qui signifie qu’une seule
et unique colonne sera créée, et la largeur de celle-ci sera rabaissée à 100 pixels pour être
contenue dans l’élément.
Si la propriété column-width est si flexible, c’est bien pour permettre l’utilisation du
multicolonne dans le cas où différentes tailles d’écrans seraient impliquées.
Figure 3–2
Trois exemples de column-width
*/
.columns {
column-count: 3;
* column-width: 12em;
* column-count: auto
*/
columns: 12em;
columns: auto 12em;
/**
* column-width: auto;
* column-count: 2;
*/
columns: 2;
columns: 2 auto;
/**
* column-width: auto;
* column-count: auto;
*/
columns: auto;
column-gap: 1.5em;
}
Figure 3–4
Trois exemples de column-gap
Séparation des colonnes avec column-rule
Comme il est possible de séparer les colonnes les unes des autres, il est également
envisageable de définir une bordure verticale entre les colonnes. Celle-ci apparaîtra au
beau milieu de la gouttière déterminée par la propriété column-gap.
La propriété column-rule fonctionne exactement de la même façon que border, dans le sens
où elle est un raccourci pour les propriétés :
• column-rule-color : la couleur de la bordure (par défaut, currentcolor) ;
• column-rule-width : la largeur de la bordure (par défaut, medium) ;
• column-rule-style : le style de la bordure (par défaut, none).
.columns {
Parce que la valeur par défaut de column-rule-style est none, c’est la seule des trois propriétés
obligatoires pour afficher une ligne de séparation entre les colonnes. Du coup, la
déclaration suivante suffit :
.columns {
column-rule: solid;
}
Figure 3–5
Trois exemples de column-rule
.columns {
column-width: 8em;
}
/**
*/
.columns h2 {
break-before: column;
/**
* On force le changement de colonne
*/
.columns img {
break-after: column;
}
/**
* On évite le changement de colonne
break-inside: avoid-column;
column-span: all;
}
Figure 3–6
Envahissement des colonnes par les titres grâce à column-span
Équilibrage des colonnes avec column-fill
Par défaut, le contenu est équilibré sur toutes les colonnes, c’est-à-dire que le navigateur
essaie autant que possible de distribuer le contenu dans chaque colonne de manière
équitable en minimisant la différence de taille de contenu entre les colonnes.
De manière générale, c’est le comportement désiré, mais quand on fait face à du contenu
dynamique dont la longueur peut drastiquement varier, il est possible de rencontrer le cas
de figure où une seule courte phrase tente vainement de remplir toutes les colonnes, pour
un rendu aussi original qu’indésirable.
C’est pour cela que la propriété column-fill existe, qui elle aussi n’accepte que deux
valeurs :
• balance : valeur par défaut ;
• auto : cette valeur a pour effet de remplir les colonnes une à une. Notez qu’il est possible
que certaines colonnes soient partiellement remplies ou complètement vides.
Figure 3–7
Équilibrage des colonnes grâce à column-fill
columns: 4;
padding: 1em;
}
.list-container ul {
margin: 0;
}
Figure 3–8
Utilisation des colonnes pour alléger l’affichage d’une longue liste d’éléments
L’idée est donc de réaliser une mise en page identique sans utiliser de JavaScript,
uniquement avec des colonnes en CSS. Et effectivement, ça fonctionne plutôt bien même
si ce n’est pas extrêmement flexible.
Imaginons que nous souhaitions afficher une collection de médias (tels que des images
avec une légende, un auteur, une date, des catégories, et tout autre type d’information)
dans une grille composée de trois colonnes.
<div class="container">
<div class="item">…</div>
<div class="item">…</div>
<div class="item">…</div>
C’est tout ce dont nous avons besoin en termes d’HTML. Passons au CSS, qui est tout
aussi succinct.
/**
* Conteneur
* 1. Optionnel : valeur par défaut
*/
.container {
columns: 3;
column-gap: 1em; /* 1 */
padding: 1em;
}
/**
* Éléments
* 1. Optionnel : valeur par défaut
*/
.item {
width: 100%; /* 1 */
break-inside: avoid-column;
margin-bottom: 1em;
C’est tout ! Et encore, vous avez remarqué que deux déclarations ont été ajoutées dans le
but de rendre l’exemple plus explicite, mais elles sont en fait optionnelles puisqu’elles
reprennent littéralement les valeurs par défaut.
Notez également que nous empêchons un élément de démarrer en bas de colonne pour se
poursuivre sur la colonne suivante grâce à la déclaration break-inside: avoid-column.
Désormais, pour s’accommoder aux différentes tailles d’écran, il suffit de changer la
valeur de columns au sein d’une Media Query (détaillée au chapitre 8).
Figure 3–10
Un layout Masonry-like via CSS Columns
Comment ça marche ?
Le principe de base est simple : en appliquant la déclaration display: flex (ou inline-flex
bien que ce soit beaucoup moins usité) à un élément, celui-ci devient un flex container, ou
conteneur flexible. Cet événement a pour conséquence directe de créer un contexte de
boîte flexible, faisant de tous ses enfants directs des flex items, ou éléments flexibles.
Ceux-ci n’ont pas besoin d’avoir la propriété display définie.
Dans le cas où un conteneur flexible contiendrait du texte qui ne serait pas encapsulé dans
un enfant, celui-ci serait automatiquement encapsulé dans un élément flexible anonyme et
se comporterait donc comme les autres.
Toute mise en page flexible suit deux axes :
• l’axe principal (main axis), sur lequel les éléments se suivent ;
• l’axe secondaire (cross axis), perpendiculaire à l’axe principal.
Figure 3–12
Le modèle Flexbox et la notion d’axes, par le W3C
Syntaxe
Parce que le module Flexbox permet de faire beaucoup de choses, il est relativement
complexe à appréhender. Et pour cause, il n’introduit pas moins d’une douzaine de
nouvelles propriétés, chacune avec son set de valeurs autorisées. Autant de déclarations
qu’il est important de connaître dans le but de maîtriser le positionnement via le module
Flexbox.
Je vous propose de dédier l’intégralité de cette partie à l’explication des propriétés et à
leur syntaxe avant d’attaquer des exemples pratiques et de vrais cas d’usage pour voir
comment utiliser Flexbox dans un projet dès aujourd’hui.
C’est la propriété flex-direction qui va déterminer le sens de défilement pour les éléments
flexibles en définissant le main axis et sa direction.
Figure 3–13
Démonstration des différentes valeurs de la propriété flex-direction
Figure 3–14
Démonstration des différentes valeurs de la propriété flex-wrap
Déclaration raccourcie avec flex-flow
Cette propriété s’applique au conteneur flexible (parent).
flex-flow: <'flex-direction'> || <'flex-wrap'>
La propriété flex-flow est un raccourci pour les deux propriétés qu’on vient de voir : flex-
direction et flex-wrap. Sa valeur par défaut est donc row nowrap.
C’est généralement de cette façon qu’on introduit le sens de défilement d’un contexte
flexible puisque les deux propriétés sont complémentaires. Ne serait-ce qu’avec celles-ci
(et donc le raccourci flex-flow), il est possible de disposer une collection d’éléments dans le
sens de lecture souhaité en combinant certaines valeurs. Considérons un sens de lecture
classique en ltr :
• de gauche à droite et de haut en bas : flex-flow: row wrap ;
• de gauche à droite et de bas en haut : flex-flow: row wrap-reverse ;
• de droite à gauche et de haut en bas : flex-flow: row-reverse wrap ;
• de droite à gauche et de bas en haut : flex-flow: row-reverse wrap-reverse.
Nous avons vu comment définir les directions au sein d’un contexte Flexbox grâce aux
propriétés flex-direction et flex-wrap (ou flex-flow). C’est un excellent début mais ce n’est
pas suffisant. Le module de boîtes flexibles permet également de contrôler la distribution
de l’espace disponible sur chaque ligne. C’est le rôle de la propriété justify-content, qui
ressemble beaucoup à la propriété text-align dans son fonctionnement. Elle détermine
l’alignement des éléments sur l’axe principal à l’aide de ces cinq valeurs :
• flex-start (par défaut) : les éléments sont alignés vers le début de l’axe ;
• flex-end : les éléments sont alignés vers la fin de l’axe ;
• center : les éléments sont centrés sur l’axe ;
• space-between : les éléments sont distribués de manière équitable sur l’axe, de sorte que le
premier élément touche le début de l’axe et que le dernier touche la fin de l’axe
(justification) ;
• space-around : les éléments sont distribués de manière équitable sur l’axe de sorte que tous
les espaces soient identiques à l’exception des bords où un demi-espace est laissé avant
le premier élément et après le dernier.
Figure 3–15
Démonstration des différentes valeurs de la propriété justify-content
Pour terminer sur l’alignement des éléments dans un contexte flexible, il reste la propriété
align-content (à ne pas confondre avec align-items) qui détermine la répartition des lignes sur
l’axe principal dans le cas où il y a de l’espace disponible pouvant être réparti de diverses
façons.
• flex-start : les lignes sont groupées au début du conteneur.
• flex-end : les lignes sont groupées à la fin du conteneur.
• center : les lignes sont centrées dans le conteneur.
• space-between : les lignes sont distribuées de manière équitable de sorte que la première
ligne touche le début du conteneur et que la dernière touche la fin du conteneur
(justification).
• space-around : les lignes sont distribuées de manière équitable de sorte que tous les
espaces soient identiques à l’exception des bords où un demi-espace est laissé avant la
première ligne et après la dernière.
• stretch (par défaut) : les lignes s’étirent pour remplir l’espace restant.
Figure 3–17
Démonstration des différentes valeurs de la propriété align-content
Elle attend un unique entier, comme pour la propriété z-index, qui détermine l’ordre dans
lequel les éléments flexibles vont être alignés ; les éléments dont l’index est le plus proche
de 0 (la valeur par défaut) seront alignés les premiers. Dans le cas où plusieurs éléments
partageraient le même index, c’est bien l’ordre de la source qui déterminerait lequel vient
le premier.
Figure 3–18
La valeur flex-grow permet à un élément de prendre davantage de place que les autres.
Pour finir, la propriété flex-basis détermine la dimension par défaut de l’élément avant que
l’espace restant ne soit distribué. La valeur par défaut est auto, comme pour la propriété
width. En effet, lorsque le parser CSS du navigateur doit calculer la dimension des éléments
flexibles, il regarde d’abord la valeur des propriétés flex-*, notamment flex-basis, avant de
regarder width et height.
Dans le cas où la valeur de flex-basis serait à auto, alors c’est la valeur de la main size (width
ou height selon la direction du main axis) qui détermine la dimension de l’élément. Cette
valeur peut d’ailleurs tout à fait être auto également, ce qui impliquerait que l’élément soit
dimensionné par rapport à son contenu.
À l’exception de cette valeur, flex-basis se comporte comme width ou height dans le sens où
une valeur en pourcentage est calculée en fonction de la largeur du conteneur, et si celle-ci
n’est pas définie, alors elle est considérée comme auto.
Comme vous pouvez le constater, la propriété flex est un raccourci pour les trois propriétés
qu’on vient d’étudier : flex-grow, flex-shrink et flex-basis. Elle accepte :
• soit la valeur flex-grow uniquement ;
• soit la valeur flex-grow et la valeur flex-shrink ;
• soit la valeur flex-basis uniquement ;
• soit les trois valeurs simultanément.
On peut noter quelques valeurs courantes pour la propriété flex :
• flex: 0 auto : dimensionne les éléments en fonction des valeurs de width et height, les
empêche de s’étirer mais les autorise à se contracter si besoin ;
• flex: initial : identique à flex: 0 auto ;
• flex: auto : dimensionne les éléments en fonction des valeurs de width et height, et les rend
complètement flexibles ;
• flex: none : dimensionne les éléments en fonction des valeurs de width et height, et les rend
absolument inflexibles ;
• flex: <positive-number> : dimensionne les éléments en fonction de l’espace restant dans le
conteneur.
La propriété align-self, comme son nom l’indique, permet à un élément unique de défier
l’alignement déterminé par la propriété align-items et de suivre son propre alignement. Les
valeurs autorisées sont donc strictement les mêmes que celles de la propriété align-items,
aussi je vous invite à lire la partie concernant cette propriété pour comprendre le sens des
valeurs autorisées.
Bien qu’elle puisse sembler obsolète, il est important d’avoir une propriété permettant
d’aligner un élément dans un état particulier. Il peut arriver que vous vouliez qu’un
élément ressorte et se comporte différemment des autres. C’est grâce à align-self que vous
allez y parvenir.
<li><a href="#">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Products</a></li>
<li><a href="#">Contact</a></li>
</ul>
padding: 0;
margin: 0;
background: deepskyblue;
}
.navigation a {
display: block;
padding: 1em;
text-decoration: none;
text-align: center;
Maintenant, il ne nous reste plus qu’à appliquer nos propriétés relatives à Flexbox.
Référez-vous aux commentaires pour bien comprendre ce qu’il se passe :
/**
* 1. On définit un contexte Flexbox sur le parent.
/* Grands écrans */
.navigation {
display: flex; /* 1 */
flex-flow: row wrap; /* 2 */
justify-content: flex-end; /* 3 */
}
/* Écrans moyens */
.navigation {
justify-content: center; /* 4 */
}
/* Petits écrans */
Et voilà ! Le reste n’est que de l’habillage pour satisfaire l’esthétique. Toutes les questions
de layout sont gérées par Flexbox uniquement. Cependant, cet exemple est relativement
simple puisqu’il ne faisait usage que des propriétés applicables au conteneur.
Figure 3–19
Notre menu sur un écran de grande taille
Figure 3–20
Notre menu sur un écran de taille moyenne
Figure 3–21
Notre menu sur un écran de petite taille
<header class="header">
<article class="main">
<!-- Contenu de l’article -->
</article>
<aside class="aside aside-1">
<!-- Contenu de la première barre latérale -->
</aside>
</aside>
<footer class="footer">
<!-- Contenu du pied de page -->
</footer>
</div>
Pour garder l’exemple le plus simple possible, on se passera des styles par défaut et
purement destinés à l’esthétique. Démarrons immédiatement avec la définition du contexte
flexible :
.wrapper {
display: flex;
flex: 100%;
}
La règle flex: 100% définit à tous les enfants directs du conteneur une largeur de 100 % afin
qu’ils occupent toute la largeur offerte par la fenêtre. Ce comportement permet de servir
les éléments les uns en dessous des autres (dans l’ordre du flux) sur les plus petits écrans.
Figure 3–22
Notre layout sur un écran de petite taille
Nous allons l’adapter quand le viewport est suffisamment grand pour permettre un
changement de layout :
@media (min-width: 600px) {
.aside {
flex: 1 auto;
}
}
Cette règle fait en sorte que les deux éléments .aside partagent une ligne, de manière
équitable. On a donc le rendu suivant.
Figure 3–23
Notre layout sur un écran de taille moyenne
Pour finir, traitons du cas où l’écran est suffisamment large pour permettre aux barres
latérales d’encercler le conteneur principal.
@media (min-width: 800px) {
.main {
flex: 3 0px;
}
.aside-1 {
order: 1;
}
.main {
order: 2;
.aside-2 {
order: 3;
.footer {
order: 4;
On spécifie à l’élément .main qu’il doit prendre trois fois plus d’espace sur la ligne que les
barres latérales (pour lesquelles la valeur est 1). Ensuite, on utilise la propriété order pour
définir l’ordre de rendu des éléments. On affiche les éléments dans l’ordre voulu sans
modifier le DOM, respectant ainsi une structure sémantique propre.
Figure 3–24
Notre layout sur un écran de grande taille
*/
.parent {
display: flex; /* 1 */
justify-content: center; /* 2 */
align-items: center; /* 3 */
}
</form>
A priori, nous n’avons pas besoin de davantage de markup pour un simple formulaire de
recherche. Il ne nous reste plus qu’à appliquer le CSS qui ne va pas être bien plus long :
/**
* Formulaire
*/
form {
display: flex; /* 1 */
margin: 1em;
padding: 1em;
border: 1px solid silver;
}
/**
* Champ de recherche
* 1. Occupe l’espace restant
* 2. Gouttières de chaque côté du champ
*/
input {
flex: 1; /* 1 */
margin: 0 1em; /* 2 */
}
Terminé ! Aucune dimension fixe n’a été assignée ; on peut donc tout à fait modifier
l’intitulé du libellé ou du bouton, et la taille du champ s’adaptera automatiquement. Merci
Flexbox !
Figure 3–25
Formulaire sur une seule ligne sans dimensions fixes grâce à Flexbox
* 2. Firefox 21-
* 3. Internet Explorer 10
@mixin flexbox {
display: -webkit-box; /* 1 */
display: -moz-box; /* 2 */
display: -ms-flexbox; /* 3 */
display: -webkit-flex; /* 4 */
display: flex; /* 5 */
}
@mixin flex($values) {
-webkit-box-flex: $values; /* 1 */
-moz-box-flex: $values; /* 2 */
-ms-flex: $values; /* 3 */
-webkit-flex: $values; /* 4 */
flex: $values; /* 5 */
@mixin order($val) {
-webkit-box-ordinal-group: $val; /* 1 */
-moz-box-ordinal-group: $val; /* 2 */
-ms-flex-order: $val; /* 3 */
-webkit-order: $val; /* 4 */
order: $val; /* 5 */
}
.container {
@include flexbox;
}
/* Éléments flexibles */
.item {
@include flex(1 200px);
@include order(2);
}
Le Grid Layout
Dans la section précédente, nous avons eu un bel aperçu de ce que l’avenir proche nous
réserve en matière de mise en page CSS avec Flexbox mais ce n’est pas tout ! Il existe
également un module dédié à la création de grilles natives, appelé « CSS Grid Layout
Module ».
L’idée principale derrière ce nom intrigant est de définir un élément comme une grille
avec des colonnes et des rangées, un peu à la manière d’un document Excel si on veut
schématiser. Ensuite, les enfants de cet élément pourront être distribués dans les cellules
de cette grille, et pourquoi pas s’étendre sur plusieurs cellules si besoin.
Ce schéma, au-delà d’être plus simple à appréhender qu’un système de float ou inline-
block, permet surtout de dissocier complètement le contenu des styles. En effet, l’atout
majeur du Grid Layout est qu’il rend la mise en page intégralement indépendante de la
structure du document et de l’ordre de la source.
CLARIFICATION Grid ou Flexbox ?
On est tout à fait en droit de se demander quel est l’intérêt d’avoir à la fois Flexbox et
Grid. Après tout, les deux modules peuvent être utilisés dans des cas plus ou moins
similaires. En réalité, ils n’ont pas été prévus pour les mêmes tâches. Le Grid Layout a
été explicitement inventé pour permettre aux designers et développeurs de mettre en
page des documents de manière totalement indépendante de la structure HTML.
De son côté, le module Flexbox est plus adapté pour gérer le comportement de
composants plus petits. C’est l’outil idéal pour la gestion de l’espacement et de
l’alignement des éléments au sein d’un conteneur. Autrement dit, de manière optimale,
on utiliserait Grid pour la mise en page globale et Flexbox pour le comportement des
modules.
En résumé, et pour reprendre les termes de Tab Atkins Jr., auteur des spécifications
des deux modules : Flexbox est prévu pour les layouts sur une dimension, c’est-à-dire
ceux dont l’objectif est d’aligner les éléments sur une ligne (possiblement divisée en
plusieurs lignes si l’espace manque). Grid, en revanche, est prévu pour les layouts à
deux dimensions.
Comment ça marche ?
Pour faire simple, une grid est un tableau dont la structure est intégralement gérée par le
CSS.
Lors de l’utilisation du Grid Layout, le contenu d’un grid container, autrement dit un
élément auquel est appliqué la déclaration display: grid, est disposé selon une grille fictive.
Cette grille est le résultat de l’intersection de lignes (techniquement appelées grid lines)
qui divisent le conteneur en cellules (grid cells) qui pourront accueillir les enfants de
celui-ci.
Figure 3–26
Une ligne servant à la division de la grille en lignes et en colonnes
Figure 3–27
Une rangée de la grille
Pour mettre en place une grille, on affuble donc un élément de la valeur grid (ou inline-grid
en fait) pour la propriété display, puis on définit un zoning.
Celui-ci peut être déclaré de différentes façons : en nommant les zones, ce qui est
généralement le plus simple, mais on ne connaît pas toujours toutes les zones à l’avance
(grille flexible, contenu dynamique, etc.).
Du coup, on peut également spécifier les dimensions des rangées et des colonnes (là
encore, de bien des manières, comme nous le verrons plus loin). On peut aussi mixer les
deux en nommant les lignes cette fois (pas les zones) tout en renseignant les dimensions
des rangées et des colonnes.
Quoi qu’il en soit, une fois la grille déclarée, on peut placer les enfants dans les cellules
générées par l’intersection des rangées et des colonnes (elles-mêmes résultant de
l’intersection des lignes).
Figure 3–28
Une cellule de la grille, créée par l’intersection des lignes
Figure 3–29
Une zone de la grille, (éventuellement) composée de plusieurs cellules
Le Grid Layout est très complet : il implémente une pléthore de nouveaux concepts et
intègre de nombreuses propriétés, certaines s’appliquant au conteneur, d’autres aux
enfants, ainsi qu’une nouvelle unité de mesure (fr).
Pour bien comprendre les tenants et aboutissants du Grid Layout, il est important de voir
toutes ces propriétés ainsi que leur fonctionnement, mais avant même de s’y atteler,
permettez-moi de vous proposer un petit exemple.
* 1. Dimensions
* 2. Contexte de grille
* 3. Rangées (partage de l’espace en 3)
*/
.morpion {
width: 300px; /* 1 */
height: 300px; /* 1 */
display: grid; /* 2 */
grid-template-rows: 1fr 1fr 1fr; /* 3 */
grid-template-columns: 1fr 1fr 1fr; /* 4 */
Si vous ne comprenez pas encore la valeur 1fr, c’est tout à fait normal ; nous la verrons
plus en détail un peu plus loin. Sachez simplement qu’il s’agit d’une fraction de l’espace
restant. Dans notre cas, ça signifie simplement que toutes les cellules seront de même
taille.
Pour l’instant, nous n’avons fait qu’initialiser une grille de 3 par 3 cellules mesurant
chacune 100 pixels de côté (un tiers de 300 pixels). En revanche, nous n’avons pas placé
les véritables cellules (enfants de .morpion) dans la grille. C’est justement notre prochaine
étape.
/**
* Première rangée
* 1. Première colonne
* 2. Deuxième colonne
* 3. Troisième colonne
*/
.cellule-1-1 { grid-area: 1 / 1; } /* 1 */
.cellule-1-2 { grid-area: 1 / 2; } /* 2 */
.cellule-1-3 { grid-area: 1 / 3; } /* 3 */
/**
* Deuxième rangée
* 1. Première colonne
* 2. Deuxième colonne
* 3. Troisième colonne
*/
.cellule-2-1 { grid-area: 2 / 1; } /* 1 */
.cellule-2-2 { grid-area: 2 / 2; } /* 2 */
.cellule-2-3 { grid-area: 2 / 3; } /* 3 */
/**
* Troisième rangée
* 1. Première colonne
* 2. Deuxième colonne
* 3. Troisième colonne
*/
.cellule-3-1 { grid-area: 3 / 1; } /* 1 */
.cellule-3-2 { grid-area: 3 / 2; } /* 2 */
.cellule-3-3 { grid-area: 3 / 3; } /* 3 */
À nouveau, si la syntaxe vous semble encore obscure, pas d’inquiétude, c’est tout à fait
normal. Nous avons tout le temps d’approfondir tout ça.
Figure 3–30
Notre grille de morpion construite grâce à Grid Layout
Avant de passer à la suite, voyons une façon différente d’aborder notre mise en page, en
nommant les zones cette fois :
/**
* 1. Dimensions
* 2. Contexte de grille
*/
.morpion {
width: 300px; /* 1 */
height: 300px; /* 1 */
display: grid; /* 2 */
grid-template-rows: 1fr 1fr 1fr; /* 3 */
grid-template-areas:
"top-left top top-right"
/**
* Première rangée
* 1. Coin supérieur gauche
* 2. Milieu en haut
* 3. Coin supérieur droit
*/
.cellule-1-1 { grid-area: top-left; } /* 1 */
* Deuxième rangée
* 1. Milieu à gauche
* 2. Milieu
* 3. Milieu à droite
*/
.cellule-2-1 { grid-area: left; } /* 1 */
/**
* Troisième rangée
* 1. Coin inférieur gauche
* 2. Milieu en bas
* 3. Coin inférieur droit
*/
Le résultat est strictement le même que pour l’exemple précédent, mais la syntaxe est plus
élégante comme ceci.
Et voilà ! Nous venons d’utiliser Grid Layout pour la première fois ; pas si compliqué
finalement. Notre exemple était volontairement enfantin, mais il a le mérite de montrer les
rudiments du module. Maintenant que vous êtes opérationnels, nous allons pouvoir passer
aux choses sérieuses !
RESSOURCE Grid by Example
Rachel Andrew, réputée pour ses prouesses avec Grid Layout depuis son apparition,
est l’auteur du projet Grid by Example dont le but est de sensibiliser les développeurs
à ce nouveau système de mise en page, et de l’expliquer à l’aide d’exemples concrets.
Évidemment, je ne peux que vous recommander ce projet.
http://bit.ly/grid-by-example
Figure 3–31 Le projet Grid by Example de Rachel Andrew
Cette propriété s’applique bien évidemment au conteneur. Elle est la base du module
puisqu’elle initialise un contexte de grille. Attention toutefois, les conteneurs de grille sont
légèrement différents de conteneurs blocs classiques, en cela que :
• les propriétés du module multicolonne n’ont aucun effet sur le conteneur ;
• les propriétés clear, float et vertical-align n’ont aucun effet sur les enfants ;
• les pseudo-éléments ::first-letter et ::first-line ne s’appliquent pas au conteneur.
Vous l’aurez compris, il est tout à fait possible d’instancier une grille en ligne via la valeur
inline-grid. Cependant, si l’élément est affublé de la propriété float, ou qu’il est positionné
de manière absolue, la valeur traitée pour la propriété display ne serait pas inline-grid mais
bien grid. Ne nous perdons pas inutilement dans des détails et tâchons d’avancer.
Définir la grille
Avant d’initialiser la grille, il faut la définir. Nous l’avons vu en introduction, il y a bien
des manières de procéder. Nous n’allons toutefois pas commencer par la plus simple.
Pour définir une grille, il est possible de spécifier ses dimensions en termes de colonnes et
de rangées. Pour cela, il y a deux propriétés principales :
• grid-template-rows ;
• grid-template-columns.
Ensemble, ces propriétés définissent la grille dite « explicite » du conteneur. À noter qu’il
existe également la propriété grid-template qui n’est qu’un raccourci pour définir ces
propriétés (ainsi que grid-template-areas qu’on verra plus loin) simultanément.
Commençons par les choses les plus simples : définir le nombre de colonnes et le nombre
de rangées dans notre grille. Je vous passe la syntaxe officielle qui est assez imbuvable, et
je résume.
Les propriétés grid-template-rows et grid-template-columns acceptent une liste de valeurs
séparées par des espaces correspondant à d’éventuels identifiants de colonnes/rangées
entourés de parenthèses (voir la section « Nommage », page 95) et à des valeurs de
dimensionnement exprimées :
• en longueurs fixes (42px, 13.37pt…) ;
• en longueurs relatives (42vw, 13.37em…) ;
• en pourcentage ;
• via la fonction calc() (voir chapitre 5) ;
• via la fonction repeat() (voir section suivante) ;
• via la fonction minmax() ;
• en valeurs prédéfinies (min-content, max-content ou auto, qui n’est autre qu’un alias pour
minmax(min-content, max-content)) ;
Parce que la valeur est constituée d’une liste de trois valeurs séparées par des espaces, cela
signifie que la grille sera composée de trois colonnes :
• une première colonne de 100 pixels de large ;
• une deuxième colonne qui occupera suffisamment d’espace pour que son contenu le plus
large apparaisse sur une même ligne ;
• une dernière colonne qui occupera la moitié de la largeur de l’élément.
Figure 3–32
grid-template-columns: 100px max-content 50%;
Comme vous pouvez le voir, on peut mixer les unités fixes avec les unités relatives et
laisser soin au navigateur de faire fonctionner tout cela. Plutôt pratique n’est-ce pas ?
Figure 3–33
Les zones de 1 em sont réservées aux marges, et les zones de 100 pixels au contenu.
Dans notre exemple, la fonction répète trois fois 1em 100px, suivi d’une dernière colonne à
1em, aussi la déclaration aurait pu s’écrire ainsi :
Autrement dit, la fonction repeat() est tout à fait optionnelle et n’est là que pour alléger un
peu l’écriture, mais aussi la lecture du code. N’hésitez pas à l’utiliser quand vous en avez
l’occasion !
Nommage
display: grid;
grid-template-columns: (first nav) 200px (main) 1fr (last);
Figure 3–34
La déclaration de grille précédente génère ce layout.
Dans cette grille de deux colonnes sur trois rangées (deux longueurs dans la valeur de grid-
template-columns et trois longueurs dans la valeur de grid-template-rows) :
• la première ligne verticale peut être référée comme first, nav ou encore 1 ;
• la deuxième ligne verticale par main ou 2 ;
• la dernière ligne verticale par last ou 3 ;
• la première ligne horizontale par first, header ou 1 ;
• la deuxième ligne horizontale par main ou 2 ;
• la troisième ligne horizontale par footer ou 3 ;
• la dernière ligne horizontale par last ou 4.
ATTENTION Lignes nommées et Sass
J’ai réalisé que Sass retire les parenthèses autour des noms de ligne au moment de la
compilation (dans la mesure où les parenthèses délimitent une liste de valeurs et sont
optionnelles en Sass).
Il y a bien des manières de contourner le souci, entre autres celle consistant à
interpoler les valeurs (ou utiliser la fonction unquote) :
.grid {
display: grid;
grid-template-columns:
#{"(first nav)"} 150px
#{"(main)"} 1fr
#{"(last)"};
grid-template-rows:
#{"(first header)"} 50px
#{"(main)"} 1fr
#{"(footer)"} 50px
#{"(last)"};
}
Assez indigeste, je vous l’accorde. Le mieux reste de passer par un mixin pour éviter
de devoir gérer ça :
http://bit.ly/grid-sass-fix
Nommer les zones
Il est également possible de nommer directement les zones de la grille pour lui donner
davantage de sens. Attention, ce ne sont pas les éléments que l’on nomme, mais bien les
zones de la grille. Par la suite, ces éléments sont insérés dans les zones via leur nom.
C’est la propriété grid-template-areas qui permet de nommer les différentes sections d’une
grille, à la différence des propriétés grid-template-rows et grid-template-columns qui nomment
les lignes qui séparent les rangées et colonnes.
grid-template-areas: none | <string>+
Chaque chaîne de caractères (encapsulée dans des guillemets simples ou doubles) présente
dans la valeur de grid-template-areas correspond à une rangée. Ces chaînes sont ensuite
parsées pour déterminer les colonnes selon les règles suivantes :
• une séquence de caractères représente une cellule nommée selon ladite séquence ;
• une séquence d’espaces/tabulations ne produit rien ;
• un point (.) représente une cellule anonyme.
REMARQUE Longueurs identiques
Toutes les chaînes doivent avoir le même nombre de tokens. Aussi, dans le cas où on
souhaite faire en sorte qu’une zone nommée s’étende sur plusieurs colonnes
consécutives, il faut répéter le nom de cette zone pour chaque colonne.
Reprenons notre exemple précédent (deux colonnes, trois rangées) en nommant les zones :
grid-template-areas: "header header"
"nav main"
"footer footer";
Parce que les zones header et footer sont répétées dans leur chaîne respective, elles
s’étendent toutes deux sur toute la longueur de la grille.
Si on souhaite ajouter à ces zones des dimensions, on utilise les propriétés grid-templaterows
et grid-template-columns :
grid-template-columns: 150px 1fr;
Rangée Colonne
Début Ligne horizontale de début Ligne verticale de début
Fin Ligne horizontale de fin Ligne verticale de fin
Envahissement Envahissement des rangées Envahissement des colonnes
Si au moins deux valeurs parmi début, fin et envahissement sont renseignées (ou
calculées), alors la troisième est considérée comme définie également (calculée). En effet,
si on connaît :
• la ligne de début et la ligne de fin, on peut calculer le nombre d’emplacements occupés
par l’élément ;
• la ligne de début et le nombre d’emplacements occupés par l’élément, on peut calculer
la ligne de fin ;
• la ligne de fin et le nombre d’emplacements occupés par l’élément, on peut calculer la
ligne de début.
Le tableau suivant résume les conditions selon lesquelles le placement d’un élément est
considéré comme défini ou automatique.
Tableau 3–9 Conditions selon lesquelles un emplacement/une étendue est défini automatiquement ou non
Position Envahissement
Envahissement explicite, implicite ou par
Défini Au moins une ligne spécifiée défaut
Le placement peut être renseigné de diverses façons : soit en termes d’index, soit avec des
identifiants. Quoi qu’il en soit, ce sont les propriétés grid-row-start, grid-row-end, grid-column-
start et grid-column-end (ou les raccourcis grid-column, grid-row et grid-area) qui le permettent.
Vous êtes perdu ? Ne vous en faites pas, tout va bientôt devenir limpide !
grid-row-end: <grid-line>
grid-column-start: <grid-line>
grid-column-end: <grid-line>
où:
<grid-line> =
auto |
<custom-ident> |
+--+--+--+--+--+--+--+--+
| | | | | | | | |
A B C A B C A B C
| | | | | | | | |
+--+--+--+--+--+--+--+--+
Et maintenant, quelques exemples pour s’assurer que vous avez tout compris :
/* Démarre à la ligne 4
*/
grid-column-start: 4;
grid-column-end: auto;
/* Termine à la ligne 6
* N’occupe qu’un seul emplacement (défaut)
*
* Résultat : entre les lignes 5 et 6
*/
grid-column-start: auto;
grid-column-end: 6;
/* Démarre à la première ligne C
*/
grid-column-start: C;
grid-column-end: C -1;
*/
grid-column-start: C;
grid-column-end: span C;
grid-column-start: span C;
grid-column-end: C -1;
/* Démarre à la ligne 5
*
* Résultat : entre les lignes 5 et 9
*/
grid-column-start: 5;
grid-column-end: C -1;
/* Démarre à la ligne 5
*/
grid-column-start: 5;
grid-column-end: span C;
grid-column-start: B 2;
grid-column-end: span 1;
/* Démarre à la ligne 8
* Termine à la ligne 8
* -> end doit être supérieur à start, donc auto
*
grid-column-end: 8;
Leur syntaxe est simple : elle accepte une ou deux valeurs. Dans le cas où une seule valeur
est renseignée, celle-ci est appliquée pour start et end simultanément (si ce sont des
identifiants, sinon auto). Si deux valeurs séparées par un slash (/) sont renseignées, alors
elles seront respectivement appliquées à start et à end.
On peut donc réécrire la déclaration suivante :
grid-column-start: 4;
grid-column-end: 8;
/* … ou plus simplement */
grid-column: 4 / 8;
Si une seule valeur est renseignée et qu’il s’agit d’un identifiant, alors les quatre valeurs
valent cet identifiant, sinon auto.
On peut donc réécrire la déclaration suivante :
grid-row-start: 3;
grid-row-end: 3;
grid-column-start: 2;
grid-column-end: auto;
/* … ou plus simplement */
grid-area: 3 / 2 / 3;
Si on résume, grid-area peut être un raccourci pour grid-row et grid-column, sachant que grid-
row est un raccourci pour grid-row-start et grid-row-end et que grid-column est un raccourci pour
grid-column-start et grid-column-end. En somme, on peut dresser le tableau récapitulatif
suivant.
Tableau 3–10 Propriétés raccourcies du Grid Layout
Chose intéressante à noter, utiliser la valeur auto fera en sorte que l’élément soit placé de
manière automatique dans la grille au premier emplacement disponible.
C’est un comportement qui peut s’avérer pratique, toutefois si vous souhaitez laisser la
main au navigateur pour placer tous les éléments, rendez-vous dans la section suivante.
Le terme subgrid
Faisons un petit aparté avant de s’attaquer aux exemples pour signaler qu’un élément de
grille peut lui-même être un conteneur de grille s’il est affublé de la déclaration display:
grid. Dans ce cas, le contexte de son contenu sera totalement indépendant de celui auquel il
participe.
Cependant, dans certains cas, il peut être nécessaire d’aligner le contenu de plusieurs
éléments de grille. Aussi, un conteneur de grille imbriqué dans une autre grille peut
déléguer la définition de ses colonnes et rangées à son parent, faisant de lui une subgrid.
Dans ce cas, les éléments de la grille imbriquée participent au dimensionnement et
contexte de la grille supérieure, rendant possible l’alignement du contenu des deux grilles.
Pour ce faire, il suffit de donner à l’élément la déclaration grid: subgrid. Par exemple :
/* Grille supérieure */
ul {
display: grid;
}
/* Éléments de grille */
li {
/* Grilles imbriquées… */
display: grid;
/* … mais liées */
grid: subgrid;
Une grille imbriquée fonctionne de la même façon qu’un conteneur de grille habituel si ce
n’est que :
• le nombre de lignes est donné par le grid span plutôt que par grid-template-rows et grid-
template-columns ;
• le placement des éléments de la subgrid via les propriétés de placement est contenu dans
la subgrid uniquement ; par exemple, les index de position démarrent à la première ligne
de la subgrid, et non à la première de la grille parent ;
• les éléments de la subgrid participent au dimensionnement de la grille parent, et y sont
alignés. De fait, la somme des marges intérieures et extérieures et des bordures des
éléments de la subgrid sont comptabilisées dans le dimensionnement de la rangée et de la
colonne dans lesquelles se trouve la subgrid ;
• la subgrid est systématiquement étirée, en cela que les déclarations de taille (width, height)
et d’alignement individuel (align-self) n’ont aucun effet.
<li class="grid-item">
<img src="…" alt="…" />
</li>
<!-- … avec autant de li qu’on le souhaite. -->
</ul>
.grid {
list-style: none;
padding: .5em;
display: grid; /* 1 */
grid-auto-flow: row; /* 2 */
grid-template-columns: 1fr; /* 3 */
}
/**
* Éléments de grille
*/
.grid-item {
margin: .5em;
}
/**
* Premier élément de grille
* grid-columns: 1 / span 2;
* grid-rows: 1 / span 2;
* Ou encore :
* grid-column-start: 1;
* grid-column-end: span 2;
* grid-row-start: 1;
* grid-row-end: span 2;
*/
.grid-item:first-of-type {
}
/**
* Images
*/
.grid-item img {
display: block;
height: auto;
max-width: 100%;
margin: 0 auto;
Nous avons là les bases de notre grille, en revanche nous n’avons pas encore appliqué le
colonage en fonction de la largeur de l’écran.
@media (min-width: 400px) {
}
@media (min-width: 1200px) {
.grid { grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
}
@media (min-width: 1400px) {
.grid { grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
Figure 3–35
Notre galerie d’images sur un écran de petite taille
Figure 3–36
Notre galerie d’images sur un écran de taille moyenne
Figure 3–37
Notre galerie d’images sur un écran de large taille
}
}
<article class="main">
<nav class="nav">
<!-- Liste de navigation -->
</nav>
<h2 class="title">
<!-- Intitulé du document -->
</h2>
<main class="content">
Maintenant que nous avons notre DOM, nous pouvons y appliquer les CSS. Afin de
garder notre exemple aussi succinct et parlant que possible, nous n’appliquerons aucun
style dédié à l’embellissement du document (typographie, thème…) pour se concentrer
uniquement sur le layout.
/**
* Conteneurs
* 1. Dimensionnement
* 2. Centrage
*/
.header,
.main {
width: 1170px; /* 1 */
padding: 1em 0;
}
/**
* Conteneur principal
* 1. Déclaration de la grille
.main {
display: grid; /* 1 */
grid-template-columns: 250px 25px auto 25px 250px; /* 2 */
grid-template-rows: auto 1fr; /* 3 */
Nous venons de créer une grille de deux rangées sur cinq colonnes. La première rangée est
dédiée au titre du contenu, la seconde est dédiée au contenu et aux barres latérales (nav et
sidebar).
Du point de vue des rangées, nous avons un contenu à largeur fluide entouré de deux
gouttières de 25 pixels de large, puis de deux barres latérales de 250 pixels de large.
Vous remarquerez que l’en-tête ne fait pas partie de la grille.
/**
* Titre du contenu
* Première rangée
* Troisième colonne
* Équivalent : grid-area: 1 / 3
*/
.title {
grid-row: 1;
grid-column: 3;
}
/**
* Équivalent : grid-area: 2 / 3
*/
.content {
grid-row: 2;
grid-column: 3;
}
/**
* Navigation
* Deuxième rangée
* Première colonne
* Équivalent : grid-area: 2 / 1
*/
.nav {
grid-row: 2;
grid-column: 1;
/**
* Barre latérale
* Deuxième rangée
* Cinquième colonne
* Équivalent : grid-area: 2 / 5
*/
.sidebar {
grid-row: 2;
grid-column: 5;
Dans notre exemple, nous ne spécifions que grid-row-start et grid-column-start car nos
éléments ne s’étalent jamais sur plusieurs cellules, aussi les valeurs pour grid-row-end et
grid-column-end sont implicites.
Figure 3–38
Notre layout à trois colonnes basé sur Grid Layout
Maintenant que nous avons mis tout cela en place, il est assez simple de basculer sur des
zones nommées dans le but de faciliter la maintenance, par exemple. Il suffit de rajouter
une déclaration grid-template-areas à notre conteneur et de remplacer les déclarations grid-
row et grid-column de chaque partie avec grid-area.
.main {
display: grid;
grid-template-columns: 250px 25px auto 25px 250px;
Ici, on utilise le symbole . pour renseigner les zones anonymes, notamment les gouttières.
En effet, inutile d’encombrer notre zoning en nommant les parties dans lesquelles aucun
élément ne sera placé.
.title {
grid-area: title;
.content {
grid-area: content;
.nav {
grid-area: navigation;
.sidebar {
grid-area: sidebar;
Dans notre cas, l’élément .header se comporte de manière tout à fait normale jusqu’à ce
qu’on scrolle suffisamment pour qu’il soit partiellement hors de l’écran. À ce moment
précis, il devient sticky et se positionne selon les offsets (top, right, bottom, left) spécifiés.
Dans ce cas-ci, à 1em du haut de la fenêtre.
originOffsetY = header.offsetTop,
onScroll;
onScroll = function(e) {
window.scrollY >= originOffsetY ? header.classList.add('sticky') :
header.classList.remove('sticky');
}
document.addEventListener('scroll', onScroll);
Comme vous pouvez le constater, le code nécessaire pour achever cet effet en JavaScript
est très court. En revanche, cette solution présente des défauts, notamment celui des
performances. En effet, il est toujours délicat de lier des événements au scroll dans la
mesure où cela a tendance à être relativement gourmand en termes d’exécution.
SUPPORT Polyfill JavaScript
L’agence web FilamentGroup a publié un polyfill sur GitHub.
http://bit.ly/filament-group-sticky
Les régions
Il y a des choses qu’on a toujours su faire dans le domaine du print, mais qui, encore
aujourd’hui, demeurent un challenge dans le Web. Distribuer du texte dans différentes
colonnes ou zones fait partie de ces choses-là.
Nous l’avons vu au début de ce chapitre sur les nouvelles méthodes de positionnement et
de mise en page, le module dédié aux colonnes CSS vient pallier ce manque. Pour aller
encore plus loin, un module appelé CSS Regions a fait son entrée.
L’idée principale derrière ce module est de pouvoir afficher le contenu de telle zone ou
telle zone à tel endroit. C’est bien grâce à cette définition qu’on voit la différence avec les
autres modèles que nous avons étudiés dans ce chapitre (multicolonne, Flexbox, Grid
Layout). Effectivement, les régions en CSS n’ont pas pour vocation de mettre en page un
document. Elles servent simplement à distribuer un contenu dans une ou plusieurs zones.
Terminologie
Le module dédié aux régions n’est en soi pas très compliqué. En revanche, il appelle à
comprendre un certain nombre de termes afin d’être à l’aise pour l’explication des
nouvelles propriétés.
• Tout d’abord, une région CSS est un élément auquel est associé un flux.
• Une chaîne de régions est comme son nom l’indique une séquence de régions associées
à un flux. Les régions dans cette chaîne reçoivent le contenu du flux selon leur ordre au
sein de celle-ci.
• Un flux est une séquence ordonnée de contenus, identifiée par un nom. Les contenus au
sein d’un flux sont triés en fonction de leur ordre dans la source du document.
• Un contenu est placé dans un flux grâce à la propriété flow-into. Ensuite, le contenu de ce
flux est distribué dans sa chaîne de régions associée grâce à la propriété flow-from.
• Un flux est donc créé quand il reçoit du contenu via flow-into ou quand au moins une
région lui réclame du contenu via flow-from.
Comment ça marche ?
Un élément injecte son contenu dans un flux en utilisant la propriété flow-into. Ensuite, un
ou plusieurs éléments réclament le contenu de ce flux via la propriété flow-frow.
Le contenu du flux est donc distribué dans sa chaîne de régions.
Les changements de région sont similaires aux changements de page (comme dans le
module dédié à la pagination en CSS, non abordé dans ce livre) ou de colonne (vu au
début de ce chapitre) en cela qu’ils suivront les règles standards et pourront être régis par
les propriétés break-*.
En somme, chaque région consomme du contenu du flux. Celui-ci est donc positionné
dans la région en cours jusqu’à ce qu’un point de rupture naturel ou forcé intervienne. À
ce moment précis, la région suivante de la chaîne devient la région en cours pour accueillir
le contenu.
S’il n’y a pas suffisamment de régions dans la chaîne pour accueillir tout le contenu du
flux, la distribution du contenu restant dans la dernière région de la chaîne est contrôlée
par la propriété region-fragment.
Dans le cas inverse où il n’y aurait pas suffisamment de contenu pour remplir toutes les
régions, certaines resteront vides.
Outre la valeur none qui signifie simplement que le contenu de l’élément ne fait partie
d’aucun flux, cette propriété accepte un identifiant (le nom d’un flux), ainsi qu’un
éventuel mot-clé parmi element et content.
Si le mot-clé element est présent, ou qu’aucun des mots-clés n’est spécifié, alors l’élément
est retiré de son parent et placé dans le flux nommé après <ident>. Si le mot-clé content est
spécifié, alors seul le contenu de l’élément est placé dans le flux, laissant l’élément vide et
utilisable pour accueillir le contenu d’un autre flux, par exemple.
REMARQUE Identifiants invalides
Les valeurs none, inherit, default, auto et initial ne sont pas des identifiants valides et par
conséquent ne devraient pas être utilisés pour décrire un flux.
Important : la propriété flow-into affecte uniquement le rendu visuel des éléments placés
dans le flux et distribués dans la chaîne de régions. De fait, elle n’a pas d’effet sur la
cascade et l’héritage des éléments auxquels elle est spécifiée, ni leur position dans le
DOM. De plus, elle n’altère pas l’ordre dans les médias non visuels comme speech, ou la
façon dont se déroule une navigation (nav-index, tabindex…). En somme, on pourrait presque
dire que c’est de la poudre aux yeux !
La syntaxe de flow-from est légèrement plus simple que la syntaxe de flow-into. En effet, elle
n’accepte que trois valeurs différentes : none, inherit et un identifiant. Si l’identifiant ne
correspond à aucun flux, alors aucun rendu n’est effectué.
REMARQUE Régions et contenu non affiché
Si la propriété display de l’élément, ou celle de l’un de ses parents, s’avère être none,
l’élément ne devient pas une région non plus.
REMARQUE Régions et propriété content
Si la propriété content a une valeur autre que normal (ou none pour les pseudo-éléments),
l’élément ne devient pas une région. Par extension, si un pseudo-élément (par
exemple, ::after ou ::before) réclame le contenu d’un flux et qu’il a sa propriété content
à none, il est alors considéré comme une région valide.
Boucles infinies
Il est possible de générer des boucles infinies avec les régions. Par exemple, si un même
élément déclare les propriétés flow-into et flow-from à la même valeur, cela aura pour effet de
déclarer une récursion mal contrôlée. En effet, l’élément injecte son contenu dans un flux,
puis appelle le contenu de ce même flux, qui est aussitôt réinjecté dans le flux et ainsi de
suite…
Fort heureusement, les spécifications et les implémentations sont bien faites, aussi dans le
cas où un élément déclarerait les deux propriétés à la même valeur, celles-ci n’auraient pas
d’effet.
Arborescence HTML spécifique
Comme nous l’avons vu, la propriété flow-into déplace l’élément auquel elle est déclarée
dans un flux. De par ce fonctionnement, le choix des sélecteurs doit être effectué avec
précaution.
Considérons l’exemple suivant :
table {
flow-into: table-content;
Cette règle déplace toutes les tables dans un flux appelé table-content. Maintenant :
tbody > * {
flow-into: table-content;
Cette règle va déplacer tous les enfants directs d’un <tbody> dans le flux table-content. C’est
un comportement qui peut être intéressant si on souhaite fusionner plusieurs tables. Mais :
tbody * {
flow-into: table-content;
}
Cette règle va déplacer tous les descendants d’un <tbody> dans le flux table-content, sans
préserver l’arborescence initiale. En effet, les <tr>, <th> et <td> vont tous se retrouver au
même niveau hiérarchique, détruisant ainsi complètement la notion de tableau HTML.
Cet exemple illustre bien pourquoi il faut être prudent lorsqu’on choisit d’appliquer la
propriété flow-into à un élément. La hiérarchie de ses enfants ne sera pas préservée !
Mais quand on passe sur mobile, ladite sidebar se trouve en dessous du conteneur
principal, bien loin du regard de l’internaute. Pas de visualisation, pas de clic ; pas de clic,
pas de revenu.
Et si on utilisait les régions pour faire remonter les espaces publicitaires dans la zone de
contenu ? Pourquoi pas à plusieurs endroits ? Par exemple, un encart publicitaire après
l’introduction de l’article, puis deux plus bas au cœur du contenu, et enfin un après la
signature, juste avant les commentaires.
Pour cela, on pourrait placer des éléments vides avec une classe particulière aux endroits
désirés dans le conteneur principal. Considérons également que les quatre emplacements
publicitaires sont de taille 250 × 250 pour des raisons de simplicité.
<article class="article">
<p>Introduction …</p>
<div class="ad-slot"></div>
<p>… du contenu …</p>
<div class="ad-slot"></div>
<div class="ad-slot"></div>
<div class="article-comments"></div>
</article>
<aside class="sidebar">
<p>Du contenu dans la sidebar … </p>
</div>
Voila à quoi ressemble notre markup. Les emplacements publicitaires pour mobile sont
identifiés par la classe ad-slot. Passons justement au CSS.
/**
html {
box-sizing: border-box;
}
*, *::after, *::before {
box-sizing: inherit;
}
.ad {
margin-bottom: 1em;
}
.ad img {
display: block;
max-width: 100%;
height: auto;
}
.ad-slot {
display: none;
}
/**
*/
@media (max-width: 767px) {
.ad {
flow-into: ads;
}
/**
* Emplacements publicitaires
* 1. Hauteur fixe (image + padding vertical + borders)
* 2. Espacement entre l’encart et le titre
.ad-slot {
height: calc(250px + 2 * 1em + 2 * .5em); /* 1 */
padding: 1em 0;
border-left: none;
border-right: none;
background: #EFEFEF;
margin-bottom: 1em; /* 2 */
flow-from: ads; /* 3 */
}
}
C’est tout ce dont on a besoin pour atteindre l’effet escompté ! Les emplacements sont
masqués en temps normal mais quand on passe sous une certaine taille d’écran, ils sont bel
et bien visibles, avec une hauteur fixe, et réclament le contenu du flux ads. Celui-ci est
rempli par le contenu des éléments .ad, initialement présents dans la sidebar du site.
Figure 3–40
Sur un écran de petite taille, les encarts publicitaires remontent au cœur du contenu.
Tout cela fonctionne plutôt bien, mais nous avons été contraints de renseigner des
éléments vides dans notre markup. Non seulement cela n’est pas très élégant, mais c’est
surtout très irréaliste dans la mesure où le contenu de l’article provient très probablement
d’un système de gestion de contenu (CMS), comme WordPress, Drupal ou que sais-je
encore.
Contournons le souci en utilisant des pseudo-éléments. En ciblant le pseudo-élément
::before des titres de niveau 2 par exemple (qui pullulent probablement dans le contenu de
l’article), on pourrait non seulement injecter des publicités plus ou moins tout au long de
l’article, mais surtout s’assurer qu’une publicité n’intervient pas au beau milieu d’une
section, de manière abrupte.
@media (max-width: 767px) {
.ad {
flow-into: ads;
.article h2::before {
display: block;
.ads {
flow-from: ads;
}
Malheureusement, le support du module n’est pas aussi superbe que ce dernier. À l’heure
d’aujourd’hui, seul Safari le supporte correctement.
Notons qu’Internet Explorer supporte les régions CSS mais uniquement lorsque la source
d’un flux provient d’un élément iframe, ce qui rend le module somme toute assez limité.
L’ingénieur David Storey (Microsoft) m’informe que l’équipe d’Internet Explorer attend
la stabilisation des spécifications avant de basculer sur une implémentation définitive.
Google a supporté les régions CSS entre les versions 15 et 18 sous le préfixe -webkit-,
après quoi la fonctionnalité a basculé derrière un flag jusqu’à la version 34. À compter de
la version 35, Chrome a volontairement abandonné le module pour des raisons d’ordre
politique.
Les objectifs de Google pour l’année 2014 sont d’optimiser la performance, spécialement
sur mobile. En raison de leur nature, les régions CSS peuvent être coûteuses à
implémenter, aussi Google a préféré remettre leur support à plus tard.
Les masques de forme en CSS
Depuis toujours, nous avons constitué nos pages de rectangles. Après tout, tout élément
HTML est naturellement une boîte, autrement dit un rectangle. Aussi emboîtions-nous ces
éléments les uns dans les autres, tentant vainement d’en changer la forme en ayant recours
à des subtilités graphiques. Mais dans le fond, le résultat a toujours été le même : ce ne
sont que des rectangles.
C’est cette restriction fondamentale que le module dédié aux formes (shapes), ainsi qu’un
certain nombre d’autres modules d’ailleurs, essaye de corriger. Introduite en 2012 par
Adobe, la spécification dédiée aux formes vise à prodiguer aux designers et développeurs
web un moyen de définir comment le contenu s’écoule au sein et/ou autour d’une forme
plus ou moins complexe.
POUR ALLER PLUS LOIN
Nous n’allons voir qu’une brève introduction au concept des masques de forme. Si le
sujet vous plaît, je ne peux que vous recommander les écrits de Sara Soueidan,
véritable experte en la matière.
http://bit.ly/shapes-101
http://bit.ly/sara-soueidan-shapes
Figure 3–41 Sara utilise les masques de forme pour rendre ses mises en page plus agréables à l’œil.
Comment ça marche ?
Pour donner une forme à un élément de type bloc, les propriétés shape-inside et shape-outside
sont utilisées. Celles-ci acceptent des fonctions comme valeurs, telles que circle(), polygon()
ou encore url().
La différence entre les deux propriétés est une question de contexte. Pour shape-inside, nous
allons déclarer une forme dans laquelle le contenu peut s’écouler. Pour shape-outside, nous
allons déclarer une forme autour de laquelle le contenu peut s’écouler.
Par la suite, davantage de contrôles sont possibles grâce aux propriétés annexes telles que
shape-image-threshold et shape-margin.
Avant de rentrer dans le vif du sujet, il y a certains termes qu’il est important
d’appréhender dès maintenant afin de mieux comprendre le fonctionnement du module.
Zone de flottement
La zone de flottement correspond à la zone utilisée pour l’écoulement du contenu autour
d’un élément flottant. Ce sont les bords de cette zone qui détermineront comment le
contenu va s’écouler le long de l’élément flottant. Par défaut, la zone de flottement
correspond à la margin box de l’élément. La propriété shape-outside qu’on verra plus loin
permettra de définir une zone de flottement non rectangulaire.
Formes de base
Les formes de base sont utilisées par le biais des fonctions de forme (voir plus loin). Le
système de coordonnées pour la déclaration de la forme est celui de l’élément à qui est
appliquée la propriété, l’origine se trouvant dans le coin supérieur gauche de celui-ci. De
cette façon, les longueurs exprimées en pourcentage réfèrent aux dimensions de l’élément.
Seules quatre fonctions de forme sont actuellement supportées :
• circle() ;
• ellipse() ;
• inset() ;
• polygon().
Ce sont ces fonctions qui vont permettre de définir la forme d’un élément, conditionnant
ainsi l’écoulement du contenu autour et à l’intérieur de lui.
circle
circle() = circle( [<shape-radius>]? [at <position>]? )
ellipse
ellipse() = ellipse( [<shape-radius>{2}]? [at <position>]? )
inset
inset() = inset( <shape-arg>{1,4} [round <border-radius>]? )
polygon
polygon() = polygon( [<fill-rule>,]? [<shape-arg> <shape-arg>]# )
La fonction polygon() est la plus complexe de toutes puisqu’elle permet de définir des
formes arbitraires plus avancées, utilisant un nombre indéfini de points.
Cette fonction accepte un nombre variable d’arguments, chacun étant une paire de
coordonnées X et Y (en valeurs fixes, relatives ou pourcentages) séparées par un espace.
Chaque paire de coordonnées représente bien sûr un point de la forme, celle-ci étant
finalement définie par le navigateur en reliant les points les uns aux autres, notamment le
premier de la liste au dernier.
Parce qu’une forme ne peut être constituée de moins de trois points, au moins trois paires
de coordonnées sont donc nécessaires pour que la fonction aboutisse.
RESSOURCE Shape Tools
Bear Travis, ingénieur chez Adobe très impliqué dans les spécifications autour de
Shapes, a réalisé une collection d’outils pour faciliter l’utilisation des masques de
forme. Citons notamment Poly Draw, outil permettant de définir un polygone grâce à
des curseurs pour en retirer la syntaxe pour la fonction polygon().
http://bit.ly/shape-tools
Syntaxe
À l’heure actuelle, la spécification n’intègre que trois propriétés différentes pour
manipuler l’écoulement du contenu autour et à l’intérieur de zones arbitraires :
• shape-outside ;
• shape-image-threshold ;
• shape-margin.
La propriété shape-inside n’existe pas encore ou plutôt n’existe plus, puisqu’un brouillon de
spécification avait été rédigé mais fut retiré par la suite pour être implémenté dans une
future version du module.
De la même façon que shape-outside profite de shape-margin, shape-inside sera accompagnée
d’une propriété shape-padding. Autrement dit, il n’est pour l’instant pas possible de
manipuler la façon dont le contenu s’écoule au sein d’une forme. Il est uniquement
possible de le faire autour d’une forme.
Cette propriété attend donc un nombre entre 0 et 1 comme valeur, comme pour la
propriété opacity. Ce nombre représente le seuil de transparence au-delà duquel un pixel est
impliqué dans la détermination de la forme d’une image.
shape-image-threshold: <number>
Par exemple, si la valeur est 1, seuls les pixels complètement opaques seront évalués.
Inversement, si la valeur est de 0, les pixels intégralement transparents seront
comptabilisés. La valeur par défaut est de 0.5, et c’est à vous de l’ajuster en fonction de
l’image si le besoin se fait sentir.
Notons qu’une future version des spécifications prévoit de permettre la définition du seuil
à partir de la luminosité de l’image plutôt que de la transparence. Ceci permettra de gérer
des formes sur des images intégralement opaques, comme des fichiers JPEG.
<div class="bio-content">
</section>
Dans un souci de lisibilité, j’ai omis le contenu textuel au sein de l’élément .bio-content,
mais c’est à cet endroit que se trouve la description de l’utilisateur.
.bio {
padding: 1em;
}
/**
* Avatar
* 1. Flottant obligatoire
* 2. Forme circulaire « fonctionnelle » sur l’image
*/
.bio-avatar {
float: left; /* 1 */
shape-outside: circle(); /* 2 */
border-radius: 50%; /* 3 */
text-align: justify;
}
Dans notre cas, les choses se dégradent de manière très gracieuse si les masques de forme
ne sont pas supportés : le contenu textuel n’épouse pas les bords de l’image. Rien de
dramatique en somme !
Figure 3–42
Exemple d’amélioration progressive avec les masques de forme
De nouvelles couleurs
Finalement, le module de couleurs de niveau 3 a standardisé pas moins de 130 nouveaux
noms pour un total de 147 noms différents (dont 13 nuances de gris), tous utilisables
aujourd’hui y compris sur de vieux navigateurs comme Internet Explorer 6. On ne les
listera pas ici car ce serait trop long, mais vous pouvez facilement trouver la liste sur
Internet. Quoi qu’il en soit, cette première étape démontre la volonté des fabricants de
navigateurs à faciliter l’utilisation des couleurs parfois complexe avec la notation
hexadécimale.
CULTURE GÉNÉRALE À propos de rebeccapurple
Le 19 juin 2014, le W3C s’est accordé pour ajouter la couleur nommée rebeccapurple
(#663399) aux 147 couleurs existantes, en hommage à Rebecca, la fille d’Eric A. Meyer,
décédée d’un cancer le 7 juin, jour de son 6e anniversaire.
Eric Meyer a dédié près de deux décennies de sa vie au Web et ses travaux ont eu
impact indéniable sur l’évolution de celui-ci, notamment en ce qui concerne les CSS,
domaine dans lequel il a toujours excellé. C’est pour saluer le travail d’Eric que le
W3C a souhaité rendre hommage à sa fille, avec son accord bien sûr.
Parallèlement, il a rapidement été question d’ajouter aux couleurs une notion de
transparence, ce que l’on connaît aussi sous le nom de canal alpha (alpha channel). C’est
pour cela qu’ont été standardisées deux notations : rgba et hsla.
La notation rgba
La notation rgba est directement corrélée avec la notation déjà existante de rgb, si ce n’est
que la fonction accepte un quatrième argument pour l’opacité, définie entre 0 et 1, 0 étant
complètement transparent et 1 complètement opaque.
/* Noir à moitié transparent */
.black {
color: rgba(0, 0, 0, 0.5);
}
/* Rouge transparent, donc invisible */
.red {
color: rgba(255, 0, 0, 0);
color: rgba(100%, 0%, 0%, 0);
Comme pour la fonction rgb, il est possible de définir des valeurs pour les canaux vert,
bleu et rouge, en pourcentage ou sous la forme d’entiers compris entre 0 et 255. J’ai beau
trouver la notation en pourcentage plus facile, je dois dire que j’ai tendance à utiliser la
version entier, principalement par habitude.
La notation hsla
hsla et hsl (dans une moindre mesure) sont deux formats qui ont vu le jour durant le
passage du module des couleurs au niveau 3. Ils permettent de définir des couleurs à l’aide
d’une représentation HSL (Hue Saturation Lightness) et non pas RGB (Red Green Blue).
L’objectif derrière la fonction hsl est d’offrir aux développeurs une solution plus intuitive
pour la création de couleurs. En effet, le mode de représentation HSL est plus facile à
appréhender et sûrement moins mathématique que le mode de représentation RGB utilisé
par rgb, rgba et hexadécimal.
Le premier argument est une valeur sans unité (définie en degrés) entre 0 et 360 où se
placent les couleurs : rouge à 0 (et 360), vert à 120, bleu à 240. C’est cette valeur qui va
définir la couleur à proprement parler.
Figure 4–1
Le cercle chromatique des couleurs utilisé par la notation HSL
.element {
color: hsl(0, 100%, 50%);
/* Noir */
.element {
/* Blanc */
.element {
color: hsl(0, 0%, 100%);
Et comme nous l’avons vu, il est donc possible d’utiliser la fonction hsla afin d’ajouter un
quatrième argument, à savoir le degré d’opacité exprimé entre 0 et 1, exactement comme
pour la fonction rgba.
CURIOSITÉ Des teintes nommées
Le niveau 4 du module des couleurs intègre 24 noms visant à simplifier l’utilisation
des teintes (valeur en degrés de la notation HSL), répartis de manière plus ou moins
équitable autour du cercle chromatique. Dans l’ordre : red (0deg), orangish red (7.5deg),
red orange/orange red (15deg), reddish orange (22.5deg), orange (30deg), yellowish orange
(37.5deg), orange yellow/yellow orange (45deg), orangish yellow (52.5deg), yellow (60deg),
greenish yellow (75deg), yellow green/green yellow (90deg), yellowish green (105deg),
green (120deg), bluish green (150deg), green blue/blue green (180deg), greenish blue
(210deg), blue (240deg), purplish blue (255deg), blue purple/purple blue (270deg), bluish
purple (285deg), purple (300deg), reddish purple (315deg), red purple/purple red (330deg),
purplish red (345deg).
En revanche, cette spécification n’est pas encore validée et ceci n’est implémenté dans
aucun navigateur. Ceci étant dit, vous conviendrez que CSS nous réserve encore de
belles choses !
.element {
opacity: 0.5;
La modification de l’opacité d’un élément est souvent utilisée pour le rendre légèrement
moins impactant visuellement, jusqu’à ce qu’il soit survolé, auquel cas il reprend alors son
opacité complète, à savoir 1.
.element {
opacity: 0.5;
}
.element:hover {
opacity: 1;
}
Figure 4–2
Niveaux d’opacité de 1 à 0.1 (de 0.1 en 0.1)
opacity et l’héritage
Une chose très importante à savoir sur la propriété opacity est qu’elle n’est pas héritée,
autrement dit il n’est pas possible pour l’enfant d’un élément ayant une opacité réduite de
la rehausser. Par exemple, si vous appliquez une opacité de 0 à l’élément body, il est
absolument impossible de rendre visible un quelconque élément de la page.
C’est pourquoi on ne peut pas utiliser opacity pour rendre le fond d’un élément semi-
transparent, tout en conservant le contenu de l’élément (texte, par exemple) complètement
visible. À la place, il faut se rabattre sur les modes de couleur hsla et rgba qui permettent de
définir la transparence d’une couleur via le canal alpha.
Dans le cas où le fond serait une image et non pas une couleur unie, on ne peut pas se
contenter d’une couleur d’arrière-plan en semi-transparence. Il y a donc deux solutions : la
première, et probablement la plus simple, consiste à utiliser un PNG semi-transparent.
Fonctionnel, mais nécessite de regénérer une image si l’opacité change.
L’autre solution consiste à utiliser un pseudo-élément pour afficher l’image de fond, placé
derrière l’élément, avec une transparence amoindrie. Par exemple :
/**
* Élément
*/
.element {
position: relative; /* 1 */
}
/**
* 3. Image d’arrière-plan
* 4. Réduction de l’opacité
*/
.element::after {
content: '';
position: absolute; /* 1 */
top: 0; /* 1 */
right: 0; /* 1 */
bottom: 0; /* 1 */
left: 0; /* 1 */
z-index: -2; /* 2 */
background-image: url(../images/background.jpg); /* 3 */
opacity: .5; /* 4 */
C’est donc le pseudo-élément (considéré comme un enfant de l’élément) qui est utilisé
pour afficher l’image en semi-transparence ; l’élément n’est pas impacté par la propriété
opacity et son contenu est affiché normalement. Pratique, mais attention aux versions
d’Internet Explorer inférieures à 8 qui ne supportent pas les pseudo-éléments !
Figure 4–3
À gauche, l’opacité est appliquée sur le parent rendant le contenu illisible ; à droite, on utilise l’astuce du pseudo-
élément.
/* Internet Explorer 5, 6 et 7 */
filter: alpha(opacity=50);
/* Internet Explorer 8 */
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
}
filter: alpha(opacity=#{$opacity-ie});
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=#{$opacity-ie})";
opacity: $value;
}
.element {
@include opacity(.5);
}
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
opacity: 0.5;
}
La version simple
La propriété border-radius est un raccourci pour les quatre propriétés individuelles :
• border-top-left-radius ;
• border-top-right-radius ;
• border-bottom-right-radius ;
• border-bottom-left-radius.
De fait, elle accepte plusieurs syntaxes différentes. Tout d’abord, la plus évidente et la plus
répandue consiste à donner une longueur qui agira pour les quatre angles de l’élément.
Ladite longueur détermine le rayon du cercle qui définit la forme arrondie du coin.
/**
* Supérieur gauche : 2em
*/
.element {
border-radius: 2em;
}
Figure 4–4
border-radius: 2em;
Il est possible de donner deux valeurs séparées par un espace. Dans ce cas, la première
valeur agira pour les angles supérieur gauche et inférieur droit, et la seconde valeur agira
pour les angles supérieur droit et inférieur gauche.
/**
.element {
Figure 4–5
border-radius: 2em 1em;
Vous pouvez aussi spécifier trois valeurs séparées par des espaces. La première correspond
au coin supérieur gauche, la deuxième aux coins supérieur droit et inférieur gauche et la
troisième au coin inférieur droit.
/**
.element {
Et enfin, bien évidemment, vous pouvez déclarer quatre valeurs pour spécifier les quatre
angles, dans le sens des aiguilles d’une montre à commencer par l’angle supérieur gauche.
/**
*/
.element {
Figure 4–7
border-radius: 2em 1em 3em 4em;
.element {
border-radius: 2em / 3em;
Figure 4–8
border-radius: 2em / 4em;
/**
* Supérieur gauche : 2em / 4em
*/
.element {
border-radius: 2em 1em / 4em 2em;
Figure 4–9
border-radius: 2em 1em / 4em 2em;
/**
* Supérieur gauche : 2em / 4em
*/
.element {
border-radius: 2em 1em 3em / 4em 2em 1em;
}
Figure 4-10
border-radius: 2em 1em 3em / 4em 2em 1em;
/**
*/
.element {
border-radius: 2em 1em 3em 4em / 4em 2em 1em 3em;
Figure 4-11
border-radius: 2em 1em 3em 4em / 4em 2em 1em 3em;
border-radius: 50%;
}
Figure 4-12 border-radius: 50%;
padding: 1em;
}
/**
*/
.enfant {
border-radius: 1em;
}
Figure 4–13
Radius interne égal à celui du parent (2em) et visuellement incorrect
Figure 4–14
Radius interne correctement calculé (1em) et visuellement plaisant
À l’heure actuelle, il n’est plus utile d’utiliser les préfixes constructeurs pour la propriété
border-radius. Tout d’abord, ni Internet Explorer ni Opera n’ont besoin de préfixe pour cette
propriété, contrairement à ce que l’on peut lire dans certaines sources : ces deux
navigateurs sont passés de la case « non supporté » à la case « supporté sans préfixe ».
Concernant Mozilla et Safari, il n’est pas nécessaire d’écrire les préfixes sauf si la majorité
de votre trafic provient des navigateurs Firefox 3.6-, Safari 4-, iOS 3.2- ou Android 2.3-, à
savoir des navigateurs très anciens (ce qui serait malheureux). Dans le cas contraire, je
vous recommanderais d’écrire uniquement la règle non préfixée, rendant le code plus
lisible, et aussi plus léger.
WEBKIT Champs de recherche
Par défaut, les éléments input[type="search"] ont des bords arrondis sur le moteur de
rendu WebKit (entre autres). Si vous désirez restaurer les angles droits communs à
tous les champs de saisie, il vous faut malheureusement passer par la propriété non
standardisée appearance.
En effet, ce n’est pas la propriété border-radius qui donne ce rendu sur WebKit mais la
déclaration -webkit-appearance: searchfield. Pour corriger le tir, on peut donc écrire :
input[type="search"] {
-webkit-appearance: textfield;
border-radius: 1em;
border: .5em solid black ;
}
.img-container img {
border-radius: 1em;
Figure 4-15 Problème de border-radius sur une image avec bordure sur Safari 5
Des ombres avec box-shadow
Une autre propriété bien trop connue du module Background and Borders Level 3 est la
propriété box-shadow qui permet d’appliquer des ombres portées aux éléments sans avoir à
recourir à des images. Cette propriété décorative est aujourd’hui correctement supportée et
joue un rôle important dans la conception de sites et d’applications puisqu’elle permet
d’ajouter de manière très simple des effets d’ombres plus ou moins subtils.
La propriété box-shadow accepte une liste de <shadow> séparées par des virgules, allant du
premier plan à l’arrière-plan. Cela signifie que si vous spécifiez plusieurs ombres dans la
propriété box-shadow, en cas de chevauchement, la première définie se trouvera devant la
seconde, qui se trouvera devant la troisième et ainsi de suite jusqu’à la dernière.
Une <shadow> se compose de la façon suivante :
<shadow> = inset? && <length>{2,4} && <color>?
Figure 4–16
box-shadow: . 5em .5em .25em #aaa;
On peut imaginer des choses un petit peu plus subtiles, comme un fin liseré blanc à
l’intérieur de l’élément, juste en dessous de sa bordure supérieure, comme pour simuler un
léger effet de lumière.
/**
* Offset horizontal : 0
* Offset vertical : 1px
* Blur : 0
* Spread : 0
.element {
background: tomato;
border: 1px solid rgba(0, 0, 0, .1);
De même, on peut appliquer une ombre légère sous l’élément pour lui donner un petit peu
de profondeur.
/**
* Offset horizontal : 0
* Spread : -0.25em
* Couleur : rgba(0, 0, 0, .2)
* Inset : non
*/
.element {
box-shadow: 0 .5em .25em -.25em rgba(0, 0, 0, .2);
Figure 4–17
box-shadow: 0 .5em .25em -.25em rgba(0, 0, 0, .2);
Il est même possible de simuler une bordure avec la propriété box-shadow en ne spécifiant ni
offset ni flou, juste une valeur de spread de la valeur désirée comme épaisseur de bordure.
.element {
Il y a toutefois une différence notable entre box-shadow et border : les ombres sont appliquées
sur un calque visuel, c’est-à-dire qu’elles n’interagissent pas avec l’environnement
(modèle de boîte). Une ombre ne peut modifier le layout ou déclencher l’apparition de
barres de défilement.
box-shadow:
.z-layer-2 {
.z-layer-3 {
}
.z-layer-4 {
border: 1px solid rgba(0, 0, 0, .1);
box-shadow:
.z-layer-5 {
border: 1px solid rgba(0, 0, 0, .1);
box-shadow:
0 15px 24px rgba(0, 0, 0, .22),
Figure 4–18
Les six niveaux de layering des interfaces Android
Par la suite, ces classes peuvent être appliquées à des éléments d’interface pour les
ordonner sur l’axe Z. Quand elles sont utilisées de manière transversale, ces ombres
donnent du sens au layout et facilitent les interactions.
Figure 4–19
Un zoning d’une interface Android issu des guidelines Google Design utilisant les ombres pour introduire la notion de
plans superposés
Comme pour la propriété border-radius, il n’est plus nécessaire d’écrire les préfixes
constructeurs pour la propriété box-shadow. Internet Explorer et Opera n’en ont jamais eu
besoin, et les préfixes -moz- et -webkit- ne seront utiles que pour Firefox 3.6-, Safari 4-, iOS
3.2- et Android 2.3-, à savoir de très vieux navigateurs.
Je vous recommande donc de n’écrire aucun préfixe et de vous contenter de la règle
originale.
SAFARI 6, IOS 6 ET ANDROID 2.3 Problème avec un blur à 0px
Les navigateurs Safari 6, iOS 6 et Android 2.3 rencontrent un problème lorsque la
valeur du paramètre blur est de 0px. Définir une valeur de 1px au minimum suffit à
contourner ce souci.
Des ombres de texte avec text-shadow
La propriété text-shadow est très similaire à la propriété box-shadow que l’on vient d’étudier si
ce n’est qu’elle permet d’appliquer des effets d’ombrage aux textes et non pas aux
éléments eux-mêmes. Là encore, cette propriété au support plus que correct joue un grand
rôle dans la conception d’interfaces graphiques engageantes.
Sa syntaxe est très proche de celle de box-shadow si ce n’est que le mot-clé inset n’est pas
autorisé, et que la valeur de spread n’existe pas.
<shadow> = <length>{2,3} && <color>?
Figure 4–20
Utilisation d’une ombre grasse et non floutée sur un titre
C’est là un effet relativement primaire, et pour un rendu plus qualitatif vous allez sûrement
avoir besoin de plusieurs ombres en même temps. Vous remarquerez aussi que bien
souvent, on se sert des text-shadow non pas comme ombres mais comme reflets lumineux en
utilisant un ton blanc semi-transparent.
DÉMO
CodePen recèle de démos typographiques utilisant text-shadow. Par exemple, dans cette
démo, Diego Pardo utilise pas moins de cinq ombres différentes pour un effet
letterpress.
text-shadow:
Figure 4–21 Une démonstration impressionnante des capacités de text-shadow par Diego Pardo
Syntaxe
La syntaxe des dégradés CSS n’est pas facile, non pas parce qu’elle a été mal conçue mais
parce qu’elle doit être suffisamment flexible pour permettre divers cas d’usage. Pour la
résumer très simplement, un dégradé se compose d’une direction (qui est verticale par
défaut) ainsi que d’une succession de ce que l’on appelle des color-stops, c’est-à-dire une
couleur éventuellement accompagnée d’un longueur ou d’un pourcentage indiquant le
point de départ de la couleur au sein du dégradé.
Dégradés linéaires
Pour les amateurs de syntaxe bien écrite :
<linear-gradient> = linear-gradient(
[ [ <angle> | to <side-or-corner> ] ,]?
<color-stop>[, <color-stop>]+
)
Figure 4–22
Le dégradé linéaire réalisé par l’une des déclarations précédentes
Dégradés radiaux
Nous avons vu comment écrire des dégradés linéaires simples. Vous l’aurez compris, il est
possible de faire des choses assez poussées grâce à la syntaxe permissive des dégradés. De
plus, il est possible de réaliser de radieux dégradés radiaux ! Ce sont des dégradés qui ne
suivent pas un axe mais deux, permettant ainsi la mise en place de dégradés circulaires ou
elliptiques.
Le besoin est plus compliqué et naturellement la syntaxe l’est aussi. Cela vient
essentiellement du fait que la quasi-totalité des arguments de la fonction radial-gradient()
sont optionnels (à l’exception des couleurs et de leur point de rupture bien sûr).
Nous n’allons pas nous étendre sur celle-ci, aussi je vais résumer le plus simplement
possible comment écrire un dégradé radial simple.
Vous pouvez tout d’abord spécifier une forme et/ou une dimension suivie de at et d’une
position, et finalement une suite de color-stops comme pour les dégradés linéaires. Seule
la collection de couleurs est obligatoire.
Par exemple :
/* Ces 3 déclarations produisent le même dégradé */
Si vous êtes très intéressé par les dégradés radiaux, je ne peux que vous recommander de
lire les spécifications bien qu’elles soient assez complexes. Attention aux articles
obsolètes ! La syntaxe des dégradés a changé de nombreuses fois, aussi certains articles à
leur propos ne sont plus à jour. Veillez à consulter la date de vos sources pour vous assurer
qu’elles sont actualisées.
Figure 4–25
Les célèbres boutons de Google utilisent de subtiles dégradés linéaires verticaux pour donner du relief
La valeur linear-gradient() doit être la valeur/propriété la plus mal préfixée de toutes les
fonctionnalités nécessitant un préfixe constructeur. Je me permets donc de faire un rappel
sur l’état actuel des préfixes dans le cadre de la fonction linear-gradient().
Tout d’abord le préfixe -ms- n’a jamais été nécessaire dans la mesure où la première
version d’Internet Explorer supportant les dégradés CSS, Internet Explorer 10 a
directement supporté la version standard de la fonction, rendant le préfixe absolument
inutile.
Mozilla Firefox a supporté les dégradés CSS à partir de la version 3.6 via le préfixe -moz-,
puis sans le préfixe à compter de Firefox 16. Aujourd’hui, le trafic généré par Firefox
entre les versions 3.6 et 16 est proche de nul. Je pense ne pas prendre de risque en vous
recommandant de vous passer du préfixe -moz-, en tout cas pour les dégradés les plus
subtiles qui ne visent qu’à améliorer légèrement le rendu de tel ou tel aplat de couleur.
Opera s’est mis à supporter la version standardisée des dégradés à partir d’Opera 12.1, et
Opera Mini n’a jamais mis en place le support pour cette propriété donc à moins de
vouloir impérativement tenir compte des 0,25 % que représentent les utilisateurs d’Opera
12.1-, je pense qu’il est tout à fait possible de se passer du préfixe -o-, à condition d’avoir
une couleur unie de repli.
Et enfin, le dernier mais non des moindres, -webkit- qui est malheureusement encore
nécessaire puisque Safari ne supporte toujours pas la version standard et que Chrome ne
l’a fait que très tardivement. Aussi, il est important de conserver ce préfixe, non seulement
pour Safari et les anciennes versions de Chrome, mais aussi pour le navigateur d’Android
qui est basé sur le moteur de rendu WebKit.
En somme et à moins que les statistiques de votre site n’affirment le contraire, je pense
que le seul préfixe qu’il est important de conserver à l’heure actuelle est -webkit-. La
version standardisée et un aplat de couleur en tant que solution de repli feront le reste.
.element {
background: tomato;
background: -webkit-linear-gradient(tomato, deepskyblue);
Il est ensuite possible de dimensionner, positionner et manipuler chacun des fonds grâce
aux autres propriétés background-* en renseignant là encore une liste de valeurs séparées par
des virgules.
.ciel {
background-image:
url('oiseaux.png'),
url('nuages.png'),
url('soleil.png');
Notons que l’ordre a une importance ici : les arrière-plans sont classés du plus proche de
l’utilisateur au plus éloigné. Dans notre cas, les oiseaux seront au premier plan, puis les
nuages et enfin le soleil.
Imaginons désormais que nous souhaitions positionner ces arrière-plans :
.ciel {
background-image:
url('oiseaux.png'),
url('nuages.png'),
url('soleil.png');
background-position:
100% bottom,
center top,
top right;
Les index sont respectés, c’est-à-dire que le premier fond de la liste background-image se
verra attribuer la première position de la liste background-position, et ainsi de suite.
Dans le cas où une liste aurait trop de valeurs, celles en surplus sont simplement omises.
Par exemple, si nous avions quatre positions pour trois arrière-plans, la quatrième position
ne serait pas traitée.
Dans le cas contraire où une liste manquerait de valeurs, cette liste serait répétée jusqu’à
ce qu’il y ait suffisamment de valeurs pour satisfaire tous les arrière-plans. Si nous
n’avions renseigné que deux positions pour trois fonds, alors la liste aurait été copiée
depuis la première position, renseignant ainsi la troisième position manquante.
Notons qu’il est possible d’écrire notre exemple précédent de la façon suivante grâce à la
propriété raccourcie (dite shorthand) background :
.ciel {
background:
Figure 4–26
Les emplacements inoccupés sont indiqués par des placeholders, grâce aux dégradés linéaires et à background-repeat
Il y a plusieurs façons d’attaquer le problème, mais une idée pourrait être de laisser des
zones grises aux emplacements non occupés. Pour faire cela, la méthode la plus simple
consisterait à utiliser des éléments vides (<div />, par exemple), mais c’est somme toute
assez inapproprié de générer du markup vide, surtout dans un cas comme celui-ci.
On peut donc aborder le problème avec davantage de sagacité et employer la méthode
subtile des dégradés répétés. En effet, il n’est pas possible de répéter une couleur de fond
sur des zones bien définies, aussi on utilise un dégradé plat, c’est-à-dire un dégradé allant
d’une couleur à… la même couleur. Voyez plutôt :
/**
width: 810px;
padding: 15px;
}
.gallery img {
float: left;
}
.gallery img + img {
margin-left: 15px;
}
Grâce à la répétition intelligente du dégradé de 150 × 150 pixels sur toute la largeur de la
galerie et à la distribution équitable de l’espace restant entre les occurrences, les
emplacements inoccupés sont représentés par des carrés gris (la couleur utilisée dans le
dégradé).
PLUS D’INFOS
Si vous désirez approfondir l’idée, je vous invite à lire l’article original de Nico
Hagenburger, en anglais.
http://bit.ly/background-repeat
background-image: url('ciel.png');
background-size: cover;
}
Figure 4–27
Le site de l’agence Edenspiekermann utilise la valeur cover pour leur grande image d’arrière-plan.
Il est recommandé de ne pas utiliser background-size: cover sur l’élément body car Safari
sur iOS rencontre quelques soucis avec cette configuration.
background-image: url('ciel.png');
background-position: cover;
filter:
progid:DXImageTransform.Microsoft.AlphaImageLoader(src='.ciel.png',
sizingMethod='scale');
-ms-filter:
"progid:DXImageTransform.Microsoft.AlphaImageLoader(src='ciel.png',
sizingMethod='scale')";
}
Mais que faire si l’on souhaite spécifier les décalages non pas par rapport au coin
supérieur gauche de l’élément mais plutôt par rapport au coin inférieur droit ? C’est là que
les nouvelles spécifications interviennent en offrant la possibilité de déclarer les décalages
depuis n’importe quel bord en le spécifiant avant sa valeur.
Si l’on souhaite placer notre soleil à 50 pixels du bord droit, et 20 pixels du bord bas, on
peut donc réécrire notre règle comme suit :
.ciel {
background-image: url('soleil.png');
Notons que les valeurs peuvent tout aussi bien être renseignées en pourcentage et même
être omises, auquel cas elles seront considérées comme valant 0. Si l’on désire coller notre
soleil dans le coin inférieur droit, on peut tout à fait écrire right bottom.
La propriété background-position a donc trois syntaxes différentes désormais (et ce pour
chaque valeur dans le cas où plusieurs positions pour plusieurs fonds seraient spécifiées) :
• une valeur unique (mot-clé, pourcentage ou longueur) ayant effet sur les deux axes
depuis le coin supérieur gauche ;
• deux valeurs (mots-clés, pourcentages ou longueurs) ayant effet depuis le coin supérieur
gauche ;
• deux valeurs (mots-clés + éventuels longueurs ou pourcentages) ayant effet depuis les
bords renseignés.
Ou si vous préférez…
[ left | center | right | top | bottom | <percentage> | <length> ]
|
[ left | center | right | <percentage> | <length> ]
|
[ center | [ left | right ] [ <percentage> | <length> ]? ] &&
Malheureusement, Safari ne supporte pas cette nouvelle syntaxe (voir page 187).
Toutefois, il est possible de dresser une alternative spécialement pour Safari en utilisant le
potentiel de la fonction calc() que vous pouvez découvrir au chapitre 5 de ce livre.
overflow: auto;
max-height: 200px;
background-repeat: no-repeat;
background-color: white;
background-image:
linear-gradient(white 30%, rgba(255, 255, 255, 0)),
linear-gradient(rgba(255, 255, 255, 0), white 70%) 0 100%,
Figure 4–30
Affichage d’ombres en haut et en bas d’une zone scrollable pour indiquer qu’il est possible de faire défiler le contenu au
sein de celle-ci.
PLUS D’INFOS
Pour plus d’informations sur cette expérience, notamment sur la déclaration backgroud-
attachment: local, veuillez vous référer à l’article original de Lea Verou.
Pour une solution alternative à cet effet visuel ne nécessitant pas background-attachement:
local, sachez que Roman Komarov y est parvenu grâce à une astucieuse utilisation des
pseudo-éléments.
http://bit.ly/lea-verou-background-attachment
http://bit.ly/kizmarh-background-attachment
Si on applique la valeur padding-box, alors le fond ne sera pas peint par le navigateur dans la
zone de bordure. De la même manière, la valeur content-box a pour effet d’appliquer le fond
uniquement à l’intérieur de la zone de contenu, ne tenant ainsi pas compte de la zone de
padding, ce qui est normalement le cas.
Figure 4–31
Illustration des différentes valeurs de background-clip (avec background-origin: border-box pour la démo)
Figure 4-32
Masque de texte avec background-clip: text et -webkit-text-fill-color: transparent, fonctionnel uniquement sur le
moteur de rendu WebKit, bien évidemment
background-color: white;
background-clip: padding-box;
border: 1em solid rgba(0, 0, 0, .5);
Pour les navigateurs ne supportant pas background-clip, il est possible d’obtenir un résultat
sensiblement identique grâce à box-shadow.
.modal {
background-color: white;
box-shadow: 0 0 0 1em rgba(0, 0, 0, .5);
}
Ceci étant dit, le support de box-shadow n’est pas tellement mieux que background-clip.
Pour les navigateurs ne supportant aucune des deux propriétés, il faut recourir à deux
éléments imbriqués, le parent ayant un fond transparent noir via un PNG semi-transparent,
et l’enfant un fond blanc. M’est avis qu’il vaut mieux se passer de la bordure semi-
transparente à ce niveau-là.
Comment fonctionnent-ils ?
De manière très générale, un filtre est une opération graphique. Il faut lui passer une image
d’entrée (input) qui sera traitée pour finalement retourner une nouvelle image (output).
Parce que les filtres sont avant tout des opérations d’images (un peu à la manière de
Photoshop), il est important et intéressant de comprendre comment le navigateur s’y prend
pour les appliquer.
Le fonctionnement basique est le suivant : le navigateur calcule le positionnement de tous
les éléments, applique les styles, puis les dessine mais ne les affiche pas encore. À cet
instant, il copie le rendu de la page comme image bitmap, effectue des calculs graphiques
au niveau des pixels, puis applique le résultat sur l’image originale.
Fonctionnellement, c’est proche d’un filtre placé sur la lentille d’un appareil photo.
Lorsque l’on regarde à travers cette lentille, le rendu parait modifié (sans qu’il le soit pour
autant réellement).
Vous l’aurez compris, appliquer des filtres aux éléments du document demande aux
navigateurs des manipulations supplémentaires, ce qui peut potentiellement impacter les
performances. Cependant vous connaissez le dicton : « Ne jamais abuser des bonnes
choses ». Appliqués intelligemment, les filtres n’auront aucun impact négatif sur votre
document.
RAPPEL De la notion d’image
Pour toute la partie dédiée aux filtres, le terme « image » ne réfère pas strictement à
une image au sens HTML du terme, mais bien à l’image d’entrée du filtre, générée par
le navigateur, autrement dit au rendu graphique de l’élément auquel est appliqué le
filtre.
Syntaxe
filter: none | <filter-function-list>
où <filter-function-list> signifie :
[ <filter-function> | <url> ]+
et <filter-function> :
[ <blur()> | <brightness()> | <contrast()> | <custom()> | <drop-shadow()> |
<grayscale()> | <hue-rotate()> | <invert()> | <opacity()> | <sepia()> |
<saturate()> ]
La propriété filter accepte donc une liste de filtres séparés par des espaces, eux-mêmes
déclarés via la fonction url(), une fonction prédéfinie ou une fonction personnalisée
(<custom()>).
Pour bien comprendre leur fonctionnement mais surtout la façon de les utiliser, je vous
propose de les voir un à un, notamment leur effet quand utilisé sur une image HTML (car
d’expérience, je dois dire qu’ils sont souvent appliqués sur des images).
Figure 4-34
Image initiale, non filtrée
blur
filter: blur( <length> )
Figure 4-35
filter: blur(5px)
brightness
filter: brightness( [ <number> | <percentage> ] )
Figure 4-36
filter: brightness(.33)
contrast
filter: contrast( [ <number> | <percentage> ] )
Figure 4-37
filter: contrast(.33)
drop-shadow
filter: drop-shadow( <length>{2,3} <color>? )
Le filtre drop-shadow() est très semblable à la propriété box-shadow que nous avons vu plus en
amont dans ce chapitre, excepté qu’il n’accepte pas de paramètre spread et qu’il n’est pas
possible de définir de multiples ombres.
Ces restrictions viennent de la façon dont drop-shadow() est implémenté : il s’agit d’une
copie du masque alpha de l’image, floutée, déplacée et rendue dans une couleur en
particulier sous l’image.
Figure 4–38
filter: drop-shadow(0 0 1em red)
Figure 4–39
À gauche le pseudo-élément est intégré dans le tracé de l’ombre via drop-shadow alors qu’à droite, il est omis avec box-
shadow.
grayscale
filter: grayscale( [ <number> | <percentage> ] )
Le filtre grayscale() permet de transformer une image en noir et blanc. Le paramètre passé à
la fonction définit la proportion de conversion. 0% (ou 0) laisse l’image dans son état initial
alors qu’une valeur de 100% (ou 1) rend l’image intégralement noire et blanche.
Les valeurs en dessous de 0 sont interdites et donc considérées comme invalides. En
revanche, les valeurs supérieures à 100 % sont autorisées mais n’ont pas plus d’effet que
la valeur 100%. C’est le rôle du navigateur de restreindre la valeur au maximum.
Figure 4-41
filter: grayscale(100%)
Figure 4-42
À gauche le SVG original, à droite le même avec le filtre grayscale appliqué
hue-rotate
filter: hue-rotate( <angle> )
Figure 4-43
filter: hue-rotate(100deg)
invert
filter: invert( [ <number> | <percentage> ] )
Figure 4-44
filter: invert(1)
opacity
filter: opacity( [ <number> | <percentage> ] )
Le filtre d’opacité est très semblable à la propriété opacity que nous avons vue un peu plus
tôt dans ce chapitre à deux différences près.
• Il accepte aussi bien une valeur en pourcentage qu’en flottant (comme la majorité des
autres filtres).
• Il peut faire partie d’une chaîne de filtres, ayant donc un impact sur le rendu des filtres
suivants, contrairement à la propriété opacity qui agit après le rendu des filtres.
Figure 4-45
filter: opacity(.33)
saturate
filter: saturate( [ <number> | <percentage> ] )
Figure 4-46
filter: saturate(.33)
sepia
filter: sepia( [ <number> | <percentage> ] )
Figure 4–47
filter: sepia(100%)
<ul>
<li data-hue="0" class="is-selected">Rouge</li>
<li data-hue="120">Vert</li>
<li data-hue="240">Bleu</li>
</ul>
$('[data-hue]').on('click', function () {
item = $(this);
tint = item.data('hue');
item
.addClass('is-selected')
.siblings()
.removeClass('is-selected');
image.css({
'-webkit-filter': 'hue-rotate(' + tint + 'deg)',
}());
Au clic sur un des éléments de liste, le script JavaScript récupère la valeur de l’attribut
data-hue, et le passe au filtre hue-rotate() appliqué à l’image du produit. Ainsi, une seule et
unique requête HTTP est effectuée : celle de l’image. Les différentes teintes du produit
sont gérées via CSS exclusivement.
Cette technique demande un processus pour déterminer les bonnes valeurs de hue-rotate
pour chaque déclinaison du produit, mais le nombre d’images requises chute
drastiquement : une par produit.
Figure 4–48
Une seule image utilisée, altérée par le filtre hue-rotate
var timer;
window.addEventListener('scroll', function() {
clearTimeout(timer);
if (body.style.pointerEvents !== 'none') {
body.style.pointerEvents = 'none';
}
timer = setTimeout(function() {
body.style.pointerEvents = defaultPE;
}, 500);
}, false);
}());
Le problème avec la propriété pointer-events est qu’elle n’est supportée qu’à partir
d’Internet Explorer 11, mais comme on dit, mieux vaut tard que jamais. En revanche, il
existe des solutions implémentées en JavaScript si vous avez impérativement besoin de
cette propriété dans un projet devant supporter Internet Explorer 10 et les versions
antérieures à celles-ci.
Des images comme bordures
Il a longtemps été possible d’utiliser une image comme bordure grâce à la propriété border-
image, mais le support a toujours été relativement mauvais. Au-delà de ça, certaines des
implémentations étaient parfois approximatives, à la hauteur de la spécification.
En somme, border-image a toujours été le vilain petit canard des propriétés dédiées à
l’embellissement des documents. Même aujourd’hui, alors que les spécifications sont
finalisées, il est toujours très difficile d’utiliser correctement cette fonctionnalité.
La syntaxe est complexe, les prérequis sont nombreux et exigeants, le tout pour un rendu
souvent discutable. C’est pour ces raisons que nous n’allons voir ici qu’un aperçu léger de
cette propriété. Je vous encourage donc à l’expérimenter par vous-même si le sujet vous
passionne.
Avant d’aller plus loin, il faut savoir que la propriété border-image n’est en réalité qu’un
raccourci pour la collection de propriétés relatives aux images comme bordures, toutes
préfixées par border-image-*. Aussi, il est possible de n’utiliser que la propriété border-image,
ou les autres, ou un mélange des deux, comme pour les autres propriétés raccourcies.
Comment ça marche ?
L’application d’une image comme bordure fonctionne selon une découpe en neuf parties :
quatre zones d’angles, quatre zones de côtés et une zone centrale. L’image désignée via
border-image-source est divisée en neuf parties formant ainsi une grille de trois par trois selon
les dimensions renseignées par la propriété border-image-slice.
Les quatre zones d’angle se placeront dans les angles de la zone de bordure de l’élément.
Le comportement adopté par les quatre zones de côté est paramétrable via la propriété
border-image-repeat. La zone centrale est omise, considérée comme transparente, sauf si la
propriété border-image-slice l’indique autrement.
Syntaxe
border-image-source
border-image-source: none | <image>
La propriété border-image-source spécifie une image à utiliser à la place du style donné par
border-style. Dans le cas où la valeur est à none, que l’image ne peut pas être utilisée
(chemin incorrect, par exemple), ou que la propriété n’a simplement pas d’effet, la valeur
définie par border-style prendra effet.
En l’occurrence, la valeur <image> détermine le chemin vers un fichier image valide défini
par la fonction url(), comme dans le cas d’un arrière-plan.
border-image-slice
border-image-slice: [<number> | <percentage>]{1,4} && fill?
La syntaxe de cette propriété ressemble énormément à celle des propriétés margin et padding
si ce n’est que la ou les valeurs renseignées le sont en pourcentage ou nombres sans unité,
contrairement aux deux propriétés susnommées pour lesquelles des pourcentages ou des
longueurs sont attendus.
Dans le cas où des valeurs seraient renseignées sans unité, elles correspondent à des pixels
si l’image est non vectorielle, ou à des coordonnées de vecteur si elle l’est.
Ces valeurs, qu’elles soient renseignées ou automatiquement calculées, servent au
découpage de l’image en neuf parties. Elles définissent donc les offsets vis-à-vis des bords
haut, droit, bas et gauche, respectivement.
Figure 4–49
Le découpage de la zone d’image en neuf parties selon le W3C
Si le mot-clé fill est renseigné, la zone centrale est alors appliquée par-dessus l’arrière-
plan au lieu d’être laissée transparente.
border-image-width
border-image-width: [ <length> | <percentage> | <number> | auto ]{1,4}
La propriété border-image-width est assez trompeuse dans le sens où elle ne définit pas la
largeur de la bordure comme on pourrait le croire ; ceci est le rôle de border-width. En effet,
border-image-width détermine les dimensions de la zone d’application de l’image.
À l’instar des propriétés padding et margin, border-image-width accepte de une à quatre valeurs
renseignées en longueurs, pourcentages ou, cette fois à l’inverse des propriétés
susnommées, en nombres sans unité auquel cas il s’agit d’un ratio vis-à-vis de la propriété
border-width.
Par défaut, sa valeur est de 1 ; en somme cela signifie « une fois la dimension de border-
width ». C’est généralement la valeur que vous allez choisir, mais cela peut varier.
Si la valeur (peu importe la façon dont elle est renseignée : longueur, pourcentage,
nombre…) est plus grande que l’épaisseur de la bordure, la zone d’image empiétera sur
l’arrière-plan. À l’inverse, si elle est plus petite, seule une partie de la zone de bordure sera
imagée.
border-image-outset
border-image-outset: [ <length> | <number> ]{1,4}
Cette propriété détermine à quel point hors du modèle de boîte l’image va être rendue. Par
défaut, elle est à 0, à savoir à l’intérieur de l’élément. Si la valeur est supérieure à 0, alors
l’image est rendue hors de l’élément, sans pour autant perturber ce qu’il y a autour.
Là encore, cette propriété fonctionne sur le même modèle que padding et margin.
border-image-repeat
border-image-repeat: [ stretch | repeat | round | space ]{1,2}
C’est grâce à cette propriété que la magie va véritablement pouvoir opérer. Celle-ci
indique le comportement adopté par les quatre zones de côté, grâce aux quatre valeurs
possibles :
• stretch : l’image est étirée dans les zones de côté ;
• repeat : l’image est répétée dans les zones de côté ;
• round : l’image est répétée dans les zones de côté, quitte à être légèrement
redimensionnée pour qu’un nombre rond d’occurrences apparaisse ;
• space : l’image est répétée dans les zones de côté, et l’espace restant est distribué de
manière égale entre les occurrences.
Il est possible de spécifier deux valeurs différentes (ou identiques, auquel cas la seconde
est optionnelle) afin de déterminer deux comportements différents pour les axes X et Y.
À propos de la syntaxe
Il y a quelques éléments à connaître sur la syntaxe de calc pour s’en servir sans problème.
Tout d’abord, comme nous l’avons vu précédemment, seuls les opérateurs +, -, / et * sont
supportés. On note donc que modulo (%) n’est pas supporté.
Point important à propos de la syntaxe : les opérateurs + et - doivent être entourés
d’espaces pour être interprétés. En effet, la valeur calc(1em -5px) sera interprétée comme
une liste de deux valeurs séparées par un espace et non pas un calcul mathématique. De
fait, je recommande de mettre également des espaces autour des signes / et * même s’ils ne
sont pas nécessaires, principalement dans un souci de cohérence.
Vous pouvez bien évidemment utiliser des parenthèses internes à votre calcul afin de
faciliter la compréhension ou pour forcer la précédence des opérations + et - sur * et /. Et
enfin, les divisions par 0 sont bien sûr proscrites.
}
.sidebar {
width: 250px;
.content {
width: calc(100% - 250px);
Et voila, c’est aussi simple que ça. Attendez que l’on ait accès aux variables natives (voir
chapitre 7) pour insérer ce 250px dans une variable afin d’éviter la répétition et ce sera le
plus simple et le plus beau code que vous n’aurez jamais vu !
Figure 5–1
Utilisation de calc pour contourner l’absence de support du nouveau modèle de background-position sur Safari 6
height: 8em;
position: absolute;
left: 50%;
top: 50%;
Fonctionnel, mais il y a plus élégant avec la fonction calc ! Au lieu de décaler l’élément de
50 % vers la droite et vers le bas pour ensuite le redécaler vers le haut et vers la gauche
avec une marge négative, pourquoi ne pas le décaler de la valeur exacte dès le départ ?
.element {
width: 20em;
height: 8em;
position: absolute;
font-size: 1.2em;
}
Pour une raison quelconque, nous souhaitons que nos éléments de liste soient légèrement
plus gros que le corps de texte, aussi nous leur appliquons une taille de police de 1.2em, soit
1,2 fois la taille du parent (qui est très probablement un ul) qui lui a une police par défaut à
1em, soit 1 fois la taille du parent, et ainsi de suite jusqu’à atteindre l’élément html. Quoi
qu’il en soit, cette règle applique une taille de 19.2px à nos éléments de liste (1.2 × 1 × 1 …
× 16 px).
Figure 5–2
Les éléments de liste ont une taille de police de 1.2 em, soit 120 % la taille normale.
Jusque-là, aucun souci. Maintenant, imaginons que nous ayons une liste imbriquée dans
une liste, comme ceci :
<ul>
<li>1</li>
<li>
<ul>
<li>1.1</li>
<li>1.2</li>
</ul>
</li>
</ul>
Quand bien même les éléments de liste de premier niveau se comporteront toujours de la
même façon, on va faire face à un souci avec les éléments de liste de second niveau : leur
taille est définie à 1,2 fois celle de leur parent, un ul, qui est enfant d’un li dont la taille est
déjà à 1,2 fois celle du parent. En somme, la taille de la police des li imbriqués n’est pas à
19.2px mais 23px (1.2 × 1 × 1.2 × 1 … × 16 px).
Figure 5–3
Les éléments de la liste imbriquée ont une taille de 1.2 em, soit 120 % du parent qui lui-même a une taille de 120 % celle
de son parent, soit 144 %.
C’est précisément ce genre de problèmes que l’unité rem tente de corriger. En rendant les
dimensions non pas relatives à la fonte du parent mais à celle de l’élément racine, on évite
les soucis de cascade tout en gardant une unité relative (permettant, par exemple, le zoom
texte).
margin-top: 0;
margin-bottom: 1em;
}
h1 {
h2 {
font-size: 2.8rem; /* == 44.8 pixels */
}
h3 {
h4 {
h5 {
font-size: 1.8rem; /* == 28.8 pixels */
}
h6 {
font-size: 1.6rem; /* == 25.6 pixels */
Figure 5–4
Mise en place d’un rythme vertical simple en rem
L’utilisation est très simple. Au lieu de saisir à la main la taille de police en rem, il suffit
d’inclure le mixin en spécifiant la taille désirée. Celui-ci se chargera de définir la règle en
pixels et de calculer la valeur en rem. Par exemple :
.element {
@include rem(14px);
}
font-size: 14px;
font-size: 0.875rem;
Non seulement on automatise le fallback pour Internet Explorer 8 (et les autres
navigateurs obsolètes) mais en plus on s’épargne les calculs en les remettant entre les
mains du préprocesseur.
Toutefois, notez que l’on sert du coup des valeurs en pixels pour les quelques navigateurs
ne supportant pas rem, ce qui peut empêcher le zoom texte. À vous de voir si vous pouvez
vous permettre l’utilisation de rem avec un repli en px, ou s’il vaut mieux rester sur em.
CONSEIL Favoriser un postprocesseur
Utiliser un préprocesseur pour la mise en place de fallbacks pour rem n'est pas
nécessairement une bonne idée. Cela peut rendre la maintenance délicate et augmente
considérablement le nombre de lignes de code. Je recommanderais plutôt d'utiliser un
postprocesseur, à savoir un outil qui balaie les feuilles de styles une fois compilées
pour les modifier. Dans notre cas, un outil tel que px_to_rem est tout à fait adapté.
http://bit.ly/px-to-rem
Démystification : à propos de 62.5 %
Dans le but de simplifier les calculs de tailles de police, il est coutume de définir la taille
de la police de la racine du document (l’élément html) à 62.5 % via la déclaration suivante :
/**
* Taille par défaut du navigateur : 16 px
* 16 × 62.5% = 10 px
*/
html {
font-size: 62.5%;
}
En effet, tous les navigateurs ont par défaut une taille de police de 16 pixels. Or 16 × 62.5
vaut 10, ce qui rend tous les calculs autour de l’unité rem extrêmement évidents. Besoin de
l’équivalent de 16 pixels ? Facile, c’est 1.6rem.
Malheureusement, je me dois de faire l’avocat du diable et de vous intimer de cesser cette
mauvaise pratique. Effectivement, cette astuce transforme des calculs pénibles en simples
opérations mentales, mais elle présente aussi des inconvénients.
En effet, elle va nécessiter de redéfinir la taille de la police de tous les conteneurs textuels,
tels que p, li, les titres, et bien d’autres encore. Les calculs sont donc plus simples, mais en
même temps plus nombreux !
En définissant la taille de police de la racine du document à la taille de texte désirée,
généralement entre 12 et 20 pixels, on s’abstient de devoir redéclarer la font-size sur tous
les éléments !
Quant à la complexité des calculs, c’est un souci généralement pris en charge par le post
/préprocesseur. Dans le cas où vous n’en utiliseriez pas, la calculette reste votre meilleur
allié !
Si comme moi, vous êtes un utilisateur de Sass (ou de tout autre préprocesseur), on peut
utiliser le même principe que pour le mixin vu précédemment :
// On déclare une taille de base,
}
// La fonction rem se charge de
// la conversion px en rem.
@function rem($value) {
@return $value / $base-font-size * 1rem;
padding: 5ch;
}
<script>
document.getElementById('autosized').onkeypress = function () {
el.style.width = el.value.length + 'ch';
</script>
font-size: 2vw;
}
Cas pratique : typographie responsive
Il arrive que vous vouliez dimensionner un contenu textuel – un titre, par exemple – en
fonction de la largeur de la fenêtre ; c’est un procédé graphique assez courant de nos jours
(notamment dans les slides, entre autres).
Cependant, vous ne voulez pas d’une taille fixe qui implique des retours à la ligne
différents en fonction de la largeur de la fenêtre : vous voulez que la taille de la police
s’adapte à la largeur de l’écran, de sorte que les retours à la ligne soient toujours les
mêmes.
/**
* Titre principal
.masthead {
font-size: 9vw;/* 1 */
padding: 0 1em; /* 2 */
Figure 5–6
Typographie responsive grâce à vw sur un écran de grande taille
Figure 5–7
Typographie responsive grâce à vw sur un écran de taille moyenne
Figure 5–8
Typographie responsive grâce à vw sur un écran de petite taille
/**
* La règle suivante correspond à
*/
.element {
font-size: 1.2rem;
Cette technique aurait l’avantage de ne déclarer qu’à un seul endroit une taille relative au
viewport (la racine du document), les tailles de police seraient alors définies de manière
proportionnelle à l’élément html via rem.
Si demain on veut basculer sur une typographie non responsive, il suffit de changer la
valeur de font-size de html, sans avoir à repasser sur toutes les feuilles de styles.
*/
.box {
height: 100vh; /* 1 */
}
Figure 5–9
Un ratio 4:3 maintenu aux dimensions maximales pour tenir dans l’écran en mode paysage
Maintenant, grâce à une Media Query (voir chapitre 8) nous détectons le mode portrait et
appliquons des styles spécifiques :
@media (max-aspect-ratio: 4/3) {
/**
*/
.box {
width: 100vw; /* 1 */
height: 75vw; /* 2 */
top: calc(50vh - 3 / 4 * 100vw / 2); /* 3 */
left: auto; /* 4 */
}
Figure 5–10
Un ratio 4:3 maintenu aux dimensions maximales pour tenir dans l’écran en mode portrait
Les versions de Safari sur iOS 6 et iOS 7 sont connues pour avoir des soucis de rendu
avec l’unité vh (corrigés dans iOS 8). L’unité en elle-même est tout à fait supportée, en
revanche les calculs sont erronés. Lorsqu’un élément a une hauteur de 100vh par
exemple, et que le layout est recalculé par le navigateur (scroll, changement
d’orientation, entre autres), alors l’élément ne fait plus 100 % de la hauteur du
viewport mais davantage.
Si vous avez vraiment besoin d’utiliser l’unité vh de cette façon sur Safari iOS, sachez
qu’il existe un script JavaScript visant à corriger ce problème.
http://bit.ly/viewport-units-buggyfill
De plus, Safari sur iOS 7 définit les valeurs utilisant les unités de viewport à 0 si la
page est quittée puis réouverte dans les 60 secondes.
Pour finir, Safari sur iOS 7 recalcule les largeurs (width) définies en vh en vw, et les
hauteurs définies en vw en vh quand l’orientation de l’appareil change.
CHROME 33 ET IOS 6 ET 7 Problème d’interprétation des unités dans certains cas
En plus des problèmes mentionnés précédemment, Safari sur iOS 6 et 7, ainsi que
Chrome 33 et versions inférieures ne supportent pas les unités relatives au viewport
pour les propriétés border-width, column-gap, transform, box-shadow et toutes les propriétés
raccourcies auxquelles celles-ci sont rattachées. Similairement, les valeurs en rem
rendent la fonction calc() invalide.
WEBKIT Problème de rafraîchissement
Bien que Chrome et Safari supportent sans mal ces nouvelles unités, et que les
spécifications stipulent explicitement que lors du redimensionnement de la fenêtre le
calcul de ces unités est supposé être réeffectué, l’implémentation de ces deux
navigateurs est partiellement défectueuse puisque le calcul n’est réalisé qu’au
chargement de la page.
Si vous désirez forcer le rafraîchissement du calcul de ces unités lors du
redimensionnement du viewport, vous pouvez déclencher un repaint sur les éléments
dont une propriété utilise une valeur en vh ou vw. Pour cela, un simple écouteur
JavaScript suffit :
window.onResize = function () {
document.body.style.zIndex = 'auto';
}
*/
iframe {
width: 100vmin; /* 1 */
height: 56.25vmin; /* 2 */
}
Voila tout ce dont nous avons besoin. Quelle que soit l’orientation de l’écran ou sa taille,
la vidéo occupera toujours un maximum de place sans jamais déborder de l’écran.
<figure>
<img src="/images/vespa.jpg" alt="" />
<figcaption>Ceci est une très longue légende à propos de la photographie ci-dessus. Elle tire
inutilement en longueur et n’apporte rien de plus que ce que vous voyez déjà.</figcaption>
</figure>
width: min-content;
margin: 0 auto 1em;
padding: 1em;
background: #EFEFEF;
border: 1px solid rgba(0, 0, 0, .1);
figure img {
display: block;
margin-bottom: 1em;
}
figcaption {
line-height: 1.3;
}
Figure 5–11
L’élément figure est dimensionné selon son contenu le moins large, en l’occurrence l’image.
Figure 5–12
Avec max-content, la largeur de l’élément figure est déterminée par le contenu le plus large, ici la légende. Résultat : un
débordement horizontal.
padding: 1em;
border: 1px solid;
width: fill;
}
Quand elle est appliquée comme valeur pour la propriété height, cela fonctionne de la
même manière, seulement ce n’est pas un comportement qui existe hors de cette
déclaration à l’heure actuelle.
<li><a href="…">Home</a></li>
<li><a href="…">Blog</a></li>
<li><a href="…">About</a></li>
<li><a href="…">Contact</a></li>
</ul>
Et le CSS :
/**
* 1. Dimensionnement
* 2. Centrage
*/
ul {
width: fit-content; /* 1 */
margin: auto; /* 2 */
}
li {
display: inline-block;
}
Et voilà !
Figure 5–13
Une liste centrée et dimensionnée selon son contenu grâce à fit-content
#{$property}: -webkit-#{$value};
#{$property}: -moz-#{$value};
#{$property}: $value;
}
// Si la valeur est fill, available ou fill-available,
#{$property}: -moz-available;
#{$property}: fill;
}
}
}
}
6
Contrôle du texte
Bien que nous ne soyons toujours pas en mesure de répliquer à l’identique les mises en
page des magazines papier, CSS nous offre toujours plus de contrôle sur nos contenus
textuels.
Le contenu le plus important sur Internet reste le texte, dans la majorité des cas. Peuvent
éventuellement être exclues les galeries d’images, mais elles sont suffisamment rares pour
que l’on puisse statuer sur le fait que la plupart des sites sont constitués essentiellement de
contenus textuels. Parfois en grande quantité, notamment dans le cas des magazines qui
tendent de plus en plus à se tourner vers Internet pour leurs publications.
Aussi est-il normal que CSS apporte autant de contrôle sur le texte que le feraient d’autres
outils tels que Publisher ou InDesign. Nous avons vu dans les chapitres précédents qu’il
est possible de distribuer le texte dans plusieurs colonnes grâce au module CSS Columns
mais ça ne fait pas tout ! Qu’en est-il des césures ? De l’interlettrage ? De l’alignement ?
Tant de problématiques typographiques auxquelles CSS n’était simplement pas préparé
jusqu’à encore peu. Mais aujourd’hui, les nouveautés du langage, notamment celles
s’attelant à la typographie sur le Web, introduisent une pléthore d’outils afin de conférer
aux designers et développeurs un contrôle plus fin sur leurs contenus textuels.
Je vous propose donc de dédier ce chapitre à la découverte (ou redécouverte pour certains)
de ces propriétés.
La gestion des débordements avec overflow-wrap
La propriété overflow-wrap vise à déterminer si le navigateur est à même d’effectuer une
coupure à l’intérieur des mots si ceux-ci sont trop longs pour tenir sur une seule ligne au
sein de leur conteneur.
En temps normal, celui-ci ne s’autorise un retour à la ligne que sous deux conditions :
présence d’un espace sécable ou d’un trait d’union. Cette propriété permet donc au
navigateur de couper où il le souhaite. Elle ne propose que deux valeurs.
• normal, qui est la valeur par défaut et indique que le navigateur ne peut pas s’autoriser à
couper un mot en deux dans le cas où il serait trop long.
• break-word, qui indique que le navigateur peut arbitrairement couper un mot s’il est trop
long pour tenir dans son conteneur.
À noter que cette propriété n’aura d’effet que dans le cas où un mot serait trop long pour
rentrer sur une seule ligne dans son conteneur. On ne parle pas là de césure classique, qui
est, quant à elle, gérée par hyphens (voir plus loin). C’est pourquoi aucun caractère (tel que -
) ne sera inséré à l’emplacement de la césure.
Il est relativement rare que des termes soient trop longs pour déborder de leur conteneur.
En revanche, c’est quelque chose qui peut tout à fait se produire dans le cas des URL,
chaînes de caractères parfois très longues. Pour éviter qu’elles débordent hors du
conteneur et forcent un retour à la ligne, on peut tout à fait utiliser la propriété overflow-
wrap.
/**
*/
.container {
overflow-wrap: break-word;
}
word-wrap: break-word;
overflow-wrap: break-word;
}
La gestion des espaces avec white-space
Par défaut, en HTML, les espaces successifs sont fusionnés en un espace unique (ou
même aucun espace dans certains cas). C’est pour cette raison que vous pouvez indenter
votre code HTML sans que cela ait d’impact sur le rendu de votre page. Ceci étant dit, il y
a des cas de figure où l’on souhaite conserver ces espaces et indentations, notamment dans
le cas où l’on souhaite présenter du code.
La propriété white-space permet de spécifier si et comment les espaces au sein d’un élément
sont fusionnés, et si les lignes peuvent être générées aux opportunités de création de ligne
habituelles. C’est grâce à cette propriété que l’on peut faire en sorte que du code indenté à
l’écriture reste indenté à l’affichage.
.element {
white-space: normal | pre | pre-wrap | nowrap | pre-line;
}
Comme vous pouvez le constater, les possibilités sont multiples et il n’est pas toujours
évident de choisir la bonne valeur d’autant qu’elles se ressemblent toutes plus ou moins.
*/
.clearfix::after {
content: '';
display: table;
clear: both;
}
Le code précédent correspond à celui que vous souhaitez afficher : indentation et retours à
la ligne compris. On va utiliser le combo pre > code pour créer un bloc d’affichage de code.
<pre><code>/**
*/
.clearfix::after {
content: '';
display: table;
clear: both;
}</code></pre>
Figure 6–2
Avec pre, les espaces et les retours à la ligne sont préservés, mais les lignes trop longues déclenchent un débordement
(ce qui peut être souhaité dans ce cas).
Figure 6–3
Avec nowrap, les espaces et les retours à la ligne ne sont pas préservés et il y a un débordement, produisant ainsi un
résultat sur une ligne unique.
Figure 6–4
Avec pre-wrap, les espaces et les retours à la lignes sont préservés, mais il n’y a pas de débordement.
Figure 6–5
Avec pre-line, les retours à la ligne sont préservés, mais les espaces sont fusionnés ce qui pose un souci par rapport à
l’indentation du code.
Autrement dit, si vous désirez afficher du code à l’écran, il est fort probable que vous vous
rabattiez sur la valeur pre (ou pre-wrap si vous souhaitez éviter les débordements) afin de
conserver l’indentation du code.
pre code {
white-space: pre;
}
Les versions des navigateurs supportant l’intégralité des valeurs de white-space sont
présentées dans les tableaux précédents.
Ceci étant, la majorité des navigateurs supportaient déjà certaines valeurs dans des
versions antérieures, notamment :
• Internet Explorer 5.5 supportait normal et nowrap ;
• Internet Explorer 6 supportait normal, pre et nowrap ;
• Firefox 1 supportait normal, pre, nowrap et -moz-pre-wrap ;
• Firefox 3 supportait normal, pre, nowrap et pre-wrap ;
• Opera 4 supportait normal, pre et nowrap ;
• Opera 8 supportait normal, pre, nowrap et pre-wrap ;
• Safari 1 supportait normal, pre et nowrap.
Les débordements de texte et text-overflow
À nouveau la gestion des débordements ? Et oui ! En fait, nous avions besoin d’étudier la
propriété white-space entre temps pour que vous puissiez bien comprendre nos futurs
exemples.
La propriété text-overflow a pour but de gérer le rendu du texte quand il déborde de son
conteneur ; soit parce qu’on l’empêche d’être étalé sur plusieurs lignes (via white-space:
nowrap), soit parce qu’une chaîne de caractères est simplement trop longue pour rentrer sur
une seule ligne, par exemple.
.element {
La syntaxe pour cette propriété est très permissive, puisque non seulement elle autorise les
valeurs clip et ellipsis mais aussi n’importe quelle chaîne de caractères. Commençons par
expliquer les valeurs :
• clip : cache la partie débordante (un caractère peut donc être à moitié rendu) ;
• ellipsis : cache la partie débordante et affiche des points de suspension (U+2026) ;
• <string> : cache la partie débordante et affiche la chaîne de caractères renseignée.
ATTENTION Syntaxe à deux valeurs et valeur <string>
Les spécifications précisent qu’il est possible de spécifier deux valeurs, une pour le
bord end (droit en mode d’écriture de gauche à droite) et une pour le bord start (gauche
en mode d’écriture de gauche à droite). Toutefois, celles-ci mentionnent que la syntaxe
à deux valeurs et la valeur <string> sont considérées « à risque », c’est-à-dire qu’elles
sont susceptibles d’être retirées du module. Je vous recommande donc de ne pas les
utiliser.
Il devient donc possible de faire en sorte qu’un contenu ne tienne que sur une ligne, et que
le débordement soit caché via CSS par des points de suspension (ou tout autre chaîne de
caractères). S’agissant d’une solution purement native à CSS, le contenu en lui-même
reste tout à fait accessible et indexable.
En revanche, pour que cette propriété ait un effet, il faut que la valeur d’overflow du
conteneur soit définie à autre chose que visible, généralement hidden.
/**
*/
.ellipsis {
white-space: nowrap; /* 1 */
overflow: hidden; /* 2 */
text-overflow: ellipsis; /* 3 */
}
CURIOSITÉ À propos de text-overflow et overflow-wrap
Dans un cas où white-space: nowrap ne serait pas impliqué (c’est-à-dire du contenu sur
plusieurs lignes), on remarque une certaine incompatibilité entre les propriétés text-
overflow et overflow-wrap (word-wrap) si cette dernière a pour valeur break-word.
En effet, cette déclaration spécifie explicitement d’effectuer une césure en fin de ligne
si les mots sont trop longs, alors que text-overflow déclare de son côté qu’il faut cacher
les débordements.
En l’occurrence, il n’y a pas de débordement car c’est word-wrap: break-word qui prend le
pas et les mots sont coupés par un retour à la ligne.
<tr>
<th>Filename</th>
<th>Last commit description</th>
</tr>
</thead>
<tbody>
<tr>
<td>src</td>
<td><p>fix: add implicit type for required placeholders</p></td>
</tr>
<tr>
<td>.gitignore</td>
<tr>
<td>README.md</td>
<tr>
<td>package.json</td>
<td><p>Bump patch 1.5.2</p></td>
</tr>
<!-- Autant de lignes qu’on le souhaite -->
</tbody>
</table>
Dans un souci de simplicité, on ne discutera pas ici des styles destinés à embellir notre
tableau ; allons à l’essentiel et contentons nous de rendre la description du dernier commit
(la seconde colonne de chaque rangée) fluide.
Considérez l’exemple suivant :
/**
* 1. Donne une largeur implicite aux colonnes
*/
table {
table-layout: fixed; /* 1 */
width: 100%;
}
/**
* 1. Force l’affichage du contenu sur une seule ligne
* 2. Masque les débordements de la cellule
*/
td p {
white-space: nowrap; /* 1 */
overflow: hidden; /* 2 */
text-overflow: ellipsis; /* 3 */
}
Figure 6–6
Notre tableau de commits sur un écran de grande taille
Figure 6–7
Notre tableau de commits, sur un écran de petite taille, non déformé
Syntaxe
La propriété hyphens accepte trois valeurs uniquement :
hyphens: none | manual | auto
Parce que chaque valeur nécessite un certain nombre de détails afin de bien comprendre
comment elle fonctionne, je vous propose de les décortiquer une par une.
none
De manière tout à fait évidente, la valeur none empêche les césures. Les mots ne sont
absolument jamais divisés en fin de ligne, même si un caractère autoriserait parfaitement
le navigateur à effectuer une césure à cet endroit. Autrement dit, les lignes ne sont coupées
qu’aux espaces blancs et aux traits d’union.
Figure 6–8
Les césures sont proscrites et résultat, les espaces entre les mots sont parfois disgracieux.
p {
hyphens: manual;
}
auto
Finalement, la valeur auto autorise le navigateur à effectuer des césures non seulement
dans les cas de la valeur manual (hyphen et soft hyphen) mais également aux endroits
déterminés par un dictionnaire de césure spécifique à la langue utilisée (supporté par le
navigateur de manière native, ou renseignée par la directive @hyphenation-resource, non
traitée ici). En français, par exemple, on aura tendance à couper une ligne entre deux
lettres identiques successives quand c’est possible, comme le veut la règle typographique.
Figure 6–9
Les césures sont autorisées, du coup il y a moins d’espaces trop larges.
À noter que les opportunités de césure universelles (manual) prennent le pas sur les cas de
césure relatifs à la langue dans le cas où hyphens aurait pour valeur auto.
En revanche, seuls Internet Explorer et Firefox supportent la valeur keep-all ; tous les
autres navigateurs ne supportent que normal et break-all. Fort heureusement, cette dernière
est la plus utilisée.
La gestion des tabulations avec tab-size
Poursuivons sur une fonctionnalité directement liée à la propriété white-space. Lorsque vous
définissez la valeur de la propriété white-space à pre ou pre-wrap (qui conservent les espaces
et les tabulations) et que le contenu de l’élément a été indenté avec des tabulations (U+0009),
vous ne pouvez pas savoir à l’avance le rendu que cela va donner puisque la notion de
tabulation n’est pas une mesure fixe mais un caractère dont la taille peut varier.
Aussi étonnant que cela puisse paraître, la taille par défaut d’une tabulation est de 8
espaces (le caractère U+0020). Dans la majorité des cas, c’est bien trop, aussi vous allez
vouloir réduire cette distance à une valeur plus convenable, comme deux ou quatre
espaces (selon les choix de chacun sur l’indentation).
Pour cela, la propriété tab-size existe. Elle accepte un nombre ou une longueur comme
valeur. S’il s’agit d’un entier, la valeur indique l’équivalence vis-à-vis du nombre
d’espaces (U+0020), par défaut 8 (oui, oui… 8). Si c’est une longueur, elle indique tout
simplement la longueur d’une tabulation.
pre code {
tab-size: 2;
Figure 6–10
Un exemple de ponctuation hors justification (ou « suspendue »)
Cette notion typographique a été portée en CSS par la propriété hanging-punctuation. Pour
faire simple, celle-ci accepte trois valeurs bien différentes :
• none : pas de ponctuation hors justification (valeur par défaut) ;
• first : un guillemet ou une parenthèse ouvrante en début de ligne, est déplacé dans la
marge ;
• last : un guillemet ou une parenthèse fermante en fin de ligne, est déplacé dans la marge.
CLARIFICATION Caractères impactés
Je mentionne « un guillemet ou une parenthèse », mais il faut savoir que c’est un
comportement qui s’applique en réalité à tous les caractères Unicode des catégories Ps
(ouverture de ponctuation), Pe (fermeture de ponctuation), Pi (guillemet ouvrant) et Pf
(guillemet fermant).
Notons en réalité que les spécifications mentionnent également :
• force-end : un point ou une virgule en fin de ligne est nécessairement déplacé dans la
marge ;
• allow-end : un point ou une virgule en fin de ligne est déplacé dans la marge si elle ne
tient pas sur la ligne avant la justification.
Néanmoins, ces deux dernières valeurs sont selon moi trop spécifiques pour avoir un réel
intérêt. Non seulement elles sont si similaires que la distinction aurait pu être épargnée,
mais de plus elles n’affectent qu’une collection restreinte de caractères (voir tableau
suivant).
Figure 6–11
La valeur allow-end
Figure 6–12
La valeur force-end
La notion de « un point ou une virgule » des propriétés force-end et allow-end réfère à la liste
de caractères suivante.
Tableau 6–14 Caractères englobés dans la notion de « un point ou une virgule »
Cette liste n’est pas exhaustive, aussi les navigateurs sont-ils tout à fait autorisés à y
ajouter des caractères s’ils le jugent approprié.
PLUS D’INFOS
Si les jeux de typographie vous intéressent, et pour aller plus loin dans l’utilisation de
hanging-punctuation, je vous suggère cet article de Steve Hickey dans lequel il décrit
comment les spécifications auraient selon lui dû être écrites.
http://bit.ly/hickey-hanging-punctuation
Figure 6–13
Centrage de la dernière ligne de texte grâce à text-align-last
unicode-range: U+A5;
/* Les caractères ASCII basiques */
unicode-range: U+0-7F;
@font-face {
font-family: DroidSans;
src: url(DroidSansFallback.woff);
}
/**
* Glyphes japonais (1.2Mo)
*/
@font-face {
font-family: DroidSans;
src: url(DroidSansJapanese.woff);
unicode-range: U+3000-9FFF, U+ff??;
}
/**
* Glyphes latins, grecs, cyrilliques
* et certains caractères de ponctuation (190Ko)
*/
@font-face {
font-family: DroidSans;
src: url(DroidSans.woff);
Maintenant, considérons un texte tout à fait standard comme cette phrase auquel on
appliquerait la police “ DroidSans ”.
<!-- HTML -->
<p>CSS, c’est fun !</p>
/* CSS */
p {
font-family: DroidSans;
Le navigateur effectue une vérification sur la dernière déclaration @font-face (set latin) :
tous les caractères de notre texte se trouvent dans l’intervalle Unicode U+000-5FF. Le
navigateur télécharge le fichier DroidSans.woff et applique la police au texte.
Maintenant, modifions notre texte initial pour y inclure un caractère spécial : une flèche
(⇨) dont le point de code Unicode est U+21E8, écrite via l’entité HTML ⇨.
<!-- Introduction du caractère dans le texte -->
Le navigateur effectue le même test que précédemment sur le set latin : l’intervalle U+2000-
2300 contient U+21E8 alors la police est téléchargée. Toutefois, elle ne contient pas de glyphe
pour ce caractère (c’est-à-dire que l’unicode-range déclaré n’est pas l’unicode-range effectif,
dans un souci de simplicité).
Le navigateur va alors évaluer la police contenant les glyphes japonais. La valeur de
unicode-range déclarée n’inclue pas U+21E8 puisqu’elle démarre à U+3000, alors cette police
n’est pas téléchargée.
Enfin, le navigateur évalue la dernière police qui n’a pas d’unicode-range spécifié, ce qui
revient à considérer tous les points de code Unicode. Elle est alors téléchargée et
appliquée.
Cet exemple montre comment il est possible d’optimiser la bande passante en s’épargnant
le téléchargement de polices non utilisables grâce à la propriété unicode-range. C’est bien
évidemment un cas d’usage que l’on va essentiellement rencontrer dans un contexte multi-
alphabet.
REMARQUE Google Fonts
Le service très populaire de Google, Google Fonts, utilise la propriété unicode-range
dans la feuille de styles desservie afin de limiter les téléchargements superflus.
Par exemple, pour la police Lobster, appelée avec le navigateur Chrome (précision qui
a son importance quand on sait que Google effectue de la détection de navigateur pour
ne servir que les formats supportés, en l’occurrence woff2) :
/* cyrillic */
@font-face {
font-family: ’Lobster’;
font-style: normal;
font-weight: 400;
src: local(’Lobster’),
url(http://fonts.gstatic.com/s/lobster/v9/MeFZ5NpSE1j8mC06Jh1miALUuEpTyoUstqEm5AMlJo4.woff2)
format(’woff2’);
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
/* latin-ext */
@font-face {
font-family: ’Lobster’;
font-style: normal;
font-weight: 400;
src: local(’Lobster’),
url(http://fonts.gstatic.com/s/lobster/v9/MCZn_h27nLxWmTqnbmnb3gLUuEpTyoUstqEm5AMlJo4.woff2)
format(’woff2’);
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-
A7FF;
}
/* latin */
@font-face {
font-family: ’Lobster’;
font-style: normal;
font-weight: 400;
http://bit.ly/fonts-googleapis
Figure 6–14
Une esperluette personnalisée grâce à unicode-range
Pour commencer, nous allons déclarer une police appelée « Ampersand », qui utilise la ou
les polices de votre choix dont vous considérez l’esperluette comme élégante. Cette police
va utiliser la propriété unicode-range pour ne déclarer que ce point de code Unicode.
@font-face {
font-family: 'Ampersand';
url('MyAwesomeFont.ttf') format('truetype');
unicode-range: U+26;
}
h1 {
Théoriquement, ce code devrait suffire. Je dis bien « théoriquement » car dans la pratique,
ce n’est jamais si facile. Pour les navigateurs qui supportent unicode-range, tout se passe
bien : s’il y a une esperluette dans le texte stylé, la police est téléchargée et appliquée sur
l’esperluette uniquement.
En revanche, les navigateurs qui n’interprètent pas la propriété unicode-range procèdent
comme avec n’importe quelle propriété non supportée : ils la passent. Or une déclaration
@font-face sans unicode-range utilise l’intervalle par défaut, c’est-à-dire l’intégralité
d’Unicode. Ces navigateurs vont donc appliquer votre police à tout le texte.
Il faut donc ruser pour s’assurer que seul le caractère esperluette soit concerné. Pour cela,
nous pouvons ajouter une seconde déclaration @font-face qui vient se cumuler à la première
ou la remplacer totalement selon le support d’unicode-range. Par exemple :
@font-face {
font-family: 'Ampersand';
url('MyAwesomeFont.svg') format('svg'),
url('MyAwesomeFont.ttf') format('truetype');
unicode-range: U+26;
}
@font-face {
font-family: 'Ampersand';
src: local('Helvetica'), local('Arial');
unicode-range: U+270C;
}
h1 {
font-family: 'Ampersand', 'Helvetica', 'Arial', sans-serif;
Vous avez remarqué que la seconde @font-face contient une déclaration d’unicode-range, sur
le point de code U+270C ; nous allons voir pourquoi dans un instant. De plus, les fichiers de
polices utilisées sont des fichiers locaux, c’est-à-dire des polices dites « système ».
Les navigateurs ne supportant pas unicode-range vont donc appliquer la deuxième
déclaration au texte (car définie plus en aval), à savoir la police Helvetica (ou Arial si
Helvetica n’existe pas).
En revanche, les navigateurs interprétant unicode-range font une vérification sur le texte afin
de voir si les caractères demandés sont présents dans l’intervalle Unicode. En
l’occurrence, l’intervalle Unicode de notre deuxième déclaration ne contient qu’un
caractère, très rarement utilisé : la « main de la victoire » (✌).
Le caractère n’étant pas utilisé dans notre cas, le navigateur remonte sur la première
déclaration. Il s’assure de la présence des caractères spécifiés dans l’intervalle Unicode –
l’esperluette – puis télécharge et applique la police spécifique à l’esperluette, et la police
suivante dans la « font stack » (Helvetica) pour le reste du texte. Tout le monde est
gagnant !
CLARIFICATION La main de la victoire
Nous avons utilisé le caractère de la main de la victoire (U+270) afin de nous assurer que
l’intervalle Unicode ne serait jamais effectif, mais vous pouvez tout à fait choisir un
autre caractère rare.
Cette astuce provient d’un article de Drew McLellan sur le site 24ways.org, que je
vous invite à lire si le sujet vous intéresse.
http://bit.ly/unicode-range
Compatibilité des navigateurs pour unicode-range
Tableau 6–19 Navigateurs desktop
Bien que les navigateurs clament supporter la propriété unicode-range, il faut savoir qu’ils ne
se comportent pas tous bien pour autant. Ci-dessous un récapitulatif de l’état du support de
cette fonctionnalité.
Tableau 6–21 À gauche le test ainsi que le résultat attendu ; dans chaque colonne, les polices téléchargées par le
navigateur. En gras les résultats corrects.
Cas pratique : des blocs de code qui donnent envie
Vous êtes probablement des lecteurs assidus de blogs techniques et par conséquent vous
avez sûrement déjà rencontré des blocs de code qui ne donnent pas du tout envie d’être
lus… Une indentation chaotique, des retours à la ligne apocalyptiques, pas de coloration
syntaxique, et bien d’autres rimes en -ique…
Pourtant, mettre en place des blocs de code corrects n’est pas si sorcier, surtout avec tout
ce que nous venons d’étudier ! Aussi je vous propose de voir comment faire avec un court
exemple de code.
pre > code {
/* Comportement */
display: block;
overflow-x: auto;
/* Espacements */
padding: 1em;
tab-size: 2;
margin-bottom: 1em;
/* Typographie */
font: 1em/1.5 "Consolas", "Monaco", "Lucida Console", "Liberation Mono", "DejaVu Sans Mono",
"Courier New", monotype;
white-space: pre;
/* Thème */
background: #EFEFEF;
border-left: .5em solid #777;
}
Dans un souci de lisibilité, j’ai séparé les déclarations dans diverses sections en les
précédant d’un commentaire. Notre première section traite du comportement du bloc de
code : notamment le fait que ce soit un élément de type block justement, et qu’il soit
possible de scroller de manière horizontale si les lignes sont trop longues.
Ensuite vient la gestion des espacements : un marge interne (padding) vient décoller le
contenu des bords du conteneur alors que la marge en bas (margin-bottom) permet juste de
s’assurer que tout contenu venant juste après le bloc de code soit séparé de celui-ci par une
gouttière. Concernant les tabulations, j’ai une préférence pour 2 espaces mais c’est un
débat dans lequel je ne rentrerai pas.
S’en suit la partie qui nous intéresse le plus, la typographie. Tout d’abord, il est de
coutume d’utiliser une police « monospace » pour afficher du code à l’écran (que ce soit
dans un navigateur ou un éditeur), c’est-à-dire une police dans laquelle tous les caractères
ont la même largeur.
Nous préservons aussi la mise en forme du code avec la déclaration white-space: pre. En
revanche, nous empêchons également les retours à la ligne (faute de place) en choisissant
pre plutôt que pre-wrap. Je vous accorde que c’est là une décision assez discutable car c’est
essentiellement subjectif. Certaines personnes n’aiment pas du tout avoir à scroller mais
personnellement, je trouve le code extrêmement pénible à lire quand il y a des retours à la
ligne.
Dans le cas où vous souhaiteriez remplacer white-space: pre par white-space: pre-wrap pour
autoriser les retours à la ligne afin d’éviter le scroll horizontal, je vous suggère d’ajouter la
déclaration hyphens: none pour éviter toute césure qui n’a pas lieu d’être dans du code.
Et enfin, nous appliquons une légère couleur de fond à notre bloc de code. Je n’ai pas
extrapolé davantage sur cette partie dans la mesure où c’est souvent un « colorateur
syntaxique » tiers qui va s’occuper de tout ce qui a attrait au thème.
Figure 6–15
Le rendu de notre bloc de code, et encore, sans colorateur syntaxique
RESSOURCE Prism.js
Prism.js est colorateur syntaxique léger et extensible écrit en JavaScript par Lea
Verou. Il s’adapte particulièrement bien aux blogs et aux slides.
http://bit.ly/prism-js
7
Variables natives
Vous les attendiez ? Les voilà enfin ! CSS propose finalement un système de variables
natives qui ne nécessite pas un programme tiers (post/préprocesseur).
Une des principales raisons qui a fait que les préprocesseurs CSS ont connu un tel succès,
est le manque crucial de variables dans le langage CSS. Bien que celui-ci soit relativement
simple sur le papier, dans les faits, il est parfois compliqué de maintenir une architecture
viable. C’est d’autant plus vrai que l’on supporte de plus en plus de choses avec CSS : les
écrans de diverses tailles, des médias de différents types, des thèmes, et bien plus encore.
Alors, autant il était envisageable de concevoir des sites avec des outils simples il y a 10
ans de cela, autant aujourd’hui la boîte à outils des CSS semble parfois manquer de
quelques ustensiles incontournables. C’est d’ailleurs précisément ce que les
préprocesseurs tentent de corriger, en attendant que les spécifications le fassent et que les
navigateurs adoptent ces dernières.
Une application, qu’elle soit de petite ou de grande ampleur, peut contenir énormément de
styles, et de par la nature simpliste de CSS, cela produit nécessairement beaucoup de
répétitions. Par exemple, un site aura sûrement une palette de couleurs, réutilisant les
mêmes trois ou quatre couleurs un peu partout. Modifier ces couleurs peut s’avérer
compliqué dans la mesure où elles sont répétées à de nombreux endroits, potentiellement
dans des fichiers différents, rendant le « chercher-remplacer » difficile (au-delà du fait que
ce soit potentiellement dangereux).
C’est pourquoi nous avons là une bonne nouvelle puisque les spécifications CSS
introduisent désormais un module destiné intégralement à l’implémentation de variables
natives à CSS
(connu sous le doux nom de CSS Custom Properties for Cascading Variables Module
Level 1), c’est-à-dire utilisables sans l’aide d’un outil ou d’un langage annexe.
Comment ça marche ?
Les variables CSS sont effectivement des variables au sens où on l’entend, c’est-à-dire des
espaces de stockage permettant de conserver une donnée afin de la réutiliser à divers
endroits. Mais d’un point de vue strictement technique, elles se définiraient plutôt comme
des « propriétés personnalisées » (d’où le terme officiel Custom Properties), dont la valeur
peut être retrouvée à partir du nom de la propriété.
Parce qu’il s’agit techniquement de propriétés CSS, elles sont dotées d’un certain nombre
de caractéristiques avantageuses qu’il faut bien comprendre, surtout lorsque l’on est
habitué aux variables d’un préprocesseur.
Tout d’abord, elles « cascadent », c’est-à-dire qu’une variable déclarée à la racine du
document cascadera sur tous les enfants, et sera donc accessible partout. C’est d’ailleurs
pour ça qu’il est coutume de déclarer les variables CSS dans la pseudo-classe :root, qui se
trouve systématiquement être la racine du document (soit <html> dans le cas du HTML),
afin qu’elles soient accessibles par tous les éléments du DOM.
Ensuite, et ceci découle directement de ce l’on vient de voir, elles peuvent être redéfinies.
Ainsi, si un élément définit une variable à une valeur A, mais que son enfant la redéfinit à
une valeur B, les enfants de l’enfant auront cette variable définie à B. Souvenez-vous, les
variables sont des propriétés qui cascadent. De même, il est tout à fait possible de redéfinir
une variable dans une Media Query (voir chapitre 8).
Enfin, parce qu’il s’agit bel et bien de propriétés CSS comme les autres, elles ne sont pas
restreintes à une feuille de styles comme peuvent l’être les variables d’un préprocesseur.
Mieux encore, elles sont toujours accessibles une fois la page rendue, c’est-à-dire qu’il est
tout à fait possible de les lire et de les altérer avec JavaScript.
Il est d’ailleurs bon de préciser que le contenu d’une variable CSS ne peut être utilisé que
comme valeur d’une propriété (y compris d’une variable). Elle n’est pas utilisable dans un
sélecteur, un nom de propriété ou quoi que ce soit d’autre.
La syntaxe
Définir une variable en CSS est très simple : il suffit de déclarer une propriété et de lui
assigner une valeur, comme vous le feriez normalement avec une propriété CSS tout à fait
standard.
La seule contrainte est que la propriété doit débuter par deux traits d’union (--) suivis de la
chaîne de caractères de votre choix faisant office de nom de variable. Il est recommandé
de choisir un nom qui peut être renseigné sans avoir à échapper des caractères. Par
exemple, --0 doit être échappé pour être référencé, ce qui risque de ne pas être très
pratique.
Concernant la valeur, elle peut accueillir absolument n’importe quoi du moment qu’elle ne
provoque pas d’erreur de syntaxe (), ], }, etc.). La seule valeur non autorisée est une
chaîne de caractères vide non encapsulée dans des guillemets, autrement dit le point-
virgule de fin d’expression directement juxtaposé aux deux points d’assignation (par
exemple, --prop:;).
Mais vous n’êtes pas tenu de définir des valeurs CSS valides, vous pouvez tout à fait
stocker ce que vous désirez, comme du JavaScript que vous pourrez par la suite récupérer
et exécuter.
Prenons un exemple simple pour démarrer et déclarons une variable appelée --my-custom-
property, ayant pour valeur la longueur 20px.
:root {
--my-custom-property: 20px;
}
Pour utiliser cette variable maintenant, il faut recourir à la fonction var(). Cette dernière ne
prend généralement qu’un seul argument : le nom de la propriété personnalisée (variable),
y compris les deux traits d’union.
.element {
margin-bottom: var(--my-custom-property);
}
.element {
background-color: hotpink;
background-color: var(--length);
}
Ici, la variable est considérée comme invalide car la propriété CSS background-color attend
une couleur, alors que la variable référencée est une longueur (20px). La propriété
background-color n’étant pas héritée, elle sera alors définie à sa valeur initiale, c’est-à-dire
transparent. De fait, la première définition background-color: hotpink sera bel et bien écrasée.
.element {
color: hotpink;
color: var(--length);
Quoi qu’il en soit, ces deux exemples montrent que l’on ne peut pas dresser de solutions
de repli aux variables invalides comme ceci.
Les valeurs de recours en cas d’invalidité
C’est pourquoi la fonction var() accepte en réalité deux arguments. Le premier, comme
nous l’avons vu, est le nom de la propriété personnalisée à référencer. Le second est une
valeur de repli, dans le cas où la valeur récupérée de la variable serait invalide ou
inexistante. Ainsi, vous pouvez définir un fallback en renseignant le second argument :
/**
* - la variable existe
* - la valeur est valide pour la propriété margin-bottom
*/
.element {
margin-bottom: var(--my-custom-property, 1em);
}
La valeur de repli correspond à tout ce qui se trouve après la première virgule, c’est-à-dire
que vous pouvez tout à fait utiliser des virgules dans le second argument, comme dans
l’exemple suivant :
/**
* Applique la valeur de --my-custom-font-family si :
* - la variable existe
* - la valeur est valide pour la propriété font-family
* sinon, applique "Arial", "Helvetica", sans-serif
*/
.element {
Dans des cas comme ceux-ci, les variables sont automatiquement définies à leur valeur
initiale, qui se trouve être une valeur vide, donc invalide.
Animations
Les propriétés personnalisées peuvent théoriquement être animées, mais dans la mesure où
le navigateur ne peut pas interpréter leur contenu, elles basculent d’un état à l’autre passée
la moitié (50 %) de l’animation/transition.
En revanche, une propriété personnalisée utilisée au sein d’une déclaration d’animation
(@keyframes, voir le chapitre 10 dédié aux animations et transitions) est normalement animée
quand utilisée via la fonction var().
La compatibilité des navigateurs pour les variables
natives
Tableau 7–1 Navigateurs desktop
Firefox supporte en réalité les variables natives depuis la version 29 via un flag. En
revanche, il s’agit là de l’ancienne version des spécifications. Seules les versions 31 et
plus récentes supportent la dernière version.
Le support est encore très léger, mais compte tenu du besoin crucial auquel répondent les
variables natives, il y a fort à parier que le support se décantera bientôt pour que l’on
puisse les utiliser sur des projets en production.
8
Styles conditionnels
On a parfois tendance à oublier qu’en CSS, les styles sont bien souvent conditionnels.
Écran ? Impression ? Dimensions ? Fonctionnalités supportées ? C’est d’autant plus vrai
que l’on doit prendre en compte un éventail toujours plus large de navigateurs et
d’appareils. Heureusement, @supports et @media permettent d’écrire des styles conditionnels.
Feature Queries
On a beau critiquer les problèmes que l’on peut rencontrer avec CSS, il s’agit globalement
d’un langage bien pensé. Par exemple, le fait que CSS ne plante pas (ou plutôt ne fasse pas
planter le parser) lorsqu’une propriété ou une valeur non reconnue est rencontrée est
vraiment une très bonne chose pour un langage interprété par le navigateur comme CSS.
C’est parce que CSS est prévu pour simplement omettre les erreurs plutôt que de les
relever que l’on peut mettre en place de l’amélioration progressive. Dupliquer une
propriété ne déclenche pas d’erreur (ce qui en soit pourrait être le cas), et assigner une
valeur erronée ne déclenche pas d’erreur non plus. À la place, les navigateurs omettent
simplement la ligne, n’empêchant pas le reste du fichier d’être utilisé.
Seulement parfois, dupliquer les règles dans le but de demander à chaque navigateur de
faire ce qu’il peut faire de mieux n’est pas suffisant. Autant cela fonctionne très bien pour
déclarer une couleur en rgba, avec un fallback en hexadécimal juste au-dessus, autant
quand il s’agit d’utiliser une technique de mise en page plutôt qu’une autre en fonction du
navigateur, ça devient plus compliqué.
Pour cela, il existe l’excellente bibliothèque JavaScript Modernizr, qui effectue des tests
de support de fonctionnalités au chargement de la page afin d’assigner des classes à
l’élément racine déterminant si le navigateur supporte telle ou telle propriété. Modernizr
vient également avec une API JavaScript simple permettant de tester les fonctionnalités
supportées par le navigateur directement depuis JavaScript, et pourquoi pas de
conditionner l’inclusion de scripts, par exemple.
Seulement Modernizr, c’est du JavaScript, pas du natif ! Et ça a beau être open source et
extrêmement bien développé, ce serait quand même mieux que ce soit le navigateur qui
indique lui même ce qu’il est capable de supporter ou non. Heureusement, les
spécifications CSS évoluent et le module de styles conditionnels intègre une nouvelle
section appelée Feature Queries.
TRADUCTION À propos de Media Queries et Feature Queries
Suite à un petit sondage effectué auprès de la communauté web francophone – merci à
tous ceux d’entre vous qui y ont participé –, il s’est avéré que majoritairement les
développeurs ne souhaitent pas traduire le terme Media Queries. Dans un souci de
cohérence, on ne traduira donc pas non plus Feature Queries.
Ce module se base sur la directive @supports, qui permet de tester la compréhension d’une
déclaration (paire propriété/valeur) auprès du navigateur. Comme la majorité des
directives CSS, elle est évaluée à true ou false, définissant ainsi si son contenu sera évalué
ou non. Le module implémente aussi une API JavaScript que l’on analysera plus loin.
De la notion de « support »
Avant même de rentrer dans la syntaxe et les cas d’utilisation de @supports, il faut définir ce
qui est entendu par « supporter une propriété ». Selon les spécifications, un parser CSS
(par exemple, celui d’un navigateur) peut affirmer supporter une déclaration (constituée
d’une propriété et d’une valeur) si celle-ci ne déclenche pas une erreur de parsing, c’est-à-
dire si elle est interprétée et a un effet sur l’élément auquel elle est appliquée.
Toute déclaration qui serait normalement omise à cause d’une erreur de parsing parce
qu’elle n’est pas implémentée ou que son implémentation n’est pas utilisable doit être
considérée comme « non supportée ».
Ceci étant dit, une fonctionnalité qui serait manuellement désactivée par l’utilisateur sera
toujours considérée comme supportée. Par exemple, si l’utilisateur active le mode de
contraste élevé qui a pour effet de redéfinir les couleurs, le processeur considérera toujours
color comme supportée même si les déclarations de la propriété color n’ont pas d’effet.
En revanche, une fonctionnalité expérimentale dont le support serait effectif via une
option du navigateur (flag) comme c’est de plus en plus le cas dans les navigateurs récents
aujourd’hui (voir page 9), est considérée supportée ou non supportée en fonction de l’état
actuel de l’option. Il est donc tout à fait possible – et c’est d’ailleurs souvent le premier
cas d’usage – de tester le support des nouvelles fonctionnalités CSS pour les implémenter
uniquement quand elles sont disponibles.
Syntaxe
La syntaxe en elle-même ressemble beaucoup à celle des Media Queries si ce n’est que
celle-ci est un peu plus permissive puisqu’elle intègre également la négation via le mot-clé
not, la conjonction via le mot-clé and et la disjonction via le mot-clé or. Ainsi, il est possible
d’implémenter des styles selon si un set de propriétés est supporté, ou bien une propriété
plutôt qu’une autre, ou encore si une propriété n’est pas supportée du tout.
Une directive de support se construit de la façon suivante :
/* Simple */
@supports (propriété: valeur) {
/**
* Déclarations dans le cas où
*/
}
/* Négation */
/* Conjonction */
@supports (propriété: valeur) and (autre-propriété: autre-valeur) {
/**
*/
}
/* Disjonction */
@supports (propriété: valeur) or (autre-propriété: autre-valeur) {
/**
*/
Comme vous pouvez le constater, la syntaxe est somme toute assez stricte puisqu’il faut
impérativement que les déclarations testées soient encapsulées par des parenthèses
(@supports property: value est donc invalide). De plus, les mots-clés doivent être séparés par
des espaces.
Par ailleurs, et pour des raisons d’interprétation des expressions impliquant une
combinaison de mots-clés and, or et not, il faut impérativement spécifier les différentes
composantes de l’expression entre parenthèses pour que celle-ci soit considérée valide.
/* Cette règle est invalide */
/* Déclarations */
}
/* Il faut l’écrire ainsi */
/* Ou comme ceci */
API JavaScript
Comme expliqué au début de ce chapitre, le module de support conditionnel intègre aussi
une API JavaScript permettant de tester le support de propriétés directement depuis
JavaScript mais sans passer par une bibliothèque tierce telle que Modernizr ; dans ce cas,
c’est bel et bien le navigateur directement que l’on interroge via JavaScript.
La syntaxe est on ne peut plus simple puisqu’il ne s’agit que d’une unique fonction
supports() .
interface CSS {
static boolean supports(DOMString property, DOMString value);
CSS.supports('(property: value)')
Par conséquent, lorsque vous désirez tester une expression faite de différentes
composantes, il vous faudra soit utiliser la seconde syntaxe qui le permet très facilement
en évaluant la chaîne comme le ferait le parser CSS pour une règle @supports standard, soit
faire les conditions directement en JavaScript :
/**
* En réalisant les opérations en JavaScript
*/
if (CSS.supports('propriété-1', 'valeur-1')
&& !CSS.supports('propriété-2', 'valeur-2')) {
/* … */
}
/**
/* … */
}
window.supportsCSS || false);
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
}
.header {
position: sticky;
}
Cas pratique : carrousel de Bootstrap 3
Si vous êtes un utilisateur du framework CSS/JavaScript Bootstrap, vous avez sûrement
déjà mis les mains sur son carrousel. Celui-ci possède deux versions : une qui utilise le
positionnement absolu et la propriété left, et une qui utilise les transformations CSS que
l’on verra dans l’avant-dernier chapitre de cet ouvrage.
Pour appliquer les transformations CSS (et annuler le positionnement avec l’offset left)
uniquement aux navigateurs qui les supportent, Bootstrap utilise actuellement une Media
Query propriétaire : @media (transform-3d). Cela va sans dire qu’il s’agit là d’une très
mauvaise idée.
CLARIFICATION À propos de @media (transform-3d)
C’est en lisant la source du framework Bootstrap un beau jour que j’ai découvert
l’existence de cette Media Query. Intrigué, j’ai mené ma petite enquête jusqu’à avoir
une confirmation de Paul Irish, ingénieur chez Google : il s’agit d’une Media Query
propriétaire inventée par Google pour tester le support des transformations 3D.
Au-delà du fait qu’il est toujours déconseillé d’utiliser des fonctionnalités non
standardisées, Paul a ajouté que celle-ci est potentiellement buggée, retournant parfois
des résultats « faux-positifs ». Autrement dit, vous ne devriez pas l’utiliser. Et
Bootstrap non plus.
Vous connaissez la solution. La directive @supports bien entendu ! Elle est faite pour ça et
convient parfaitement à un cas d’usage comme celui-ci.
@supports (transform: translate3d(0, 0, 0)) {
.carousel-inner > .item {
}
.carousel-inner > .item.prev,
}
}
Si vous ne comprenez pas toutes les déclarations, pas de panique ! Nous verrons tout ça
plus en détail dans le chapitre 9 dédié aux transformations.
Syntaxe
La syntaxe proposée par les spécifications de niveau 3 reprend en partie la syntaxe de
l’ancienne version dans le sens où il s’agit toujours de la directive @media suivie d’un des
mots-clés mentionnés précédemment, auxquels peuvent se greffer des expressions qui
testent des conditions particulières du media analysé.
Dans tous les cas, une Media Query est une expression qui est évaluée à true ou false. Si
elle est considérée comme valide, tous les styles définis dans celle-ci sont appliqués.
@media all and (min-width: 500px) {
/* Déclarations */
}
Il est possible d’omettre le mot-clé all puisqu’il constitue la valeur par défaut du média
analysé. Ainsi, l’exemple précédent peut être écrit de la façon suivante :
@media (min-width: 500px) {
/* Déclarations */
C’est d’ailleurs dans cette syntaxe qu’on le rencontre le plus, notamment dans le cadre de
Responsive Web Design.
Conjonction
Il est bien évidemment possible de cumuler diverses expressions à l’aide du mot-clé and.
@media all and (orientation: portrait) and (min-width: 500px) {
/* Déclarations */
Condition
Le mot-clé or n’existe pas. C’est une séparation par virgule qui fait office d’aiguilleur. Par
exemple, si vous désirez appliquer des styles uniquement quand le support a un mode
portrait ou que la largeur de l’écran fait au moins 500 pixels, vous pouvez l’écrire ainsi :
@media (orientation: portrait), (min-width: 500px) {
/* Déclarations */
Négation
En revanche, il existe le mot-clé not qui permet d’introduire une négation. Très
simplement, ce mot-clé permet d’appliquer les styles quand la condition n’est pas remplie.
@media not print {
/* Déclarations */
}
Restriction
Vous pouvez rencontrer le mot-clé only qui a pour objectif d’empêcher un navigateur ne
supportant que les spécifications de niveau 2 du module d’interpréter le bloc. C’est une
sorte de filtre pour distinguer les navigateurs capables de supporter le module de niveau 3
de ceux qui n’y parviennent pas.
@media only screen {
/* Déclarations */
}
L’orientation de l’appareil
Comme vous avez pu le constater dans les premiers exemples de ce chapitre, il est
possible de tester l’orientation d’un écran : portrait ou paysage. La définition est très
simple à déterminer : si la largeur du média est plus grande que sa hauteur, il s’agit d’une
orientation paysage. Sinon, il s’agit d’une orientation portrait.
Parce qu’il y a vraiment très peu d’écrans d’ordinateur pouvant être utilisés en mode
portrait, on peut se servir de cette expression pour déterminer si l’on est sur un media
mobile orienté en mode portrait. Ainsi, il est par exemple possible d’ajuster la navigation
pour qu’elle apparaisse sous la forme d’un menu déroulant, dans l’en-tête.
@media (orientation: portrait) {
/* Déclarations */
La résolution de l’écran
La résolution d’un écran, en tout cas dans les spécifications CSS, se mesure en dpi (dots
per inch), en dpcm (dots per centimeter) ou en dppx (dots per pixel, aussi connu sous le nom
de Device Pixel Ratio, alias DPR). Concrètement, elle détermine la taille d’un dot (point)
dans une représentation graphique en indiquant combien de ces points sont susceptibles de
rentrer dans 1in, 1cm ou 1px. Parce que CSS fixe un ratio de 1:96 entre 1in et 1px (ce qui
signifie que 1in == 96px), 1dppx est strictement égal à 96dpi (c’est d’ailleurs la résolution par
défaut des images affichées en CSS).
En somme, la résolution détermine le ratio entre les pixels réels et les pixels que l’on
qualifie de device-independant (DIPS). En testant cette propriété, il est donc possible de
savoir s’il on a affaire à un écran à haute densité, c’est-à-dire un écran dont les dimensions
en pixels physiques sont différentes (plus grandes) que les device-independant pixels.
C’est le cas par exemple d’un iPhone qui a une largeur physique de 640px mais une largeur
effective de 320px, d’où la résolution de 2dppx : 2 dots par pixel.
Le moteur de rendu WebKit a longuement supporté – et supporte toujours d’ailleurs – une
propriété équivalente appelée device-pixel-ratio, également sensible aux modulateurs minet
max- (préfixés en -webkit-min-device-pixel-ratio et -webkit-max-device-pixel-ratio). Cette propriété
attend un nombre sans unité contrairement à la propriété resolution standardisée. Chrome et
Safari ne supportent pas la version standardisée mais uniquement celle-ci, aussi vous
devrez utiliser les deux formules.
En l’occurrence, pour cibler les écrans à la densité supérieure ou égale à 1.5dppx, il vous
faudra utiliser une combinaison de ces trois Media Queries :
/**
* (-webkit-min-device-pixel-ratio: 1.5) pour Chrome et Safari
* (1.5 × 96)
* (min-resolution: 1.5dppx) pour Firefox 16+
*/
@media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 144dpi), (min-resolution: 1.5dppx) {
/* Écrans à haute densité */
Présence de JavaScript
Une des premières Media Queries à avoir été ajoutée à ce niveau 4 du module a été celle
permettant de détecter si JavaScript est supporté et activé dans le navigateur : scripting.
Elle accepte trois valeurs :
• none : si JavaScript n’est pas activé ou pas supporté ;
• initial-only : si JavaScript n’est activé qu’au chargement de la page (impression,
navigateurs proxy…) ;
• enabled : si JavaScript est activé.
Dans le cas où vous auriez une fonctionnalité dépendante de JavaScript, il est désormais
possible de mettre en place une solution de repli en CSS uniquement si JavaScript n’est
pas supporté ou est simplement désactivé.
CLARIFICATION Aucune distinction
Les spécifications stipulent clairement qu’il ne devrait y avoir aucune différence entre
le JavaScript désactivé et le JavaScript non supporté.
Cependant, l’absence de JavaScript n’est plus tellement un problème dans la mesure où
l’immense majorité des navigateurs actuels l’activent par défaut. Les problèmes rencontrés
sont plutôt :
• des scripts qui ne sont pas chargés faute de connectivité ;
• des scripts qui sont exécutés trop tard ou trop lentement ;
• des scripts qui plantent à cause d’un manque de support de certaines fonctionnalités
(Internet Explorer 8, par exemple).
Malheureusement, cette Media Query ne corrige aucun de ces soucis. Aussi, je dois
avouer que je suis quelque peu sceptique à son sujet. De plus, il faudrait attendre que tous
les navigateurs l’implémentent avant de s’en servir puisqu’il faudrait mettre en place une
solution de repli dans tous les cas.
C’est sans compter qu’en théorie, le JavaScript est supposé venir se greffer à une solution
de base fonctionnelle dans le but d’améliorer l’expérience de l’utilisateur. Dans le cas où
ce ne serait pas fait ainsi, j’ai peine à penser que quelques propriétés CSS déclarées dans
un bloc @media (scripting: none) puissent venir corriger le tir.
Luminosité ambiante
Une Media Query qui a beaucoup fait parler d’elle depuis que les nouvelles spécifications
ont vu le jour est celle permettant de détecter le niveau du luminosité ambiante : light-
level.
}
@media (light-level: dim) {
body {
background: black;
color: white;
}
}
L’idéal serait même de proposer ce mode à l’utilisateur plutôt que de le lui imposer. Pour
cela, on a besoin de JavaScript mais dans la mesure où nous ne faisons qu’améliorer
l’expérience de l’utilisateur, c’est tout à fait envisageable.
if (window.matchMedia('(light-level: dim)')) {
CLARIFICATION matchMedia
La fonction matchMedia n’est pas issue d’une bibliothèque JavaScript, il s’agit bien d’une
fonction native à JavaScript présente chez tous les navigateurs supportant les Media
Queries. Celle-ci permet de tester une Media Query directement depuis JavaScript.
Cette fonction accepte un unique argument : une requête sous la forme d’une chaîne
de caractères, exactement comme elle était écrite dans une feuille de styles classique.
Ensuite, cette requête est évaluée à true ou false, selon les conditions.
INTERNET EXPLORER 10 SUR WINDOWS 8 Équivalence propriétaire
Il est intéressant de remarquer que les versions d’Internet Explorer supérieures ou
égales à 10 sur Windows 8 supportent une Media Query propriétaire appelée high-
contrast (préfixée par -ms- bien entendu).
Celle-ci accepte les valeurs active, black-on-white, white-on-black ou none. Les trois
premières valeurs sont susceptibles d’être déclenchées lorsque l’application est lancée
en mode « contraste élevé ».
Système de pointage
Parmi les nouvelles Media Queries disponibles, il en est une qui permet de déterminer le
niveau de précision du système de pointage. Rappelons que le système de pointage est
l’appareil utilisé pour déplacer le curseur à l’écran, très communément une souris ou un
trackpad.
Cette requête appelée pointer en tout simplicité, offre là encore trois valeurs possibles :
• none : l’appareil ne dispose d’aucun système de pointage (TV, imprimante…) ;
• coarse : l’appareil dispose d’un système de pointage à la précision limitée (écran tactile,
Nintendo Wii, Kinect, Google Glass…) ;
• fine : l’appareil dispose d’un système de pointage précis (souris, stylet, trackpad…).
Ainsi, il serait possible de revoir certains éléments d’interface selon si l’utilisateur est
capable d’effectuer des actions précises ou non. Par exemple, on peut imaginer agrandir la
taille des boutons quand l’utilisateur dispose d’un système de pointage imprécis (coarse),
par exemple son smartphone ou sa tablette (auquel cas on peut considérer le doigt comme
le système de pointage).
Cependant, on se heurte rapidement à un problème : nous disposons déjà d’un moyen de
faciliter nos actions lorsque nous ne sommes pas équipés d’un curseur précis, et il s’agit
du zoom.
Pensez-y, lorsque vous naviguez sur votre téléphone et que vous désirez cliquer sur un lien
ridiculement petit, vous zoomez pour pouvoir appuyer dessus. Lorsque vous avez zoomé,
considérez-vous toujours que vous êtes en possession d’un système imprécis ?
Techniquement, oui. Votre système n’a pas évolué.
En revanche, les éventuelles adaptations d’interface qui ont été effectuées par rapport à
votre système de pointage n’ont plus lieu d’être dans ce cas.
Si l’on ne fait que modifier la taille des boutons, on ne risque pas grand-chose, je vous
l’accorde. En revanche, si l’on se met à modifier l’interface dans son entièreté, les choses
peuvent devenir complexes. Prenons un exemple pas à pas où un site/une application
adapterait l’interface en fonction du système de pointage pour que :
• l’interface soit complète et précise lorsque pointer vaut fine ;
• l’interface soit plus rudimentaire et que le scroll soit horizontal quand pointer vaut coarse.
Lorsque je visite le site sur ma tablette tactile, je bénéficie d’une interface adaptée à ma
venue avec des boutons d’action larges et un scroll horizontal qui n’est pas sans rappeler
Windows 8. Tout va bien.
Je me rends ensuite sur mon ordinateur de bureau, où l’interface est adaptée là encore :
davantage de boutons visibles à l’écran, parfois plus petits certes, mais ma souris me
permet des clics précis. Pas de problème non plus.
J’achète un nouvel écran qui s’avère être tactile comme ça se fait beaucoup de nos jours
pour mon ordinateur. Me voilà avec l’interface grossière et le scroll horizontal sur mon
ordinateur !
Et cela parce que dans le cas où plusieurs systèmes de pointage seraient détectés, selon les
spécifications, le navigateur se doit de retourner la valeur la « moins capable ». Par
exemple, coarse, si coarse et fine sont tous deux détectés.
Capacité de survol
Dans la même lignée que la Media Query que l’on vient de voir, il sera bientôt possible de
détecter si l’utilisateur est susceptible de survoler des éléments du document. Là encore,
trois valeurs sont proposées :
• none : il est impossible pour l’utilisateur de survoler des éléments ;
• on-demand : il est complexe pour l’utilisateur de survoler des éléments (on pensera aux
smartphones équipés de Air Gesture) ;
• hover : l’utilisateur est en mesure de survoler les éléments sans effort.
Fait curieux, les spécifications demandent explicitement aux auteurs de ne pas supposer
qu’un contexte retournant none ne supporte jamais :hover.
En effet, dans le cas où un appareil supporterait deux systèmes de pointage, par exemple
un appareil tactile pouvant éventuellement être contrôlé par souris (Windows Surface,
Chromebook Pixel, entre autres), la valeur retournée par cette Media Query serait none car
l’appareil initial ne permet pas le survol (et les navigateurs se doivent de retourner la
valeur la moins capable s’il y a plusieurs valeurs). Cependant, la souris lui permettrait de
déclencher des actions de survol.
Cet exemple, pourtant simple, présenté dans les spécifications montre un fait tout aussi
simple : l’intérêt de cette Media Query est somme toute très limité. D’autant qu’il est
d’ores et déjà tout à fait possible d’utiliser la pseudo-classe :hover comme amélioration
progressive.
}
a:hover, a:focus {
text-decoration: underline;
}
@media (update-frequency: none) {
a {
text-decoration: none;
}
}
aurait été suffisante, avec l’avantage d’être supportée partout. Quoi qu’il en
@media print
soit, je suis peut-être médisant. Après tout, qui sait ? Peut-être que l’avenir nous réserve
du papier qui peut être mis à jour, avec une vitesse réduite dans un premier temps, ce qui
nous offrirait un cas d’usage pour @media (update-frequency: slow) !
Par la suite, cet alias peut être utilisé comme s’il s’agissait d’une requête classique :
@media (--small) {
/* Styles soumis à (min-width: 400px) */
Fait intéressant, parce que cet alias est considéré comme une Media Query à part entière, il
peut être utilisé au sein d’une liste de requêtes. Par exemple :
@media (--small) and (max-width: 800px) {
*/
*/
}
@media (min-width: 1200px) {
/**
*/
Bien que l’on teste souvent la largeur de l’écran, il peut parfois être utile de conditionner
la mise en page en fonction de la hauteur de l’écran. Un cas d’usage intéressant serait de
n’implémenter un en-tête fixe que lorsque l’écran fait une certaine hauteur, sous peine de
rendre le contenu difficilement accessible.
.header {
height: 200px;
}
/**
* Uniquement les écrans ayant une hauteur
.header {
position: fixed;
top: 0;
}
}
transform: function(value);
Plusieurs fonctions peuvent être enchaînées, séparées par des espaces. Le résultat d’une
transformation est passé à la suivante, et ainsi de suite jusqu’à ce que toutes les
transformations soient appliquées.
/* Chaînage des transformations CSS */
Il est important de comprendre que les transformations ne sont que visuelles, c’est-à-dire
qu’elles sont effectuées sur un « calque » à part. De fait, une transformation n’a
absolument aucun impact sur le reste de l’environnement, encore moins sur le modèle de
boîte.
Par exemple, un élément rectangulaire transposé occupera toujours son emplacement
initial, même si visuellement il aura l’air d’avoir été déplacé, un peu à la manière du
positionnement relatif. De même, un élément mis à l’échelle de sorte qu’il soit deux fois
plus grand qu’initialement ne poussera pas les éléments autour de lui. Là encore, ce n’est
que visuel.
REMARQUE Transformations et éléments inline
Une restriction technique vient s’imposer à l’utilisation des transformations : il ne faut
pas que l’élément transformé soit inline. Tout autre type de display, y compris inline-
block, est nécessaire.
À quoi servent les transformations CSS ?
Ne vous êtes-vous jamais posé cette question ? Elle est tout à fait légitime, rassurez-vous.
Nous réalisons des interfaces pour le Web depuis des années maintenant, et nous n’avons
jamais ressenti le besoin d’avoir recours à de la 3D.
Pourtant, les solutions sont là depuis longtemps déjà : Flash, three.js, WebGL… Et depuis
quelques temps, CSS. Et pour cause, les transformations, et plus généralement la
manipulation des éléments dans un environnement en 2 et 3 dimensions n’a pas qu’une
dimension (sans jeu de mot) esthétique. C’est aussi très pratique pour inculquer des
repères.
Un exemple assez classique d’utilisation des transformations CSS, notamment les
transformations 3D, est le card flip, littéralement « retourné de carte ». Pensez-y : vous
êtes face à un plan qui affiche un certain nombre d’informations, par exemple une liste de
tâches (la fameuse to do list). Quand vous sélectionnez un élément de cette liste, le plan
pivote sur son axe central à la manière d’une carte de jeu pour se retourner et afficher une
nouvelle interface : les informations spécifiques à l’action effectuée.
Dans ce cas de figure, l’utilisation d’une rotation est non seulement agréable parce qu’elle
évite le côté abrupte d’un changement d’état, mais surtout elle donne du sens à l’action
effectuée : l’utilisateur n’est pas « téléporté » dans un autre endroit de l’application.
En somme, les transformations CSS, notamment lorsqu’elles sont couplées avec des
animations (que l’on étudiera dans le prochain chapitre), permettent de donner du sens aux
interfaces en se rapprochant de véritables textures et matériaux.
Les transformations 2D
On peut discerner deux grandes familles de transformations : celles qui s’appliquent sur un
plan unique, et celles qui ont lieu dans un environnement en trois dimensions. Bien
évidemment, ces dernières sont plus complexes à appréhender, et c’est pourquoi nous les
étudierons dans un second temps.
Je vous propose de commencer tout de suite avec la découverte des fonctions de
transformation de base (ou la redécouverte pour ceux d’entre vous qui sont déjà à l’aise
avec le sujet).
Rotation
La fonction de rotation, rotate() permet de pivoter un élément sur lui-même. Elle accepte
un unique argument, un angle, qui déclenchera une rotation dans le sens des aiguilles
d’une montre de l’élément.
Au risque de vous surprendre, sachez que l’angle (et tous les angles en CSS, y compris
ceux pour les dégradés) peut être spécifié dans quatre unités différentes :
• en degrés (deg), entre 0 et 360 ;
• en grades (grad), entre 0 et 400 ;
• en radians (rad), un cercle étant égal à 2π radians ;
• en tours (turn), 1 tour valant 360° bien évidemment.
La règle de calcul est la suivante : 1turn == 360deg == 400grad ~= 6.283rad. Notons que la
rotation se fait dans le sens horaire et non trigonométrique. Toutefois, il est possible
d’utiliser des valeurs négatives pour tourner dans le sens inverse des aiguilles d’une
montre.
Quoi qu’il en soit, les angles sont souvent définis en degrés ou en tours, principalement
parce que ce sont là les deux unités les plus intuitives pour mesurer la valeur d’un angle.
Pivoter un élément peut sembler être un gadget mais en réalité c’est là quelque chose que
l’on trouve dans de très nombreux projets. Par exemple, pour réaliser une petite pastille à
la manière d’un sticker, ou pour donner un effet photo ou Post-it à une note. Jusqu’alors, il
fallait utiliser une image pour cela mais aujourd’hui c’est on ne peut plus simple :
.element {
transform: rotate(15deg);
}
Figure 9–1
Application d’une légère rotation à un élément (3 degrés)
De même pour les recherches « tilt » et « askew », où la page est très légèrement
inclinée via transform: rotate(1deg).
Figure 9–3 Recherche Google « akew »
Figure 9–4
Un step wizard sans image, pour lequel les flèches sont réalisées avec des pseudo-éléments pivotés.
Pour indiquer que les étapes sont successives, on fait en sorte de les lier par des flèches, un
peu à la manière des panneaux. Ces flèches indiquent la direction et le cheminement, au-
delà de donner des indications sur le parcours effectué et le parcours restant avant
aboutissement de l’action.
Le HTML dont nous avons besoin est très sémantique : une liste ordonnée, ni plus ni
moins.
<ol class="step-wizard">
<li>Panier</li>
<li>Authentification</li>
<li class="is-selected">Livraison</li>
<li>Paiement</li>
</ol>
Commençons par appliquer quelques lignes de CSS pour habiller tout cela :
/**
* Step wizard
*/
.step-wizard {
list-style: none;
font-size: 0; /* 1 */
padding: 0;
color: #555;
}
/**
* Élément de liste
* 1. Rétablissement de la taille de police par défaut
li {
display: inline-block;
font-size: initial; /* 1 */
position: relative; /* 2 */
line-height: 1; /* 3 */
background: #EFEFEF;
padding: .5em 2em;
border: 1px solid rgba(0, 0, 0, .25);
}
li + li {
border-left: none;
}
/**
* Étape en cours
*/
li.is-selected {
background: deepskyblue;
font-weight: bold;
color: white;
}
Il ne nous reste plus qu’à mettre en place nos pseudo-éléments qui font office de flèches.
/**
* Flèches
*/
li::after {
content: '';
width: 1.41421356em; /* 1 */
height: 1.41421356em; /* 1 */
transform: rotate(45deg); /* 2 */
position: absolute; /* 3 */
left: 100%; /* 3 */
top: .25em; /* 3 */
margin-left: -.7em; /* 3 */
border: inherit;
border-left: none;
border-bottom: none;
z-index: 2;
background: inherit;
http://bit.ly/step-wizard-css
/* Internet Explorer 8 */
-ms-filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);
/* Internet Explorer 7 */
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);
@return $value
/ nth($factors, index($units, unit($value)))
Par exemple :
.element {
Translation
La propriété transform accepte comme valeur la fonction translate(), qui peut prendre un ou
deux arguments. Cette fonction permet de transposer un élément sur son calque visuel. De
fait, elle ne déplace pas réellement l’élément mais simplement sa zone de rendu. L’espace
initial occupé par l’élément reste donc vide et ne peut pas être comblé.
La fonction translate() attend des longueurs (positives ou négatives) comme arguments. Le
premier argument (et possiblement le seul) agit pour l’axe X, et le second pour l’axe Y.
/**
* Déplace l’élément de
.element {
transform: translate(100px);
}
/**
* Déplace l’élément de
* 50 px vers la gauche,
* 75 px vers le haut
*/
.element {
transform: translate(-50px, -75px);
* 42 px vers la droite
*/
.element {
transform: translateX(42px);
left: 50%;
transform: translate(-50%, -50%);
}
Figure 9–6
Centrage absolu dans des dimensions inconnues grâce à transform: translate()
Mise à l’échelle
De la même façon qu’il est possible de pivoter et de transposer un élément, il est
également possible de le mettre à l’échelle, c’est-à-dire de le grossir ou de le rapetisser. Là
encore, ce n’est jamais que l’apparence visuelle qui est modifiée, et non pas les
dimensions de l’élément.
En d’autres termes, modifier l’échelle d’un bloc via la fonction scale() n’aura aucune
incidence sur son environnement.
La fonction scale accepte un ou deux arguments, sans unité. Les arguments passés à la
fonction sont des multiplicateurs, c’est-à-dire que c’est un coefficient de mise à l’échelle.
Par exemple, scale(2) signifie concrètement « deux fois plus gros ». Si un seul argument
est spécifié, la mise à l’échelle sera effectuée sur les deux axes. En revanche, si deux
arguments sont spécifiés, le premier effectuera une mise à l’échelle sur l’axe X, et le
second sur l’axe Y.
Cette règle aura pour effet de grossir un élément de 10 % sur les deux axes lorsqu’il est
survolé.
Figure 9–7
Au survol de l’élément, il grossit légèrement.
Comme pour la fonction de translation, vous avez également accès aux fonctions scaleX()
et scaleY() pour ne mettre à l’échelle un élément que sur un axe bien précis.
Inclinaison
Bien que l’on puisse trouver sans mal un intérêt à des fonctions de rotation, de
transposition et de mise à l’échelle, je dois avouer que j’ai davantage de mal à trouver un
cas d’usage pratique pour une fonction d’inclinaison d’un élément. C’est pourtant ce que
font les fonctions skewX() et skewY() ; elles permettent de faire varier l’angle de croisement
des axes X et Y.
Contrairement aux autres fonctions de transformation, il faut impérativement dissocier les
deux axes ; la fonction skew() n’existe plus. Elle a existé dans les premières ébauches de
spécifications, mais elle a été par la suite remplacée par skewX() et skewY().
Les deux fonctions n’acceptent qu’un seul argument : un angle, généralement exprimé en
degrés, mais il est également possible de l’exprimer en radians (rad), grades (grad) ou
même tours (turn) comme nous l’avons vu dans la section traitant de la fonction rotate().
Lorsque vous appliquez une inclinaison via skewX(), l’axe horizontal ne bouge pas mais
l’axe vertical pivote (déformant ainsi l’élément). À l’inverse, quand c’est une
transformation en Y, l’axe vertical reste statique mais l’axe horizontal pivote.
RESSOURCE Un outil pour skew()
Ana Tudor, spécialiste en géométrie et amatrice de CSS à ses heures perdues, a
développé une petite démo interactive pour comprendre le fonctionnement de la
transformation skew(). Évidemment, je vous la recommande fortement.
Figure 9–8 La démo interactive d’Ana Tudor pour apprendre à se servir de skew()
http://bit.ly/skew-tool
Figure 9–9
Un menu incliné grâce à transform: skew()
<li><a href="#">Robot</a></li>
<li><a href="#">Rainbow</a></li>
<li><a href="#">Unicorn</a></li>
</ul>
list-style:none;
}
/**
* Éléments de liste
.nav li {
display: inline-block;
transform: skewX(20deg); /* 1 */
background: #ddd;
}
.nav li:hover {
background: deepskyblue;
}
/**
* Liens
* 1. Inclinaison inverse des liens
*/
.nav a {
display: block;
text-decoration: none;
padding: .5em;
color: black;
transform: skewX(-20deg);/* 1 */
}
li:hover a {
color: white;
}
Vous l’avez remarqué, nous appliquons une transformation aux éléments de liste (li), mais
également une transformation aux liens. En effet, cette dernière a pour but d’annuler celle
du parent afin que le texte soit toujours lisible ! Sans celle-ci, le texte du lien serait lui
aussi incliné sur le côté, chose que nous ne désirons pas.
matrix
Nous avons vu les quatre transformations disponibles en CSS : rotation, translation, mise à
l’échelle et inclinaison.
Toutefois, une cinquième fonction a été élaborée en secret visant à remplacer les quatre
autres. Une fonction pour se moquer. Une fonction pour les amener toutes, et dans les
mathématiques les lier — en référence au Seigneur des anneaux de J. R. R. Tolkien. Il
s’agit de la fonction matrix().
Celle-ci a pour but de combiner toutes les transformations en une seule. On peut voir cette
fonction comme un raccourci pour une chaîne de transformations, mais cela impliquerait
qu’elle soit destinée à être renseignée manuellement, ce qui n’est fort heureusement pas le
cas quand on voit une valeur comme celle-ci :
transform: matrix(0.7071067811865475, 0.7071067811865476, - 0.7071067811865476, 0.7071067811865475,
-0.7071067811865497, 34.648232278140824)
transform: rotate(25deg);
.element {
/**
*/
.element::after {
content: '';
width: 6px; /* 1 */
height: 6px; /* 1 */
margin: -3px; /* 2 */
position: absolute;
left: 25%; /* 3 */
top: 66%; /* 4 */
border-radius: 50%;
background: black;
}
Figure 9–10
Le petit cercle noir est le pseudo-élément, représentant l’origine de la transformation.
L’ordre des transformations
Je l’ai mentionné au début de ce chapitre, l’ordre d’application des transformations n’est
pas anodin. Parce que le résultat d’une transformation est passé à la transformation
suivante, permuter l’ordre des transformations peut produire un résultat tout à fait
différent.
Par exemple, les deux déclarations suivantes n’auront pas le même effet :
/* Cette transformation… */
.element {
}
/* … et cette transformation… */
.element {
transform: translate(0, 50%) scale(1.25);
}
/* … produisent des résultats différents ! */
Figure 9–11
Mêmes transformations et ordre différent = résultat différent
Pour que les deux transformations soient identiques, on pourrait modifier notre code
comme suit :
.element {
/**
.element {
Cette fois, les deux transformations sont équivalentes. Pensez toujours à l’ordre dans
lequel vous appliquez vos fonctions !
Figure 9–12
Une fois les transformations adaptées, l’effet est corrigé !
Les transformations 3D
Jusqu’à présent, nous avons essentiellement parlé des transformations 2D, c’est-à-dire les
manipulations d’un élément sur un plan unique. Cependant, vous l’aurez compris, le
module destiné aux transformations CSS propose également des transformations 3D, à
savoir la manipulation des éléments non seulement sur les axes X et Y, mais également Z.
Il est impossible de parler de profondeur de champ sans parler de perspective, aussi les
transformations 3D font entrer deux nouvelles propriétés en jeu : perspective et perspective-
origin, ainsi qu’une nouvelle fonction : perspective(). Nous allons voir la différence entre la
fonction et la propriété dans un instant.
Il est important de comprendre que les transformations 3D n’auront pas l’effet escompté si
l’élément auquel elles sont appliquées n’évolue pas dans un environnement en trois
dimensions. Cet environnement est généré en assignant de la perspective, aussi je vous
propose de démarrer par cela.
RESSOURCE Un FPS en CSS
À ce jour, la démonstration la plus impressionnante des effets 3D réalisés en CSS est
le FPS (First Person Shooter) de Keith Clark. À voir absolument.
http://bit.ly/css-fps
Les deux formats activent un espace 3D avec une différence cependant. La notation sous
forme de fonction est très pratique lorsque vous souhaitez appliquer des transformations
3D à
un élément unique. Mais quand elle est utilisée sur plusieurs éléments, on remarque assez
rapidement le problème : chaque élément va avoir son propre point de fuite.
Figure 9–15
À gauche, chaque élément a son propre point de fuite et à droite, l’environnement est partagé entre tous les éléments.
Pour éviter cela, on peut alors utiliser la propriété perspective sur le parent de ces éléments
pour décrire un environnement en 3D unique, que tous les enfants partagent.
La valeur de perspective détermine le degré de perspective appliqué à l’élément. Plus la
longueur est courte (proche de 0), plus l’effet est prononcé. Au contraire, plus la longueur
se fait grande, moins la perspective est visible.
/* Effet léger */
.element {
perspective: 1000px;
}
/* Effet prononcé */
.element {
perspective: 250px;
Figure 9–16
Plus la valeur de perspective est proche de 0, plus l’effet est prononcé.
Origine de perspective
La propriété perspective-origin détermine le point de fuite de l’élément. Elle doit être
renseignée en conjonction de la propriété perspective sur l’élément parent ou de la fonction
perspective() sur l’élément transformé.
perspective: 1000px;
perspective-origin: top right;
En résumé
Nous avons à notre disposition les fonctions suivantes pour les transformations sur les
trois axes :
• rotateX(), rotateY(), rotateZ() et rotate3d() ;
• translateX(), translateY(), translateZ() et translate3d() ;
• scaleX(), scaleY(), scaleZ() et scale3d() ;
• skewX() et skewY() ;
• la fonction perspective() ;
• la propriété perspective ;
• perspective-origin ;
• matrix() et matrix3d().
Voilà beaucoup de fonctions ! Fort heureusement, la majorité d’entre elles fonctionne de la
même manière que les fonctions 2D que nous avons vu tout au long de ce chapitre, si ce
n’est qu’elles acceptent davantage d’arguments.
Il reste deux propriétés intimement liées aux transformations 3D que nous n’avons pas
encore abordées et dont nous allons avoir besoin pour notre exemple à venir, aussi je vous
propose de les étudier dès à présent.
Contexte et transform-style
La propriété transform-style détermine si les enfants d’un élément sont positionnés dans un
espace en trois dimensions (preserve-3d) ou s’ils sont aplatis dans le plan de l’élément (flat,
la valeur par défaut). Dans ce dernier cas, les enfants n’existeront pas en tant que tels dans
l’environnement 3D de l’élément.
Je dois admettre qu’il s’agit là d’une notion un peu compliquée à appréhender, toutefois
elle est capitale. Pensez à transform-style: preserve-3d comme un moyen de passer le
contexte de perspective d’un élément à ses enfants. Heureusement, notre exemple devrait
faciliter la compréhension de tout ceci.
COMPATIBILITÉ DES NAVIGATEURS transform-style
Le support de transform-style est strictement le même que le support des
transformations 3D énoncé précédemment si ce n’est qu’Internet Explorer ne
reconnaît pas cette propriété.
<div class="card">
<div class="side recto">Recto</div>
</div>
</section>
L’élément .container sert d’environnement 3D, pour l’objet .card, qui contient quant à lui
deux éléments représentant les deux faces de la carte : recto et verso. Lors d’une
utilisation avancée des transformations 3D comme nous le faisons ici, il est généralement
recommandé de procéder comme suit :
• un conteneur qui initialise un environnement en trois dimensions grâce à perspective ;
• l’objet composé qui va essentiellement servir de conteneur pour les sous-composants qui
seront quant à eux transformés.
C’est une bonne pratique que de dissocier l’environnement de l’élément en lui-même.
Maintenant que notre structure est fin prête, nous pouvons commencer à appliquer des
styles. Commençons tout naturellement avec le conteneur d’environnement, auquel nous
définissons une dimension, et bien sûr une perspective.
/**
* Définition d’un environnement en trois dimensions
* 1. Dimensions
* 2. Contexte de positionnement pour les enfants
* 3. Établissement de l’espace 3D
*/
.container {
height: 420px; /* 1 */
width: 300px; /* 1 */
position: relative; /* 2 */
perspective: 750px; /* 3 */
Notre conteneur constitue les limites de notre environnement, aussi tous les enfants, aussi
bien .card que les éléments .side, emprunteront les dimensions de celui-ci. De plus, pour
pouvoir placer les éléments les uns au-dessus des autres, nous allons réinitialiser leur
système de positionnement grâce à position : absolute.
/* Réinitialisation du système de positionnement des enfants */
.card, .side {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
Nous allons dès maintenant faire bon usage de notre propriété transform-style. En effet, la
perspective du conteneur (.container) ne descend que sur ses enfants directs (c’est-à-dire
.card). Pour que les enfants de .card fassent également partie de cet espace 3D, il faut le
leur intimer grâce à transform-style: preserve-3d, sur leur parent (.card).
N’oublions pas non plus une transition (que nous étudierons plus en détail dans le dernier
chapitre de ce livre) afin que l’effet flip se remarque.
.card {
transform-style: preserve-3d;
transition: 1s;
}
Nous avons quasiment terminé ! Il ne nous reste qu’à cacher les faces lorsqu’elles sont
retournées grâce à la propriété backface-visibility. Nous pouvons également appliquer
d’autres styles à nos faces, si nous le souhaitons bien évidemment.
/* Afin que les faces retournées ne soient pas visibles */
.side {
backface-visibility: hidden;
Enfin, il faut pouvoir déclencher l’animation d’une manière ou d’une autre. Une solution
simple consisterait à appliquer une classe à l’élément .card pour le faire pivoter d’un
demitour sur son axe. Cette classe peut être ajoutée et retirée via JavaScript.
.card.flipped {
transform: rotateY(0.5turn);
C’est terminé ! L’effet est complètement fonctionnel et peut être utilisé assez simplement.
Parmi les usages intéressants que j’ai pu noter pour le card flip, on trouve :
• un changement de vue dans une application, typiquement passer d’un mode liste à une
vue de détails ;
• la confirmation de prise en compte d’un envoi de formulaire ;
• les différents états d’un bouton si le contexte s’y prête et que l’effet est suffisamment
rapide pour ne pas être trop tape-à-l’œil.
Figure 9-17
L’effet card flip en action
Si nous désirons avoir un effet un peu plus subtil justement, c’est tout à fait possible en ne
changeant que quelques lignes. Par exemple, à l’heure actuelle la carte tourne sur son axe
central sans le moindre déplacement. C’est un mouvement pur, à tel point qu’il attire
l’attention parce qu’il est difficilement reproductible à la main.
On pourrait faire en sorte que la carte pivote sur elle-même depuis un côté, mouvement
qui serait assez semblable à celui que l’on ferait pour retourner une carte sur une table.
Nous le verrons plus en détail dans le chapitre suivant, mais les animations ont pour but de
donner des repères, au-delà d’être esthétiques.
Revenons à notre exemple. Nous pouvons commencer par modifier la propriété transform-
origin de notre élément .card, qui je le rappelle, a pour valeur par défaut 50% 50%, soit le
centre de l’élément.
.card {
transform-origin: center right;
/* Autres styles … */
}
Toutefois, cette déclaration fait que la carte ne se trouve plus à la même position une fois
retournée. En effet, parce qu’elle n’a pas pivoté sur son axe central, elle est désormais
placée directement à droite de sa position initiale. Il faut donc annuler ce mouvement via
une transformation additionnelle : une translation.
.flipped {
}
Figure 9-18
Un effet un peu plus subtil pour le card flip
Les animations font partie intégrante d’une interface de qualité. Leur but n’est pas
simplement esthétique. En effet, elles apportent énormément d’informations sur
l’environnement et aident l’utilisateur à s’y retrouver.
Il y a toujours eu des animations dans les créations web. Plus ou moins kitch, certes, mais
il y en a toujours eu. On utilisait d’abord Flash pour cela, jusqu’à ce que l’on se rende
compte des limites de l’outil et des problèmes qu’il pouvait engendrer. Ce fut alors au tour
de JavaScript de briller, notamment grâce à l’API .animate() de jQuery qui a rendu
l’animation très accessible, y compris aux intégrateurs n’ayant que de maigres
connaissances en JavaScript.
Malheureusement, les animations en JavaScript ne sont pas sans défaut non plus. Parce
qu’elles sont gérées par le moteur JavaScript du navigateur et non par le navigateur lui-
même (pour rester simple), elles peuvent s’avérer relativement lentes et coûteuses. C’est
d’autant plus vrai sur de vieux navigateurs et/ou des processeurs peu puissants.
C’est pourquoi un système d’animation intégralement géré par CSS a vu le jour. En
réalité, il y a deux spécifications différentes qui traitent de deux fonctionnalités différentes
mais intimement liées : les transitions et les animations. C’est ce que je vous propose de
voir dans ce chapitre, mais pas sans une petite introduction.
À quoi servent les animations ?
Si comme moi, vous avez longtemps cru que les animations ne sont que de la poudre aux
yeux, détrompez-vous ! J’ai eu la chance de pouvoir discuter avec Rachel Nabors,
illustratrice et web designer de renom qui a su me faire comprendre l’importance que
peuvent avoir les animations dans les interfaces web.
Elle a su me prouver que les animations ont un véritable rôle à jouer dans les interfaces
avec un exemple très simple : un menu déroulant. Considérez un menu déroulant tout ce
qu’il y a de classique : lorsque vous cliquez dessus pour choisir une option, il s’ouvre.
Figure 10–1
À gauche le menu est fermé, au milieu son opacité change pour apparaître, à droite il est ouvert.
S’il n’est pas animé, c’est-à-dire s’il passe d’un état fermé à un état ouvert de manière
instantanée, le cerveau va devoir procéder à l’animation lui-même. Il s’agit là d’un
comportement mécanique du cerveau humain : il visualise les étapes qui permettent de
passer d’un état A à un état B parce que dans le monde réel (comprendre hors écran), rien
ne passe d’un état initial à un état final sans passer par des transitions plus ou moins
courtes (un ballon qui roule, un coucher de soleil, un éclair qui s’abat…).
Maintenant, si le menu est animé de sorte qu’il se déroule au moment du clic ou qu’il
apparaisse de manière progressive, la tâche n’est plus réalisée par le cerveau, qui peut
alors se concentrer sur l’essentiel : le choix de l’élément et la poursuite de son action.
Ce simple exemple démontre bien que les animations ne sont pas uniquement esthétiques.
Elles jouent un rôle primordial dans l’expérience de l’utilisateur. Des animations subtiles
permettent de garder l’utilisateur focalisé sur son action principale.
C’est d’autant plus vrai lorsque l’on parle d’interfaces mobiles. Vous avez sûrement déjà
rencontré une application ou un site mobile dans lequel le menu est initialement masqué,
pour n’être révélé que lorsque vous touchez l’icône « hamburger » (les trois segments
horizontaux placés les uns au-dessus des autres, design pattern qui s’instaure petit à petit
comme le symbole universel du menu). À cet instant, vous avez peut-être remarqué que le
menu n’apparaît pas d’un coup d’un seul à l’écran : il glisse rapidement depuis le bord
gauche pour venir chevaucher la zone de contenu.
Pour terminer, on mentionnera la façon dont Google perçoit les animations au cœur des
interfaces (notamment celles d’Android L) :
« Percevoir la forme d’un objet nous permet de comprendre comment le manipuler.
Observer le mouvement d’un objet nous informe s’il est léger ou lourd, flexible ou rigide,
petit ou grand. Le mouvement, dans le monde du design matériel n’est pas seulement
agréable, il permet surtout de définir l’espace, le fonctionnement et l’objectif du
système. »
RESSOURCE Google Design
L’approche de Google au sujet de l’animation est très intéressante, notamment
lorsqu’elle est mise en œuvre sur les applications mobiles (dans leur cas, l’interface
d’Android Lollipop). Je ne peux que vous suggérer de lire les études réalisées par
Google à ce sujet.
http://bit.ly/google-design-animation
}
/* … ou encore */
.element {
N’importe laquelle de ces règles aura pour effet de basculer l’élément sur son propre
calque, pouvant ainsi être traité par la carte graphique et non par le processeur, améliorant
grandement les performances notamment en cas d’animation.
La seconde solution est plus récente mais aussi beaucoup plus propre. En effet, le W3C a
récemment proposé des spécifications pour une propriété appelée will-change. Celle-ci
permet de faire ce pourquoi on utilise habituellement une transformation nulle : prévenir le
navigateur que l’élément est susceptible de voir certaines de ses propriétés modifiées dans
un futur plus ou moins proche afin qu’il soit en mesure de procéder à d’éventuelles
optimisations.
/* La promotion de calque avec will-change */
.element {
.element {
will-change: all;
Attention ! Pour une solution comme pour l’autre, il peut être tentant de promouvoir tous
les éléments du document sur leur propre calque en utilisant le sélecteur universel *. Non
seulement ça ne va probablement pas améliorer les performances autant qu’on le
souhaiterait, mais surtout ça risque de faire plus de mal que de bien.
En effet, si chaque élément se retrouve promu sur son propre calque, tout ou presque est
délégué au GPU qui est susceptible de ne pas tenir la charge. À cet instant, on peut
constater des ralentissements (notez l’ironie) voire un plantage de la page. En résumé, il
faut utiliser l’accélération matérielle avec intelligence et parcimonie.
À noter que les propriétés opacity et transform sont naturellement optimisées par le
navigateur, aussi il est inutile de déclencher l’accélération matérielle manuellement (en
revanche, il est toujours judicieux d’utiliser will-change).
Bien évidemment, parce que cela s’appelle l’accélération « matérielle », cette technique
est intimement liée au matériel de votre ordinateur. Selon l’appareil utilisé, il est possible
que ce hack n’ait aucun effet. Certains navigateurs permettent de jeter un œil aux capacités
de votre machine et de voir s’il est possible de bénéficier de l’accélération matérielle. Sur
Firefox, rendez-vous à l’URL about:support, et sur Chrome à chrome://gpu.
POUR ALLER PLUS LOIN will-change
La propriété will-change étant relativement récente, les ressources sont assez rares.
Heureusement, Sara Soueidan a écrit un article extrêmement complet sur le sujet pour
le blog d’Opera.
http://bit.ly/will-change
Figure 10–3 CSS Triggers renseigne à propos des propriétés CSS coûteuses.
Les transitions
Quand une propriété CSS est modifiée, lors d’un changement d’état ou à l’aide de
JavaScript, par exemple, l’effet est appliqué de manière instantanée. Le module CSS
Transitions permet justement de faire en sorte que ce changement d’état ne soit pas abrupt
mais animé. Il est possible d’avoir un contrôle assez fin sur l’animation, notamment en
spécifiant la durée, le délai avant animation, le style d’animation ainsi que d’autres
paramètres.
Toutefois, toutes les propriétés ne peuvent pas être animées. Vous trouverez en annexe de
ce livre la liste des propriétés qui peuvent l’être mais pour faire simple, seules les
propriétés acceptant des couleurs et des valeurs numériques peuvent être animées. Par
exemple, vous pouvez tout à fait animer le changement de couleur de rouge à bleu ou une
longueur, mais pas la valeur de la propriété display.
La propriété transition est un raccourci des propriétés suivantes :
• transition-property, la propriété à animer ;
• transition-duration, la durée d’animation ;
• transition-timing-function, le type d’animation ;
• transition-delay, le délai avant animation.
En résumé, une transition concerne une seule propriété (« quoi ? »), pour une certaine
durée (« pendant combien de temps ? »), avec un éventuel délai (« quand ? ») et dans un
certain style (« comment ? »). C’est ce que nous allons voir immédiatement.
Notons qu’il est en revanche possible de définir plusieurs transitions pour plusieurs
propriétés, en les séparant par des virgules.
.element {
transition:
color 1s ease-in,
Chaque propriété listée va correspondre à une transition, et pourra être liée à un délai, une
fonction de timing et une durée de manière indépendante.
Dans le cas où l’une des propriétés spécifiées n’est pas reconnue ou ne peut être animée,
toutes les autres propriétés sont tout de même animées. En d’autres termes, une propriété
non reconnue ne met pas en péril la transition des autres propriétés ; elle est simplement
omise.
Si vous ne désirez pas spécifier de propriété mais animer tout ce qu’il est possible
d’animer, vous pouvez utiliser le mot-clé all (valeur par défaut). Il ne semble pas y avoir
de répercussion sur les performances lors de l’utilisation de all plutôt que d’une propriété
en particulier.
.element {
transition-property: all;
Figure 10–4
La représentation de la valeur initiale de transition-timing-function
Il est très compliqué de renseigner une courbe de Bézier, principalement parce que ce n’est
pas du tout intuitif, les navigateurs supportant le module de transitions intègrent également
une collection de mots-clés servant d’alias à des courbes de Bézier prédéfinies :
• linear (cubic-bezier(0, 0, 1, 1)) ;
• ease (cubic-bezier(0.25, 0.1, 0.25, 1)), la valeur par défaut ;
• ease-in (cubic-bezier(0.42, 0, 1, 1)) ;
• ease-out (cubic-bezier(0, 0, 0.58, 1)) ;
• ease-in-out (cubic-bezier(0.42, 0, 0.58, 1)).
Sachez qu’il est également possible de renseigner cette valeur à l’aide de la fonction steps,
mais pour des raisons de simplicité et parce qu’elle est assez rarement utilisée dans le
cadre des transitions, nous traiterons cette fonction uniquement dans la partie dédiée aux
animations.
De manière générale, j’aurais tendance à vous conseiller d’utiliser ease-out. Un timing
ease-out débute rapidement pour ralentir progressivement sa vitesse jusqu’au stade final.
Figure 10–7
Courbe de Bézier ease-out
Figure 10–8
Courbe de Bézier ease-out dite « Quintic »
transition-duration: 1s;
}
Choisir la durée d’une transition n’est pas chose aisée. Trop courte, l’animation peut
paraître agressive. Trop longue, elle est susceptible d’être obstructive et de frustrer
l’utilisateur. Pour choisir la bonne durée, cela dépend avant tout de l’effet recherché, mais
également de la timing-function utilisée :
• ease-out : entre 200 et 500 millisecondes. Une durée dans cet intervalle permet de
percevoir l’animation sans pour autant qu’elle ne vienne entraver le contenu. Souvenez-
vous que les animations doivent être subtiles ;
• ease-in : à nouveau, entre 200 et 500 millisecondes est généralement une bonne
moyenne. Gardez à l’esprit que ease-in se termine nécessairement par une accélération et
peu importe la durée, l’effet sera toujours aussi « brutal » ;
• effets bounce, elastic ou autre : autour d’une seconde, à 200 millisecondes près. Les effets
plus exotiques exigent une durée sensiblement plus longue afin de percevoir l’effet
complet. Un effet complexe sur une courte durée paraîtra extrêmement agressif.
INTERNET EXPLORER ET OPERA Valeurs ridiculement petites
Quand bien même nul être humain serait capable de distinguer une transition
inférieure à 10 millisecondes, il est intéressant de noter qu’Internet Explorer et Opera
ne supportent pas des valeurs aussi infimes.
WEBKIT Fonction getComputedStyle() imprécise (ou trop précise)
La fonction JavaScript getComputedStyle() permet de récupérer la valeur réelle (pas
nécessairement la même que la valeur assignée) d’une propriété CSS. Tout d’abord, il
est important de relever que la valeur de transition-duration est toujours exprimée en
secondes (s), même si elle a été spécifiée en millisecondes (ms).
Il s’avère que cette fonction a un léger problème d’implémentation sur le moteur de
rendu WebKit, retournant des valeurs extrêmement précises telles que
0.009999999776482582s au lieu de 0.01s.
« Quand ? » avec transition-delay
La propriété transition-delay, comme la propriété transition-duration, attend une valeur de
type <time> afin de définir le délai avec lequel l’animation débutera. En spécifiant une
durée positive, l’animation ne sera pas déclenchée dès que la ou les propriétés sont
modifiées mais une fois le délai écoulé.
En revanche, spécifier un délai négatif aura un effet particulier : la transition débutera
immédiatement mais on aura toutefois l’impression qu’elle a déjà effectué une partie de sa
course. Par exemple, une transition d’une seconde avec un délai de -0.5s débutera
instantanément au changement d’état. Elle ne démarrera pas à l’état initial mais
directement dans un état à moitié entamé.
.element {
transition-delay: 0.5s;
Il peut être intéressant d’appliquer un léger délai aux transitions lorsqu’elles sont
appliquées à des éléments majeurs dans le layout dont les propriétés sont modifiées en cas
de survol.
Ceci permet d’éviter des effets de flash désagréables lors d’un survol hâtif de la page.
Délayer simplement les transitions d’un dixième de seconde (ou moins) suffit à éviter de
les déclencher par inadvertance.
.element {
transition-delay: 100ms;
}
Utilisation
Les transitions sont parfaites pour éviter les modifications de styles abruptes lors d’un
changement d’état. Le cas le plus commun est de changer la couleur des liens lorsqu’ils
sont survolés ou la cible du focus. Avec une simple transition CSS, on peut donner un effet
smooth à ce changement de couleur :
a {
transition-property: color;
transition-duration: .15s;
transition-delay: 0s;
transition-timing-function: ease;
Notez que l’on spécifie la transition sur l’élément dans son état standard, et pas sous l’effet
de :hover ou :focus. En effet, on souhaite que le changement d’état soit adouci ; il faut donc
bien déclarer la transition sur l’état initial pour qu’elle ait de l’effet dès que l’état est
modifié.
On pourrait réduire la déclaration précédente en utilisant la propriété transition, raccourci
pour toutes les propriétés énoncées précédemment :
a {
Parfait ! Comme nous l’avons vu tout à l’heure, la valeur par défaut de transition-property
est all, c’est-à-dire que toutes les propriétés pouvant être animées bénéficient de la
transition, dont color.
Par défaut, les transitions n’ont pas de délai, rendant la valeur 0s obsolète. Et pour finir, il
se trouve que ease est la fonction d’animation par défaut. Ce qui ne nous laisse plus que la
durée, définie à 150 millisecondes.
*/
.element.initial {
background-color: tomato;
}
.element.final {
background-color: deepskyblue;
transition: none;
}
Eh bien non. Ce code aura pour effet de rendre abrupt le passage de l’état initial à l’état
final, et d’animer le retour de l’état final vers l’état initial. Pour que seul l’aller soit animé,
il faut écrire :
/**
.element.initial {
background-color: tomato;
transition: none; /* optionnel */
.element.final {
background-color: deepskyblue;
transition: background-color 400ms ease-out;
}
Aussi étonnant – et si vous me demandez mon avis, illogique – que cela puisse paraître,
c’est bien dans ce sens que cela fonctionne.
En fait, au moment où la classe .final est appliquée à l’élément, c’est la nouvelle valeur de
transition qui prend effet pour animer le passage de la classe .initial à la classe .final.
Figure 10–9
Sans interaction, la sidebar est semi-transparente.
Lorsque l’on interagit avec celle-ci, son contenu redevient opaque à l’aide d’une brève
transition, puis lorsque l’on cesse de l’utiliser, une transition beaucoup plus longue et donc
plus subtile vient diminuer son opacité pour qu’elle ne vienne pas visuellement empiéter
sur le contenu principal.
Figure 10–10
Au survol, la sidebar devient opaque.
Voyons comment réaliser cet effet à l’aide des transitions, notamment le fait d’avoir deux
transitions différentes (lorsque l’on rentre et lorsque l’on sort de la sidebar).
.sidebar {
opacity: .5;
transition: opacity 5s;
}
.sidebar:hover,
.sidebar:focus {
opacity: 1;
Bien évidemment, vous avez appris votre leçon et savez désormais qu’animer le passage
d’une vue à une autre est important pour donner de l’information à l’utilisateur sur l’action
exécutée.
Figure 10–13
Au choix d’un élément de la liste, cette dernière glisse pour laisser place à la vue dédiée aux détails de l’élément choisi.
</div>
</div>
</div>
* 1. Pleine largeur/hauteur
* 2. Contexte de positionnement pour les vues
width: 100%; /* 1 */
height: 100%; /* 1 */
position: relative; /* 2 */
overflow: hidden; /* 3 */
}
/**
* Vues
* 1. Pleine largeur/hauteur
.view {
position: absolute; /* 1 */
top: 0; /* 1 */
left: 0; /* 1 */
right: 0; /* 1 */
bottom: 0; /* 1 */
/**
* Place la vue de détails hors champ, sur la droite
*/
.view-details {
transform: translateX(100%);
}
Il ne nous reste plus qu’une chose à faire : décrire quelques styles afin de déplacer les vues
lors de l’ajout d’une classe au conteneur (ici, et par simplicité, .switch). La vue de liste se
déplace hors champ vers la gauche, et la vue de détails rentre dans le champ par la droite.
En somme, c’est comme si cette dernière poussait la première vue hors de l’écran.
.switch .view-list {
transform: translateX(-100%);
}
.switch .view-details {
transform: translateX(0);
}
*/
.modal {
position: fixed; /* 1 */
top: 50%; /* 2 */
transform: translateY(-50%); /* 2 */
right: 1em; /* 3 */
left: 1em; /* 3 */
box-shadow: 0 0 0 100em rgba(0, 0, 0, .5); /* 4 */
opacity: 0; /* 5 */
pointer-events: none; /* 6 */
will-change: opacity; /* 7 */
* 2. Affichage de la pop-up
* 3. Rétablissement des effets de pointeur
*/
.modal.is-visible {
pointer-events: auto; /* 3 */
Jusque-là tout fonctionne à merveille, mais l’effet est un peu simple. Que diriez-vous
d’ajouter à l’animation de l’opacité un effet de zoom, ou plutôt de « dézoom ». Nous
allons faire en sorte que la pop-up « atterrisse » sur l’écran en diminuant son échelle
jusqu’à 1. Cela implique donc que la pop-up ait par défaut une échelle supérieure à 1 ;
nous devons donc modifier les styles initiaux.
.modal {
Notez que nous devons enchaîner les transformations comme nous l’avons vu dans le
chapitre précédent. La première est destinée au centrage vertical, la seconde à la mise à
l’échelle. Attention, l’ordre est important !
Ajoutons une simple ligne à notre état final :
.modal.is-visible {
transform: translateY(-50%);
}
Prudence ! Il ne s’agit pas de retirer les transformations d’un seul bloc ; la translation
verticale n’a rien à voir avec notre effet d’apparition et doit perdurer sans quoi la pop-up
va se trouver déplacée ce qui n’est pas ce que l’on souhaite.
Transitions et pseudo-éléments
Malgré le support honorable des transitions, il faut savoir qu’il y a longtemps eu des
restrictions quant à l’application de celles-ci aux pseudo-éléments (notamment ::after et
::before). Parce que vous pourriez en avoir besoin, le tableau suivant présente la
compatibilité des pseudo-éléments uniquement.
Tableau 10–5 Tableau de compatibilité des transitions sur pseudo-éléments
Soulevons également un bug étrange sur Internet Explorer 10 (ne changeons pas les
bonnes habitudes !) : la transition d’un pseudo-élément au survol fonctionne à condition
que l’état survolé soit lui même défini, même à vide.
/* Ne fonctionne pas */
.element:hover:before { /* … */ }
/* Fonctionne */
.element:hover {}
.element:hover:before { /* … */ }
Les animations
En un sens, les animations CSS sont très voisines des transitions. La principale différence
entre les deux est le contrôle que vous avez sur vos animations (au sens large). En effet,
les transitions sont principalement prévues pour éviter les changements d’état abrupts,
mais pas pour gérer une véritable animation avec des étapes clés, des changements de
rythme…
Par exemple, une animation CSS va pouvoir boucler un certain nombre de fois, ou même
indéfiniment alors qu’une transition ne fait jamais qu’animer un élément d’un état initial à
un état final sur une certaine durée. De plus, il va être possible d’avoir un contrôle très fin
sur la qualité de l’animation, en spécifiant les étapes par lesquelles passe l’élément.
Parce qu’il est plus complet, le module d’animations est légèrement plus complexe que
celui des transitions. Le principe de base est simple : il faut définir une animation qui est
globalement une succession d’étapes clés au cours desquelles les propriétés de l’élément
animé varient, puis appliquer cette animation à l’élément grâce à la propriété animation.
Justement, la propriété animation est un raccourci de toutes les propriétés relatives à
l’animation, à savoir :
• animation-name, le nom de l’animation ;
• animation-duration, la durée de l’animation ;
• animation-timing-function, le type d’animation ;
• animation-iteration-count, le nombre d’itérations ;
• animation-direction, la direction de l’animation ;
• animation-play-state, l’état de l’animation ;
• animation-delay, le délai avant le début de l’animation ;
• animation-fill-mode, l’impact de l’animation hors de son exécution.
Comme vous pouvez le constater, une animation accepte de nombreux paramètres aussi je
vous propose de les voir un par un. Comme pour les transitions, il est possible d’appliquer
plusieurs animations à un même élément en les séparant par des virgules.
.element {
animation: <paramètres de l'animation 1>, <paramètres de l'animation 2>; }
.element {
animation-name: mon-animation;
}
/**
* Applique les animations mon-animation et mon-autre-animation à l’élément
*/
.element {
animation-name: mon-animation, mon-autre-animation;
Par exemple, si votre animation définie via @keyframes est constituée de cinq étapes, et que
vous désirez animer les changements de valeurs de l’élément sans qu’il y ait de véritable
transition d’une étape à l’autre, vous pouvez vous y prendre ainsi :
.element {
animation-timing-function: steps(5);
}
Cela dit, on utilise le plus souvent les mots-clés ease (valeur par défaut de cette propriété)
ou linear afin d’avoir une animation plate, c’est-à-dire dont la vitesse d’exécution ne varie
pas au cours de l’animation.
Quoi qu’il en soit, et là je vous renvoie à ce que l’on a vu au sujet de transition-timing-
function (voir page 296), il est souvent préférable d’opter pour ease-out, qui correspond à un
comportement naturellement appréciable.
*/
.element {
animation-duration: 1s;
}
/**
*/
.element {
animation-duration: 500ms;
}
Comme pour les transitions, choisir la durée d’une animation n’est pas toujours évident, et
pourtant c’est un des facteurs principaux dans la réussite d’une animation. Je vous invite à
vous référer à la section dédiée à la propriété transition-duration de la partie précédente
(voir page 299).
*/
.element {
animation-iteration-count: infinite;
*/
.element {
animation-direction: reverse;
}
Figure 10–15
Représentation visuelle des différentes valeurs d’animation-direction (sur une animation à trois itérations)
D’expérience, je dois dire qu’on se sert quasi exclusivement de normal et parfois d’alternate
dans le cas d’une animation infinie, mais j’ai toutefois trouvé un cas d’usage où reverse a
du sens.
Imaginez que vous ayez une animation qui transite la valeur d’opacité (opacity) de 0 à 1, et
qu’à un certain moment vous désiriez l’animer de 1 à 0. Plutôt que de créer une deuxième
animation avec @keyframes, il suffit d’appliquer la direction reverse à l’appel de l’animation.
CURIOSITÉ reverse et animation-timing-function
Lorsqu’une animation est jouée en reverse, son animation-timing-function est également
inversée, c’est-à-dire qu’une animation ease-in inversée aura l’effet d’un ease-out.
.element {
animation-play-state: paused;
}
Les cas d’utilisation ne sont pas les plus fréquents, mais on peut imaginer un carrousel
fonctionnant avec des animations CSS qui serait mis en pause lorsqu’il est survolé. Il est
courant de survoler la zone que l’on observe à l’écran, aussi interrompre les animations du
carrousel lorsque le curseur est dessus permet de laisser le temps à l’internaute de
visualiser le contenu.
.carousel {
animation: carousel 15s infinite;
}
.carousel:hover {
animation-play-state: paused;
}
.element {
animation-delay: 150ms;
}
animation-fill-mode: forwards;
}
/* Le contenu de mon-animation */
}
Elle contient ensuite une ou plusieurs étapes clés (le terme officiel étant « sélecteurs
keyframes », selon les spécifications) qui se composent d’une ou plusieurs valeurs définies
en pourcentage et/ou par les mots-clés from (équivalent à 0%) et to (équivalent à 100%). Ces
sélecteurs définissent les différentes étapes auxquelles l’élément animé verra ses
propriétés modifiées.
/**
*
*/
@keyframes mon-animation {
from {
color: deepskyblue;
25%, 75% {
color: tomato;
50% {
color: lightgreen;
Vous n’êtes pas obligé de préciser les clés 0% (ou from) et 100% (to). Dans tous les cas, le
navigateur construira lui-même une clé à ces pourcentages. De même, vous n’êtes pas tenu
de spécifier les clés dans l’ordre croissant. Elles seront de toute façon triées par le
navigateur, qui en profitera pour exclure les duplicatas et les clés invalides (inférieures à 0%
ou supérieures à 100%).
Chaque étape clé accepte donc un nombre illimité de propriétés/valeurs classiques.
Cependant, seules les propriétés pouvant être animées le seront ; vous trouverez la liste de
ces propriétés dans les annexes de ce livre.
Illustrons tout ceci avec un exemple, voulez-vous ? L’animation suivante, appelée
animation-opacity, est composée d’une unique étape clé to (soit 100%) qui indique que
l’élément doit avoir une opacité de 0 lorsque l’animation s’achève.
@keyframes animation-opacity {
to {
opacity: 0;
}
}
* animation-name: animation-opacity;
* animation-duration: 750ms;
* animation-timing-function: ease-in-out;
* animation-iteration-count: 1;
* animation-delay: 0.1s;
* animation-fill-mode: none;
* animation-play-state: running;
*/
.element:hover {
Animations et performance
Une animation maintenue en CSS sera généralement plus performante qu’une animation
réalisée en JavaScript, mais ce n’est pas pour cela que les animations CSS ne coûtent rien.
En réalité, il y a deux propriétés que le navigateur est capable d’animer de manière
extrêmement fluide : opacity, et transform. Cela n’est pas sans rappeler la notion
d’accélération matérielle que nous avons abordé au début de ce chapitre.
Bien évidemment, il est possible d’animer beaucoup plus que cela, mais ce n’est pas sans
coût du point de vue des performances. Animer une autre propriété que celles-ci peut faire
tomber les images par seconde (FPS, Fraps Per Second) en dessous de 60, palier que l’on
considère généralement optimal.
Fort heureusement, ce sont souvent ces propriétés que l’on souhaite animer. Par
conséquent, souvenez-vous qu’il vaut mieux utiliser transform: translate() que les propriétés
offset top, right, bottom et left lorsqu’il s’agit de déplacer un élément à l’écran.
POUR ALLER PLUS LOIN
Si vous désirez approfondir la question de l’animation de la position d’un élément à
l’écran, je vous recommande vivement ces articles par Chris Coyier et Paul Irish.
http://bit.ly/css-tricks-offset-performance
http://bit.ly/paul-irish-offset-performance
Côté HTML, c’est tout ce dont nous avons besoin et vous allez voir que les styles
nécessaires à la réalisation d’un loader en CSS sont tout aussi succincts.
/**
* Loader
* 1. Masquage du texte
* 2. Dimensionnement du loader
* 3. Centrage horizontal
* 4. Rendu sphérique
* 5. Bordure invisible …
* 6. … sauf à droite
* 7. Animation spin infinie
*/
.loader {
text-indent: 101%; /* 1 */
overflow: hidden; /* 1 */
white-space: nowrap; /* 1 */
width: 5em; /* 2 */
height: 5em; /* 2 */
border-radius: 50%; /* 4 */
border: .5em solid transparent; /* 5 */
border-right-color: deepskyblue; /* 6 */
/**
* Définition de l’animation avec une étape clé unique
to {
transform: rotate(1turn);
}
Figure 10–16
Un loader accessible et animé rien qu’en CSS
Pour un rendu un petit peu moins monotone, on peut opter pour une timing-function
différente de linear, comme ease-out. En somme, la rotation sera d’abord rapide, puis un
peu plus lente sur la fin, avant de reprendre la rotation suivante rapidement à nouveau.
RESSOURCE Un loader avancé par Lea Verou
Si vous aimez l’idée d’un loader exclusivement fait en CSS (et vous devriez), sachez
que Lea Verou a écrit un très bon article à ce sujet dans lequel elle combine visuel
intéressant, accessibilité et animation.
http://bit.ly/lea-verou-loader
Par exemple, vous pourriez mettre en place un carrousel d’images animé intégralement en
CSS plutôt qu’en JavaScript. C’est ce que je vous propose de faire en commençant par
déclarer le HTML nécessaire à notre carrousel :
<div class="slider-wrapper">
<ul class="slider">
<li class="slide"><img src="…" alt="…" /></li>
Le conteneur global, dont les dimensions sont les mêmes que celles d’une slide, fait office
de masque. Son débordement est caché.
/**
.slider-wrapper {
width: 620px;
height: 240px;
overflow: hidden;
}
/**
*/
.slider {
list-style: none;
padding: 0;
animation: slide 25s infinite linear; /* 1 */
margin: 0;
width: 500%; /* 2 */
overflow: hidden;
/**
* Alignement des slides à l’horizontal
*/
.slide {
float: left;
}
/**
Cet exemple est somme toute assez sommaire, et peut-être n’êtes vous pas friand de l’effet
« slide ». Concluons donc cette démo avec une autre feuille de styles pour notre slider,
cette fois pour un effet « fade-in ».
/**
*/
.slider {
width: 620px;
height: 240px;
overflow: hidden;
position: relative; /* 1 */
}
/**
* Slides
max-width: 100%;
position: absolute;
left: 0;
right: 0;
animation: slide 25s linear infinite; /* 1 */
/**
* Décalage de l’animation de 5 s pour chaque slide
*/
/**
* De 0 à 1.25 s (25 × 5 / 100), le slide apparaît
* De 1.25 s à 3.75 s (25 × 15 / 100), le slide subsiste
@keyframes slide {
0%, 20%, 100% { opacity: 0 }
5%, 15% { opacity: 1; }
RESSOURCE
Bien que l’effet n’ait rien de nouveau, cette implémentation à la fois légère et flexible
a pour auteur Brad Knutson.
http://bit.ly/knutson-carousel
POUR ALLER PLUS LOIN Un slider en CSS
Notre exemple est relativement simple mais sachez qu’il est possible de pousser les
choses beaucoup plus loin. Dans cet article publié sur le site Smashing Magazine,
Alessio Atzeni utilise des animations assez complexes pour réaliser un carrousel du
plus bel effet.
http://bit.ly/css-slideshow
Figure 10-18
Un slider CSS impressionnant par Alessio Atzeni, avec barre de défilement, légendes au survol et pour couronner
le tout, des animations soignées
Figure 10–19
Les images sont affichées avec un léger décalage, rendant le chargement initial bien moins abrupt.
width: 10em;
height: 10em;
margin: 1em;
}
Maintenant, nous avons besoin de notre animation. Elle est extrêmement simple puisque
nous ne faisons varier que l’opacité (opacity) :
@keyframes fade-in {
to {
opacity: 1;
}
Et enfin, il ne reste plus qu’à appliquer l’animation à nos éléments, avec un délai
proportionnel à leur position dans la grille.
.item {
animation-delay: $i * 0.1s;
}
}
max-height: 0;
opacity: 0;
transform: scale(0);
to {
max-height: 100px;
}
}
.new-item {
animation: grow .2s ease;
}
Quelques explications s’imposent. Tout d’abord, rappelons que l’élément est ajouté par le
biais de JavaScript, injecté dans le DOM à l’index désiré. Il est ajouté avec la classe new-
item, afin que l’on puisse appliquer des styles particuliers à cet élément justement.
Vous remarquerez que l’on n’indique pas les propriétés opacity et transform dans l’étape clé
finale (to) dans la mesure où l’on transite depuis les valeurs de l’étape clé initiale (from)
vers les valeurs par défaut (opacity: 1 et transform: scale(1)) ; par conséquent, elles sont
optionnelles.
L’animation de la propriété max-height est nécessaire afin de pousser progressivement les
éléments vers le bas pour dégager de la place. En effet, lorsque l’élément est ajouté au
DOM, il occupe déjà physiquement un emplacement même s’il est invisible à cause
d’opacity et scale(0) (qui je le rappelle n’est que visuel et ne modifie pas la taille physique
de l’élément).
Afin que les tâches suivantes soient poussées vers le bas de manière fluide, il faut que la
hauteur du nouvel élément soit animée également ; pour cela, rien de tel que max-height. De
plus, dans la mesure où la propriété animation-fill-mode n’a pas de valeur déclarée, la valeur
de max-height n’aura d’effet que durant l’animation, après quoi elle sera purement et
simplement supprimée de l’objet.
Figure 10–20
Les éléments se séparent pour faire place, puis le nouvel élément entre depuis le fond.
On peut pousser cette idée plus loin en imaginant une autre animation : d’abord, tous les
éléments venant après l’élément sur le point d’être ajouté sont décalés vers le bas pour
dégager un emplacement vide, puis l’élément vient occuper celui-ci en rentrant par la
gauche.
@keyframes make-space {
from {
max-height: 0;
}
to {
max-height: 50px;
}
}
@keyframes slide-in {
from {
opacity: 0;
transform: translateX(-300px);
}
.new-item {
animation:
open-space .2s,
slide-in .3s .2s backwards;
Cet effet étant un peu plus complexe, nous avons besoin de réaliser deux animations
différentes :
• une pour dégager un emplacement pour le nouvel élément en jouant avec max-height
comme dans notre exemple précédent ;
• une pour insérer notre élément dans le nouvel emplacement par la gauche.
Là encore, nous ne spécifions pas les valeurs finales dans notre animation slide-in
puisqu’il s’agit des valeurs par défaut (pas d’opacité et pas de transformation). En outre,
seules les valeurs pour la première étape clé sont nécessaires.
L’utilisation de nos animations est un peu plus subtile également puisqu’il faut jouer avec
animation-delay et animation-fill-mode. En effet, l’animation slide-in ne doit débuter que
lorsque la première animation est achevée ; pour cela, on applique un délai égal à la durée
de l’animation make-space.
Concernant animation-fill-mode, son utilisation est toute indiquée. Pour que l’élément soit
invisible et hors champ durant make-space, il faut que les valeurs spécifiées dans la première
étape clé de slide-in soient appliquées durant le délai de cette animation, d’où la valeur
backwards.
Figure 10–21
Les éléments se séparent pour faire place, puis le nouvel élément entre depuis la gauche avec une opacité réduite.
PLUS D’INFOS
Les exemples que nous venons d’étudier proviennent d’un article bien connu intitulé
« Transitional Interfaces » par Pasquale D’Silva. Je ne peux que vous recommander
cet article de qualité décrivant bien comment les animations peuvent aider à la
compréhension d’une interface et faciliter les actions de l’utilisateur.
http://bit.ly/transitional-interfaces
Le code, quant à lui, est de Chris Coyier qui a mis en pratique Transitional Interfaces
en CSS. J’ai pris des libertés sur son code dans le but de faciliter les explications (et
de l’améliorer un peu), mais vous devriez probablement lire son article.
http://bit.ly/transitional-interfaces-css
Figure 10–22
L’état normal du système, flouté avec la note « SPOILER ALERT »
Figure 10–23
Au survol, le compte à rebours est déclenché.
Figure 10–24
Après trois secondes, le flou disparaît laissant le contenu lisible.
* 5. Transition du floutage
*/
.spoiler {
position: relative; /* 1 */
color: transparent; /* 2 */
text-shadow: 0 0 1em black; /* 3 */
cursor: help; /* 4 */
/**
* 1. Retour à la couleur initiale (ou celle de votre choix)
* 2. Retrait du flou
* 3. Délai uniquement lors du retrait du flou et non
* lors de la réapplication lorsque le curseur sort du survol
*/
.spoiler:hover {
color: initial; /* 1 */
text-shadow: 0 0 0 transparent; /* 2 */
transition-delay: 3s; /* 3 */
}
.spoiler::before,
.spoiler::after {
position: absolute;
line-height: 1;
font-family: 'Fjalla One', sans-serif;
color: #872e27;
text-shadow: none;
}
/**
* Message « SPOILER ALERT »
* 1. Centrage horizontal
*/
.spoiler::before {
top: .5em;
left: 0; /* 1 */
right: 0; /* 1 */
text-align: center; /* 1 */
font-size: 3em;
}
/**
*/
.spoiler:hover::before {
animation: countdown-before 4s forwards;
}
/**
* Compte à rebours
*/
.spoiler::after {
left: 50%; /* 2 */
margin-left: -.5em; /* 2 */
opacity: 0; /* 4 */
top: 1.25em;
width: 1em;
font-size: 3em;
line-height: 1em;
text-align: center;
}
/**
.spoiler:hover::after {
animation: countdown-after 4s forwards;
}
Nous en avons fini avec la partie styles. Il ne nous reste plus qu’à créer les animations
countdown-before et countdown-after. La première, appliquée sur le pseudo-élément dont le
contenu est « SPOILER ALERT » va se contenter de remonter légèrement le texte, puis de
le faire disparaître.
@keyframes countdown-before {
/**
* au compte à rebours.
*/
25% {
transform: translateY(-.33em);
}
/**
75% {
opacity: 1;
}
/**
* À la fin de l’animation, le texte
100% {
opacity: 0;
transform: translateY(-.33em);
La seconde animation, dédiée au compte à rebours, est plus complexe. Elle doit animer la
propriété clip pour ne laisser apparaître que le bon chiffre à chaque étape (c’est là qu’entre
en jeu la fonction steps tant attendue). Comme pour l’animation précédente, elle doit
également s’occuper de faire disparaître le pseudo-élément pour qu’il ne gêne pas la
lecture du texte après coup.
@keyframes countdown-after {
/**
25% {
clip: rect(0, 1em, 1em, 0); /* 1 */
transform: translateY(0); /* 1 */
opacity: 1; /* 2 */
/**
* À la 2e seconde (4 × 50 / 100)
* 1. Positionné sur le chiffre « 2 »
50% {
clip: rect(1em, 1em, 2em, 0); /* 1 */
transform: translateY(-1em); /* 1 */
* À la 3e seconde (4 × 75 / 100)
* 1. Positionné sur le chiffre « 1 »
* 2. À partir de cet instant, l’opacité va progressivement
transform: translateY(-2em); /* 1 */
opacity: 1; /* 2 */
/**
* À la fin de l’animation,
*/
100% {
opacity: 0;
Je me suis posé la même question, la raison en est très simple : le passage de l’état initial à
la première étape clé doit se faire de manière fluide, afin que le compte à rebours
apparaisse progressivement.
Ensuite, les étapes 2 et 3 (passant respectivement le compte à rebours à « 2 », puis « 1 »)
sont forcées en steps() de sorte que l’on ne voit pas l’animation de clip et translate(), qui
gâcherait l’effet.
Enfin, le passage à la dernière étape clé se fait de manière fluide également, afin que
l’opacité diminue progressivement jusqu’à ce que l’élément disparaisse intégralement.
C’est là une utilisation audacieuse de la propriété animation-timing-function, mise à jour
dynamiquement au cours de l’animation !
CLARIFICATION À propos de la propriété clip
Si vous connaissez peu ou pas la propriété clip, sachez qu’elle a été dépréciée au profit
de la propriété clip-path afin de maintenir une certaine cohérence avec le monde du
SVG.
Cependant, le support de cette dernière n’est pas au mieux, surtout comparé à celui de
clip qui remonte jusqu’à Internet Explorer 4 ! De fait, on se contente de clip en
attendant que le support de la propriété recommandée s’améliore.
Si vous désirez comprendre son fonctionnement, je vous recommande ce tutoriel écrit
par votre serviteur et publié sur le site Codrops.
http://bit.ly/codrops-clip
La propriété animation-fill-mode n’est pas supportée sur Android 2.3 et toutes les versions
inférieures.
IOS 6.1 Animations des pseudo-éléments
Safari sur iOS 6.1 et ses versions inférieures ne permettent pas d’animer des pseudo-
éléments.
A
Liste des propriétés CSS et de leur valeur
par défaut
Vous trouverez ci-dessous la liste de toutes les propriétés CSS avec leur valeur par défaut.
Ces valeurs correspondent aux valeurs appliquées par le navigateur lorsque la valeur
renseignée est initial.
Tableau A-1 Liste des propriétés CSS officielles et de leur valeur par défaut
Voici la liste des propriétés CSS qui peuvent être animées, ainsi que le type de valeur
qu’elles attendent au cours d’une animation.
Vous trouverez ci-dessous la liste de toutes les propriétés qui peuvent être animées (ou
transitées, pardonnez-moi le terme). Notons toutefois qu’il s’agit là de la théorie, et que
certains navigateurs ne supportent peut-être pas les animations et transitions sur certaines
de ces propriétés.
Tableau B-1 Index des propriétés CSS qui peuvent être animées
!important 246
:any 39
:blank 34
:checked 45
:default 47
:disabled 40
:empty 33
:enabled 40
:first-child 23
:first-of-type 31
:focus 39, 301, 303
:hover 273, 301, 303, 310
:indeterminate 46
:in-range 49
:invalid 44
:lang 35
:last-child 23
:last-of-type 31
:matches 37
:not 36, 250
:nth-child 24
:nth-last-child 24
:nth-of-type 31
:only-child 30
:only-of-type 31
:optional 45
:out-of-range 49
:read-only 42
:read-write 42
:required 45
:root 36, 162, 168
:target 32
:valid 44
@custom-media 260
@font-face 229
@hyphenation-resource 223
@keyframes 242, 247, 311, 316, 324
@page 63, 250
@supports 244
A
accélération matérielle 271, 292, 318
accessibilité 40, 44, 49
align-content 76
align-items 75, 79
align-self 79, 104
all 241
all: inherit 241
all: initial 241
all: unset 241
Android 58, 144, 149, 274, 316, 334
animation 242, 289, 310, 341
animation-delay 315, 327
animation-direction 313
animation-duration 312
animation-fill-mode 315, 324, 326
animation-iteration-count 313
animation-name 311, 316
animation-play-state 314
animation-timing-function 311, 333
anti-aliasing 174
Autoprefixer 86, 208
B
backface-visibility 285
backface-visibility: hidden 287
background 344
background-attachment 165
background-clip 167
background-image 151, 157, 274, 344
background-origin 160, 164
background-position 158, 163, 187, 189
background-repeat 159
round 159
space 159
background-size 161, 344
background-size: contain 202
contain 161
cover 161
BEM 22
Bézier (courbe de) 296, 311
Blink 250
blur() 171, 330
boîte (modèle de) 54, 146
Bootstrap 57, 248
border 54
border-color 344
border-radius 137, 344
border-bottom-left-radius 137
border-bottom-right-radius 137
border-top-left-radius 137
border-top-right-radius 137
border-width 344
border-bottom 54
border-box 56, 125, 164, 167
border-image 181
border-image-outset 183
border-image-repeat 183
border-image-slice 182
border-image-source 182
border-image-width 183
border-left 54
border-radius 124
border-right 54
border-top 54
box model 54
box-shadow 144, 151, 173, 308
box-sizing 55
box-sizing: border-box 56
break-after 63
break-before 63
break-inside 68
break-inside 63
brightness() 172
C
calc() 93, 186
centrage 271
ch (unité) 196
Chrome 5, 7, 43, 48, 51, 112, 121, 174, 193, 202, 204, 232, 250, 293, 344
circle() 122
clip 333
clip-path 333
column-count 60
column-fill 65
column-gap 61
column-rule 62
column-rule-color 62
column-rule-style 62
column-rule-width 62
columns 61
column-span 64
column-width 59
content-box 56, 125, 164, 167
contenteditable 42
contrast() 172
courbe de Bézier 296, 311
CPU 292
CSSWG 12, 214
D
display
flex 72, 247
grid 92
DOM 2, 16, 28, 36, 50, 70, 83, 108, 238, 263, 294, 325
DOM parser 19
DOMContentLoaded 301
drop-shadow() 173
E
ease 311
ease-in 311
ease-in-out 311
ease-out 311, 319
ellipse() 123
em 189
F
fallback 155, 194, 230, 240, 243
Feature Queries 244
fill 206
filter 136, 171
Firefox 7, 43, 48, 51, 57, 70, 85, 179, 208, 216, 221, 225, 242, 277, 293, 309
fit-content 207
flag 2, 10, 112, 121, 242, 244
Flash 264, 289
flex 78
flex-basis 78
Flexbox 3, 11, 70, 88, 247
flex-direction 72
flex-flow 74
flex-grow 77, 78
flex-shrink 77, 78
flex-wrap 73
float 72, 87, 92, 104
float 344
flow-from 115
flow-into 114
focus (:focus) 301, 303
font-size 189, 198, 200
font-size
62.5% 194
Foundation 57
fr 90, 93, 95
fragment identifier 32
G
Gecko 51, 309
Google 6
GPU 292, 294
grayscale() 174
grid
subgrid 103
grid-area 101
grid-auto-flow 103
grid-column 101
grid-column-end 97, 101
grid-column-start 97, 101
grid-row 101
grid-row-end 97, 101
grid-row-start 97, 101
grid-template 93
grid-template-areas 96, 110
grid-template-columns 93
grid-template-rows 93
H
hack 54, 70, 187
hanging-punctuation 226
hash 33
hasLayout 273
height 54, 78, 206, 252, 344
high-contrast 257
hover (
hover) 303
hover (:hover) 273, 301, 303, 310
hsl 131
hsla 131
hue-rotate() 175
hyphens 221
auto 223
manual 222
none 222, 236
I
identifier (fragment) 32
important (!important) 246
inline-block 70, 87, 102, 104, 267
inset() 123
Internet Explorer 5, 7, 17, 20, 21, 24, 28, 33, 50, 56, 58, 70, 85, 121, 136, 144, 156, 162, 179, 181, 184, 189, 193, 197,
204, 225, 257, 262, 269, 277, 285, 300
invert() 175
Isotope 66
J
justify-content 74
K
KNACSS 57
L
linear 311, 319
linear-gradient() 152, 160
M
margin 54, 71, 188, 271, 344
margin-bottom 236
margin-box 125
Masonry 66
matchMedia 257
matérielle (accélération) 271, 292, 318
matrix() 276
max-content 93, 206
Media Queries 68, 106, 201, 238, 248, 251
min-content 93, 204
mixin 194
mobile 6
Mobile First 81, 104, 261
modèle de boîte (box model) 54, 146
Modernizr 10, 86, 244, 246
N
not (:not) 250
O
opacity 133, 314, 317, 318, 323, 325, 344
opacity() 176
Opera 8, 43, 48, 167, 216, 250, 262, 300
Opera Mini 44, 45, 70, 151
Opéra 300
opérateur d’adjacence 16
directe 16
générale 17
order 76, 83
overflow 116
overflow-wrap 212, 218
P
padding 54, 142, 206, 236, 344
padding-bottom 54
padding-box 56, 125, 164, 167
padding-left 54
padding-right 54
padding-top 54
page (@page) 63
parser CSS 28, 61, 187, 243, 244, 247
performance 19, 146, 180, 271, 317
perspective 282
perspective() 282
perspective-origin 284
PNG 134, 169
pointer-events 179
none 180, 307
Polyfill 28
polyfill 113, 203
polygon() 124
position 344
fixed 277
sticky 111, 248
préfixe constructeur 2, 9, 86, 143, 149, 156, 208, 247
préprocesseur 6, 29, 86, 107, 136, 177, 186, 193, 195, 208, 237, 269, 324
pseudo-classe 22
pseudo-élément 49, 115, 120, 134, 267, 278, 309, 330
Q
Quirks Mode 56
quotes 35
R
radial-gradient() 154
rebeccapurple 130
region-fragment 115
rem 189, 200
RenderLayer 271
repeat() 93, 94, 107
requestAnimationFrame 294
responsive 251
Responsive Web Design 6, 55, 79, 198, 200
Retina 129
rgba 130, 243
root (:root) 162, 168
rotate() 265
rtl 73
rythme vertical 192
S
Safari 7, 17, 43, 48, 58, 85, 112, 144, 149, 162, 164, 188, 197, 202, 204, 216, 334
Sass 6, 29, 30, 86, 96, 107, 136, 186, 193, 195, 208, 269, 324
saturate() 176
scale() 273
scaleX() 273
scaleY() 273
sélecteur d’attribut 18
modulé 19
simple 18
sepia() 177
shape-image-threshold 125
shape-inside 124
shape-margin 125
shape-outside 125
shape-padding 125
skewX() 274
skewY() 274
spécifications 11, 18, 186
spécificité 37
sprite 46, 344
step-end() 311
steps() 311, 329
step-start() 311
sticky (position) 248
subgrid 103
SVG 6, 36, 170, 174, 179, 277
T
tab-size 225
text-align-last 228
justify 229
text-overflow 217
ellipsis 218
text-shadow 149
transform 3, 174, 318, 325
translate3d(0, 0, 0) 292
translateZ(0) 292
transformation 248, 263
transform-origin 277, 284, 288
transform-style 284
flat 285
preserve-3d 284, 286
transition 242, 291, 295, 341
cubic-bezier() 296
transition-delay 300
transition-duration 299
transition-property 296
transition-timing-function 296, 299
translate() 270
U
Unicode 230
unicode-range 229
URL 32, 293
url() 125, 151, 171, 179
V
var() 239
Version 39
vertical (rythme) 192
vh 197
viewport 82, 111, 165, 197
visibility 344
vmax 203
vmin 203
vw 197
W
W3C 7, 8, 50, 71, 292
WebGL 6, 264
WebKit 70, 143, 168, 188, 203, 208, 254, 298, 300, 309
WHATWG 7, 9
white-space 213
normal 213
nowrap 213, 217, 218
white-space
pre 213, 235
pre-line 214
pre-wrap 214
width 54, 78, 206, 252, 344
will-change 292
word-break 224
word-wrap 213
X
XML 36
Z
zoom 273, 308
Ce livre est imprimé sur du papier couché mi-mat 115g, issu de forêts gérées durablement.
Dépôt légal : février 2015
N° d’éditeur : 9458
IMPRIMÉ EN SLOVÉNIE PAR GPS GROUP
Pour suivre toutes les nouveautés numériques du Groupe Eyrolles, retrouvez-nous sur Twitter et Facebook
@ebookEyrolles
EbooksEyrolles
Et retrouvez toutes les nouveautés papier sur
@Eyrolles
Eyrolles