Skip to main content

Chapitre 50 : Utilisation de l'API CSS.supports pour le chargement conditionnel

Détection de fonctionnalités pour charger des feuilles de style optimisées

L'API CSS.supports et le chargement dynamique

1. Quoi

L'API CSS.supports() est une interface JavaScript permettant de vérifier si le navigateur actuel supporte une propriété CSS spécifique ou une combinaison de propriété et de valeur. Elle est l'équivalent programmatique de la règle CSS @supports (appelée "Feature Queries").

Contrairement à la règle @supports qui s'exécute directement dans le moteur de rendu CSS, CSS.supports() renvoie un booléen (true ou false) utilisable dans la logique JavaScript. Cela permet de prendre des décisions architecturales sur le chargement des ressources avant même que le navigateur ne commence à parser des feuilles de style potentiellement incompatibles ou inutiles.

2. Pourquoi

Dans un projet professionnel, l'objectif est souvent de concilier innovation visuelle (utilisation de fonctionnalités modernes comme le Subgrid, les Container Queries ou les Anchor Positioning) et compatibilité ascendante (support des anciens navigateurs).

Le chargement conditionnel via JavaScript apporte plusieurs avantages majeurs :

  1. Optimisation de la Performance (Payload) : Au lieu de charger un fichier CSS massif contenant à la fois le code moderne et les "fallbacks" (solutions de secours), on ne charge que le fichier nécessaire. On réduit ainsi le poids du CSS transféré sur le réseau.
  2. Évitement du "CSS Bloquant" : En détectant le support en amont, on peut éviter d'injecter des règles CSS complexes que le navigateur passerait du temps à ignorer (bien que le coût du parsing des règles ignorées soit faible, il n'est pas nul sur des fichiers très volumineux).
  3. Contrôle Granulaire : JavaScript permet de déclencher des actions complexes suite à la détection (ex: charger un polyfill JS spécifique, modifier une classe sur le <body> pour adapter le JS, ou envoyer un événement de tracking sur la version du navigateur utilisée).
  4. Stratégie de Progressive Enhancement : On définit une base stable pour tous, et on "améliore" l'expérience dynamiquement pour les navigateurs capables de supporter des fonctionnalités avancées.

3. Comment

A. Syntaxe de base

L'API propose deux manières d'interroger le support :

1. Vérification d'une propriété seule :

if (CSS.supports('display')) {
console.log('Le navigateur reconnaît la propriété display');
}

2. Vérification d'une paire propriété/valeur (la plus courante) :

// Syntaxe avec deux arguments
if (CSS.supports('display', 'grid')) {
console.log('Le Grid Layout est supporté');
}

// Syntaxe avec une seule chaîne (format CSS standard)
if (CSS.supports('display: grid')) {
console.log('Le Grid Layout est supporté');
}

B. Cas concret : Chargement conditionnel de feuilles de style

Imaginons un scénario où nous utilisons le CSS Subgrid, une fonctionnalité puissante mais dont le support a été progressif. Nous souhaitons charger modern-layout.css pour les navigateurs compatibles et fallback-layout.css pour les autres.

/**
* Gère le chargement conditionnel du CSS en fonction du support du Subgrid.
* Cette approche évite de charger du code inutile pour les navigateurs modernes
* tout en garantissant une expérience correcte pour les anciens.
*/
const loadConditionalStyles = (): void => {
const head = document.head;
const link = document.createElement('link');
link.rel = 'stylesheet';

// On vérifie le support du subgrid (valeur 'subgrid' pour la propriété 'grid-template-columns')
const supportsSubgrid = CSS.supports('grid-template-columns', 'subgrid');

if (supportsSubgrid) {
console.info('🚀 Chargement du layout moderne (Subgrid)');
link.href = '/css/modern-layout.css';
} else {
console.warn('⚠️ Subgrid non supporté. Chargement du layout de secours.');
link.href = '/css/fallback-layout.css';
}

head.appendChild(link);
};

// Exécution immédiate pour minimiser le flash de contenu non stylisé (FOUC)
loadConditionalStyles();

C. Limitations

Bien que puissante, l'API CSS.supports() présente des limites :

  • Détection de syntaxe, pas d'implémentation : CSS.supports() vérifie si le navigateur reconnaît la syntaxe. Il ne garantit pas que la fonctionnalité est implémentée sans bugs.
  • Dépendance au JavaScript : Si l'utilisateur a désactivé JavaScript, le chargement conditionnel ne s'exécutera pas. Il est donc impératif d'avoir un fichier CSS de base chargé via une balise <link> standard pour assurer un rendu minimal.
  • Timing du rendu : L'injection d'un lien CSS via JS peut provoquer un FOUC (Flash of Unstyled Content). Le navigateur affiche le HTML brut pendant quelques millisecondes avant que le fichier CSS injecté ne soit téléchargé et appliqué.

4. Zone de Danger

L'erreur commune : Remplacer totalement le CSS par du JS Certains développeurs tentent de supprimer toutes les balises <link> du HTML pour tout gérer via CSS.supports(). C'est une erreur critique. Si le JS échoue ou est lent, la page sera totalement non stylisée.

La bonne pratique : Le "Layering" (Couches)

  1. Couche 1 (HTML) : Un fichier base.css minimaliste (reset, typographie, couleurs) chargé normalement.
  2. Couche 2 (JS/CSS.supports) : Injection du fichier de layout optimisé ou de secours.
  3. Couche 3 (CSS) : Utilisation de @supports à l'intérieur des fichiers CSS pour des ajustements mineurs.

Flux de chargement conditionnel via CSS.supports

Questions clés

1. Quelle est la différence fondamentale entre @supports en CSS et CSS.supports() en JavaScript ?

Découvrir la réponse

@supports est une directive interne au moteur CSS qui permet d'appliquer des règles conditionnelles sans quitter le fichier de style. CSS.supports() est une API JavaScript qui permet de prendre des décisions logiques (comme charger un fichier différent, modifier le DOM ou charger un polyfill) basées sur les capacités du navigateur.

2. Comment éviter le Flash of Unstyled Content (FOUC) lors d'un chargement conditionnel ?

Découvrir la réponse

Pour minimiser le FOUC, on peut :

  1. Placer le script de détection le plus haut possible dans le <head>.
  2. Utiliser un fichier CSS de base (base.css) qui définit la structure minimale.
  3. Masquer temporairement le <body> via CSS (opacity: 0) et le révéler une fois que le fichier CSS conditionnel a fini de charger (via l'événement onload de la balise <link>).

3. L'API CSS.supports() peut-elle détecter si un navigateur supporte les Media Queries ?

Découvrir la réponse

Non, car les Media Queries sont une fonctionnalité fondamentale du langage CSS et non une propriété spécifique avec une valeur. Pour détecter des capacités matérielles ou d'affichage, on utilise plutôt window.matchMedia().

4. Est-il préférable d'utiliser CSS.supports('display', 'grid') ou CSS.supports('display: grid') ?

Découvrir la réponse

Les deux sont valides. La version à deux arguments est légèrement plus "programmatique", tandis que la version à chaîne unique est plus proche de la syntaxe CSS réelle. La seconde est souvent préférée pour copier-coller rapidement des règles depuis un fichier CSS.

Mise en pratique

Exercice 1 : Reproduction guidée

Objectif : Créer un script simple qui détecte le support des "Container Queries" et ajoute une classe supports-cq à l'élément <html>.

Consignes :

  1. Vérifiez si le navigateur supporte la propriété container-type.
  2. Si oui, ajoutez la classe supports-cq au document.
  3. Affichez un message dans la console confirmant la détection.
Découvrir la solution commentée
// On vérifie le support de la propriété container-type
// C'est la propriété clé pour activer les Container Queries
if (CSS.supports('container-type')) {
document.documentElement.classList.add('supports-cq');
console.log('✅ Container Queries sont supportées. Classe ajoutée au HTML.');
} else {
console.log('❌ Container Queries ne sont pas supportées.');
}

Exercice 2 : Adaptation

Objectif : Modifier le script de l'exercice précédent pour charger un fichier CSS différent selon le support des "Custom Properties" (variables CSS).

Consignes :

  1. Détectez le support des variables CSS (ex: --main-color: red).
  2. Si supporté, chargez theme-vars.css.
  3. Si non supporté, chargez theme-static.css.
  4. Assurez-vous que le lien est injecté dans le <head>.
Découvrir la solution commentée
const applyTheme = () => {
const head = document.head;
const link = document.createElement('link');
link.rel = 'stylesheet';

// On teste une variable CSS simple pour vérifier le support des Custom Properties
const supportsVars = CSS.supports('--test-var: 1');

if (supportsVars) {
link.href = 'theme-vars.css';
console.log('Chargement du thème dynamique (Variables CSS)');
} else {
link.href = 'theme-static.css';
console.log('Chargement du thème statique (Fallback)');
}

head.appendChild(link);
};

applyTheme();

Exercice 3 : Conception

Objectif : Implémenter un système de chargement "Critique vs Optimisé".

Scénario : Vous développez une application avec un design très avancé utilisant aspect-ratio et backdrop-filter. Vous voulez que :

  1. Le CSS de base (layout simple) soit chargé immédiatement.
  2. Un fichier advanced-effects.css soit chargé uniquement si le navigateur supporte à la fois aspect-ratio ET backdrop-filter.

Consignes :

  1. Créez une fonction loadAdvancedStyles qui vérifie les deux conditions.
  2. Utilisez l'opérateur logique && pour s'assurer que les deux fonctionnalités sont présentes.
  3. Implémentez un mécanisme pour éviter d'injecter le lien plusieurs fois si la fonction est appelée à nouveau.
Mini-cours JavaScript — Les trois méthodes DOM utilisées dans la solution

document.querySelector('link[href="advanced-effects.css"]')

Parcourt le DOM et retourne le premier élément correspondant au sélecteur CSS passé en argument. Ici, on cherche une balise <link> dont l'attribut href vaut exactement "advanced-effects.css". Si aucun élément ne correspond, la méthode retourne null.

const existingLink = document.querySelector('link[href="advanced-effects.css"]');
if (existingLink) return; // Le fichier est déjà chargé, on sort de la fonction

document.createElement('link')

Crée un nouvel élément HTML en mémoire (pas encore dans la page). On lui assigne ensuite ses attributs (rel, href) avant de l'insérer dans le DOM.

const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'advanced-effects.css';
// À ce stade, le navigateur n'a pas encore téléchargé le fichier

document.head.appendChild(link)

Insère l'élément link créé en tant que dernier enfant de la balise <head>. C'est à ce moment précis que le navigateur détecte la nouvelle balise, lance le téléchargement du fichier CSS et l'applique une fois reçu.

document.head.appendChild(link);
// Le navigateur télécharge et applique 'advanced-effects.css'
Découvrir la solution commentée
/**
* Charge les styles avancés uniquement si toutes les fonctionnalités
* requises sont supportées par le navigateur.
*/
const loadAdvancedStyles = (): void => {
// 1. Vérification des pré-requis
const supportsAspectRatio = CSS.supports('aspect-ratio');
const supportsBackdrop = CSS.supports('backdrop-filter');

// On ne charge le fichier que si les DEUX sont supportés
if (supportsAspectRatio && supportsBackdrop) {

// 2. Sécurité : vérifier si le lien n'existe pas déjà pour éviter les doublons
const existingLink = document.querySelector('link[href="advanced-effects.css"]');
if (existingLink) return;

console.log('🚀 Toutes les fonctionnalités avancées sont supportées. Chargement des effets...');

const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'advanced-effects.css';

// Optionnel : on peut ajouter un événement pour savoir quand le style est appliqué
link.onload = () => {
console.log('✨ Effets avancés appliqués avec succès.');
};

document.head.appendChild(link);
} else {
console.info('Le navigateur ne supporte pas l\'ensemble des effets avancés. Mode simplifié activé.');
}
};

// Appel de la fonction
loadAdvancedStyles();