0950 75 66 69
prix d’un appel local
Il a toujours été complexe de définir avec précision un niveau de support navigateur dans les projets web, d'autant plus avec la variété des plateformes (mobile, desktop) et malgré la concentration du marché autour d'un nombre réduit de moteurs.
D'un côté car de multiples langages entrent en jeu pour l'intégration (interprétation/reconnaissance des balises HTML et des propriétés CSS), développements (syntaxe JavaScript et API utilisables, version de HTTP, protocoles et en-têtes). D'un autre côté car avec les mises à jour constantes de toutes parts, cette grille de lecture est changeante, entre le début et la fin d'un projet, si tant est qu'il y ait une fin.
L'approche par dégradation gracieuse qui a été définie comme une bonne pratique pour ne pas exclure un public non-technophile, bien que très appréciable sur le papier, n'est plus toujours applicable tant la dépendance à des frameworks tout-en-un impose déjà de reconnaître toute une panoplie de fonctionnalités et ne plus permettre de se reposer sur une version dégradée, voire brute d'un site ou d'une application web.
Les plus gros acteurs du web qui ne manquent pourtant pas de moyens et d'un public très large, ont progressivement abandonné les alternatives statiques minimalistes qui pouvaient exister. Nous avons bien plus tendance à vouloir exploiter les derniers outils à la mode, quitte à laisser des personnes sur le bord des autoroutes de l'information, là où par le passé il fallait bien plus faire attention au support des anciens navigateurs.
Des solutions existent pour automatiser et faciliter l'interprétation de code récent sur d'anciennes plateformes.
En CSS, si une propriété récente n'est pas reconnue, la mise en page ou l'apparence sera dégradée mais cela ne devrait pas empêcher la consultation et l'usage (en théorie) ; on utilise aussi autoprefixer pour éviter d'écrire des préfixes vendeurs, mais cela devrait disparaître à terme.
En JavaScript, on a longtemps transpilé, par exemple avec l'aide de Babel du code ECMAScript très récent vers des versions rétro-compatibles, utilisant des syntaxes plus communes, cela se passe en général bien ; par contre si une API ou fonctionnalité native du navigateur est absente c'est plus problématique et il faut passer par un fallback, un shim, un polyfill, c'est-à-dire un bout de code plus ou moins conséquent qui va tenter de reproduire plus ou moins fidèlement ce que le navigateur ne reconnaît pas tout seul.
En HTML étant donné la relative stabilité des balises et les solutions de repli possibles jusqu'à l'interprétation du contenu en texte brut, la question se pose moins... en théorie : deux éléments largement reconnus comme <details>
et <summary>
ont provoqué des tourments pour les lecteurs d'écran, Voiceover iOS ne reconnaissant pas le rôle de bouton de summary. Désormais au tour de <dialog>
et popover
: il y a de nouveaux éléments censés simplifier et remplacer des implémentations ARIA imparfaites mais les navigateurs et/ou lecteurs d'écran ne suivent pas ce rythme (et/ou leur spécification d'ailleurs).
Dans le seul aspect technique, une bibliothèque a aidé jusqu'à présent : Browserslist permet dans un fichier de configuration ou directement dans package.json
de définir par des mots simples quelle "quantité" du marché des navigateurs on souhaite cibler. Par exemple > 0.5%, last 5 versions, not dead
indiquera aux autres outils de compilation (Babel, Autoprefixer, Postcss...) sur quelles instructions se reposer pour satisfaire cette exigence de rétro-compatibilité.
Le site va nous permettre d'obtenir un tableau très détaillé des versions et une estimation de la population globale qui répondra à cette condition.
Vous connaissez Can I use et ses nombreux tableaux, que l'on peut affiner par zone géographique, par usage et par date. Avant de lancer un nouveau développement on pourra consulter l'une ou l'autre fonctionnalité, mais malheureusement pas toutes, cela devient tentaculaire.
Exemple de formule : "Support de Firefox 37, d'Internet Explorer 11, et Chrome 42".
Durant de nombreuses années, les navigateurs ont connu des progrès moins fluides, moins granulaires et plus espacés dans le temps, car les mises à jour automatiques n'étaient pas la règle : il était humainement possible de suivre l'évolution des versions et d'avoir en tête les nouveautés apportées, par exemple entre Internet Explorer 5 et 6.
Désormais, avec des numérotations de versions qui se comptent en dizaines et un rythme de sorties qui se compte en semaines imposé par les équipes de Chromium, puis Firefox, puis Safari (WebKit), il devient difficile de définir avec précision un tableau de support pour un projet d'autant plus que les API envisagées deviennent de plus en plus complexes pour transformer le web en plateforme complète de développement. Si certaines entreprises choisissent la "stabilité" de Firefox ESR (Extended Support Release) pour bloquer les fonctionnalités à une certaine version, cela reste marginal.
Notons l'initiative de Simon Willison qui a codé un outil permettant d'obtenir un historique plus détaillé du support déjà affiché par la documentation MDN avec MDN timelines.
Formule possible : "Support optimal des navigateurs desktop jusqu'à 2 ans et en mode dégradé jusqu'à 5 ans, support des navigateurs mobiles jusqu'à 3 ans".
Avec notre écosystème mouvant, on peut s'orienter vers un support mesuré à l'ancienneté des navigateurs. On ne mesure plus le nombre de versions écoulées mais le temps. Cela a l'avantage d'être souple et cela peut sembler "toujours à jour" quoiqu'il arrive... mais concrètement on sera toujours dans une zone approximative au fur et à mesure de la durée de vie d'un projet : faut-il mesurer par rapport à la date de démarrage des développements, ou de mise en ligne définitive ? Quid du paysage technique dans 6 mois, 1 an et plus.
Avec toutes ces difficultés et l'impossibilité de suivre exhaustivement les versions et leur immense tableau de support, la plus récente vision est le pragmatisme (et optimisme ?) : Baseline définit deux statuts :
Intitulé | Logo | Définition |
---|---|---|
Nouveauté disponible | la fonctionnalité est compatible avec tous les principaux navigateurs : elle est interopérable. | |
Disponibilité générale | 30 mois se sont écoulés depuis la nouvelle date d'interopérabilité. Cette fonctionnalité peut être utilisée par la plupart des sites | |
Disponibilité limitée | C'est trop récent, peu ou pas supporté. |
On retrouve un tableau aussi très fourni grâce à Web Platform Status, à l'initiative de l'équipe de Chrome. Il faut les comprendre, les nouveautés s'enchaînent plus vite qu'il n'est possible à un humain de les suivre et à Google d'abandonner des produits.
C'est probablement ce qui fait le plus sens à l'heure actuelle, avec un repère temporel par année (baseline 2023, 2024, 2025...) et un niveau de confiance affiché pour les développeuses et développeurs bien plus compréhensible pour la question "est-ce que je peux utiliser ce truc, ou est-ce que ça va casser ?".
On retrouve aussi Baseline sur MDN
...et sur CanIUse.
Cet alignement entre équipes de développement de moteurs, documentations et toutes les personnes qui font du web en général a le mérite d'être plus abordable.
Avec une fonctionnalité annoncée en "Disponibilité générale" : on peut assez bien prendre une décision, on sait qu'elle est intéropérable depuis un moment ayant permis à la majorité des internautes d'effectuer les mises à jour nécessaires... n'est-ce pas ? Mais rien ne le garantit, comme toujours un pourcentage non négligeable de personnes seront contraintes pour des raisons de moyens ou d'outils, et rien ne garantit non plus que l'on soit irréprochable du côté des outils d'accessibilité.
👉 On peut décider d'utiliser la fonctionnalité pour un projet qui démarre, ou qui se met à jour dès à présent, avec toutefois un point d'attention à prévoir une solution de repli si son absence est bloquante pour un pourcentage significatif d'internautes.
Avec une fonctionnalité annoncée en "Disponibilité limitée" : c'est assez évident, un ou plusieurs moteurs de navigateurs n'en sont pas équipés (ou les versions de Chromium sont trop fraîches), une majorité d'internautes n'y auront pas accès.
👉 On évite d'y toucher.
Avec une fonctionnalité annoncée en "Nouveauté disponible" : c'est plus délicat. Depuis quand est-elle disponible, pour qui, et quel est le délai qui court (pour les 30 mois), vient-il de démarrer ou est-on proches de passer en statut "Disponibilité générale" ? C'est pourquoi https://webstatus.dev/ précise des dates (lorsqu'on coche les bonnes options) :
Par exemple appearance
nouveau en 2022 est passé en "disponibilité générale" en septembre 2024. La media query prefers-contrast
obtiendra le même sésame en décembre 2024, tandis que pour forced-colors
ce sera en mars 2025.
🤷 Au cas par cas ?
Prenons quelques exemples (à la date de publication) :
Fonctionnalité | Statut | Date d'annonce / disponibilité prévue | Part de la population en France/Europe |
---|---|---|---|
Attribut HTML inert | avril 2023 / octobre 2025 | 95% | |
backdrop-filter | septembre 2024 / mars 2027 | 98% | |
requestVideoFrameCallback | octobre 2024 / avril 2027 | 98% | |
WebUSB | aucune précision* | 75% |
On notera donc des étrangetés dans le pragmatisme qui devrait faire sens : accent-color
est reconnu partout mais encore noté "Limité" ; requestVideoFrameCallback
ne devrait accéder à "Disponibilité générale" qu'en 2027 mais est déjà implémenté en réalité par tous les moteurs actuels ; le vénérable zoom
n'a été listé qu'à partir de mai 2024, etc.
Oui. Des dates en question, de l'importance des technologies à supporter et de leur caractère bloquant ou non-bloquant. Les étiquettes proposées par l'approche baseline sont intéressantes et pratiques : d'un coup d'oeil on peut se faire une première idée et échanger dans des termes simples avec des collègues ou des clients. Mais dans la réalité, on ne sera pas épargnés par les tableaux de supports, les projections temporelles et les pourcentages divers. C'est notre métier, chacun saura prendre les bonnes décisions ;)
Les import maps sont une fonctionnalité moderne de JavaScript qui permet de contrôler comment le navigateur résout les imports de modules.
Vous avez sûrement déjà rencontré dans vos projets des modules JavaScript, aussi appelés ESM (EcmaScript Modules) qui induisent un découpage des portions de code et de données. C'est très pratique, avec certains fichiers - en général fournis par une bibliothèque - qui exportent des fonctions, tableaux, objets, etc. pour les mettre à la disposition d'autres fichiers - en général les vôtres - qui les importent.
On sait aussi que depuis quelques années de tels scripts peuvent être chargés dans le navigateur à l'aide de la balise script équipée de l'attribut type="module"
.
Une déclaration d'import map pourrait être la suivante à l'aide de la nouvelle valeur type="importmap"
.
<script type="importmap">
{
"imports": {
"logger": "/js/logger.js",
"tools/": "/js/tools/",
"lodash": "https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"
}
}
</script>
On peut constater qu'il s'agit d'un objet, contenant une clé "imports"
, elle même définissant une correspondance entre des noms courts et des chemins longs.
On peut aussi faire appel à un beau fichier JSON contenant cette "carte", ce qui semblera plus propre que de les déclarer inline dans le corps de la page.
<script type="importmap" src="/map.json">
Cela a pour but de :
Ainsi on pourra écrire ensuite
import _ from 'lodash';
// Import depuis un chemin local
import { log } from 'logger';
// Import via un préfixe
import { maFonction } from 'tools/malib.js';
On améliore la lisibilité et on peut plus facilement changer les versions et les chemins réels vers les dépendances.
Il y a quelques petites limitations : les projets d'envergure avec des frameworks et des outils de compilation tels que Vite, viennent déjà avec des solutions de résolution de modules. Certaines fonctionnalités qui optimisent le développement et le poids des ressources (hot reloading, tree shaking) ne sont pas disponibles. C'est pourquoi on s'en servira plutôt pour du prototypage rapide, des petites démonstrations techniques ou des projets qui ne passent pas par des frameworks évolués.
L'attribut type="importmap"
est supporté par tous les navigateurs actuels et considéré comme faisant partie de la baseline 2023.
Mermaid est un langage qui permet de créer des diagrammes dynamiques directement dans des fichiers Markdown c'est à dire en mode texte.
Il est très utile pour représenter visuellement des concepts complexes sous forme de graphiques, schémas, diagrammes (comme des diagrammes de flux, des organigrammes, des graphiques Gantt, etc.) dans un format simple et lisible.
Un beau schéma vaut mille mots de markdown.
Mermaid s'intègre directement avec des plateformes et outils de documentation comme GitHub, GitLab, Docusaurus, MkDocs, ou Jekyll. Ainsi on peut inclure des diagrammes légers dans des fichiers README ou des wikis sans avoir besoin de les exporter en images.... images qui seront souvent non modifiables par les autres personnes participant à un projet car elles n'auront pas les sources tandis qu'avec Mermaid la source de l'image sera dans le document.
Le résultat étant généré à la volée en SVG (vectoriel) il s'adaptera à la résolution sans difficulté ainsi qu'à la préférence de thème, clair ou sombre (dark mode).
La syntaxe de mermaid est assez basique, mais efficace.
graph TD;
A-->B;
A-->C;
B-->D;
C-->D;
La première ligne définit le type de diagramme et les suivantes la logique que l'on souhaite écrire. Remarquez qu'il n'y a pas d'instruction de positionnement de bloc, on s'attache juste à décrire les relations, et la bibliothèque JavaScript se charge du reste en transformant ce texte en un beau schéma.
On peut même générer des flux Git. 🤓
gitGraph
commit
commit
branch develop
checkout develop
commit
commit
checkout main
merge develop
commit
commit
Voici un diagramme de séquence bien complexe à dessiner, et pourtant simple à écrire en texte.
sequenceDiagram
Gitlab->>Gitlab: reçoit un Push sur main
Gitlab->>Runner: Lance un runner
Runner->>Serveur: Connexion SSH
Runner->>Serveur: Envoie les instructions shell du job
Serveur-->>Gitlab: Connexion SSH vers Gitlab
Serveur-->>Gitlab: git pull
Gitlab-->>Serveur: Fichiers
Runner->>Gitlab: Retourne les logs
Note over Runner, Serveur: Fin
Voici le rendu sur GitHub avec en bonus quelques boutons pour zoomer, se déplacer, copier, etc.
Les deux plateformes les plus répandues de versionnement de code source supportent nativement Mermaid, ce qui est un grand avantage pour documenter vos projets, vos fichiers README.md
de manière compréhensible et remplacer de longues explications de texte par des schémas limpides.
La documentation de Mermaid est très bien conçue et vous permet rapidement de cerner tout ce que permet la bibliothèque
Pour faciliter les opérations, des éditeurs en ligne très bien conçus vous permettent de partir de modèles et de vous adapter progressivement à la syntaxe avec un aperçu du résultat.
Il permet aussi le partage par URL c'est-à-dire d'encoder le contenu dans l'adresse et de la diffuser pour retrouver le schéma d'origine, par exemple pour le résultat ci-dessus :
https://mermaid.live/edit#pako:eNo1T8tOxDAM_BUr5-4P9IDUBwcQW0A9tnuwErON2CTFSYBV0w_iO_gxslvWkqXxaGZsL0I6RaIUbyf3JSfkMFrIVQ1d_A4H2O3ukpyIc8OJPChn7e9PBmThsX_uEtSboR6ql4erHpqNaYd5mvfnShlttyC8QO0DU7qJmmXZI2ts6wI88SdFhv71aV3_z7gGJibvXWSZ9_qAQX9E8gnuh6btDiAKYYgNapXfWC6-UYSJDI2izFAhv49itGvWYQyuP1spysCRCsEuHqfbEGeFgVqNR0azkesfwYJfFw
Génère des rendus en ASCII art (ou dans un Terminal).
$ cat test.mermaid
graph LR
A --> B & C
B --> C & D
D --> C
$ mermaid-ascii --file test.mermaid
+---+ +---+ +---+
| | | | | |
| A |---->| B |---->| D |
| | | | | |
+---+ +---+ +---+
| | |
| | |
| | |
| | |
| v |
| +---+ |
| | | |
------->| C |<-------
| |
+---+
Mermaid est un projet open source, ce qui signifie qu'il est possible de l'étendre ou de le modifier pour des besoins spécifiques si nécessaire.
Enfin, on peut avoir accès à des thèmes de couleur
Bref, c'est rudement pratique, on le prend en main facilement et vous serez fiers de pouvoir documenter vos projets, wiki, issues avec de beaux schémas compréhensibles.
Tout le monde connaît désormamis le terme "URL" pour désigner une adresse sur le web. Bien que certains navigateurs tentent de la simplifier au maximum dans la barre de navigation (et peut être un jour de l'invisibiliser), elle revêt une importance capitale pour savoir où on est, et où on va.
Première chose à savoir : URL et URN sont des sous-types d'URI.
L'URI (Uniform Resource Identifier) est le terme le plus général qui englobe à la fois URL et URN : c'est une chaîne de caractères qui identifie de manière unique une ressource. Cela peut se faire par l'emplacement de la ressource, son nom, ou les deux.
Un chemin de fichier local pourrait donner une bonne idée d'URI :
file:///C:/Documents/Dossier/image.png
Dans cet exemple :
file
(on comprend donc que la ressource est locale).://
(que l'on retrouve également dans les URLs) distingue le schéma de l'adresse, c'est une convention admise partout, mais ici nous sommes dans un cas particulier : en général les //
précèdent un nom d'hôte, nom de domaine, adresse IP or ici nous sommes en local donc il n'y en a pas, c'est pourquoi on peut voir directement trois slash à la suite ///
./C:/Users/Utilisateur/Documents/monfichier.txt
c'est-à-dire à peu près un chemin que vous pouvez écrire de manière traditionnelle sur un système d'exploitation Windows.L'URN (Uniform Resource Name) est un type d'URI, identifiant une ressource par son nom dans un espace de noms particulier. Ici on ne spécifie pas comment accéder à la ressource (pas de protocole par exemple) mais seulement son identité, qui en théorie devrait être unique et persistante, indépendemment d'un endroit de stockage (contrairement à une URL).
Par exemple un numéro d'ISBN (International Standard Book Number) qui identifie de manière unique un livre peut être écrite sous forme d'URN.
urn:isbn:978-2-212-67683-9
On peut également écrire un numéro de téléphone.
urn:tel:+33123456789
Ou encore un UUID (Universally Unique IDentifier) qui est très utilisé en développement, ou pour donner des identifiants à des ressources matérielles.
urn:uuid:123e4567-e89b-12d3-a456-426614174000
Dans ces exemples
:
urn
L'URL quant à elle sert évidemment d'adresse pour chaque page web, fichier quelconque sur Internet et plus particulièrement sur le web même si ce n'est pas le seul protocole qui l'exploite.
Par exemple on peut écrire une URL pour une adresse FTP (File Transfer Protocol) - qui d'ailleurs à une époque pouvait être reconnue et exploitée par la plupart des navigateurs :
ftp://login:password@ftp.schnapsgpt.com:21/dossier/image.png
On reconnaît les mêmes principes que précédemment avec le schéma (protocole), les sépararateurs divers :
, @
, /
et l'ajout ici d'informations d'identifications pour accéder à la ressource login:password
ainsi que le port 21
.
Dans le cas d'une URL pour le web, nous retrouverons le protocole http
ou https
(sécurisé d'une certaine façon).
https://www.alsacreations.com/article/lire/1930-URL-URI-URN-quelles-differences.html
Ces adresses apportent des avantages :
En bonus, de belles URLs sont exploitées par les robots des moteurs de recherche (coucou Googlebot) pour mieux comprendre et indexer les pages web: bien pensées elles peuvent améliorer le référencement d'un site.
L'invention de l'URL est attribuée à Tim Berners-Lee (encore lui !) l'inventeur de bien des composants essentiels du web : HTML, le premier navigateur, le premier serveur (pour lui répondre), et le protocole HTTP. Rien que ça.
Cet adressage date donc des années 1990 lorsque Tim développait ses projets au CERN à Genève. Il souhaitait pouvoir fournir un adressage des documents et autres ressources sur le réseau informatique qu'il était en train de créer. Au fur et à mesure, le concept a été amélioré par l'IETF (Internet Engineering Task Force).
Écoutez l'épisode Disséquons une URL, première partie de l'excellent Carré, Petit, Utile : Le programme radio des gens du numérique.
L'URL est un standard décrit par la RFC 1738, précisée dans le cas des liens mailto par la RFC 2368... complétée par la RFC 2396 sur les URI, puis par la RFC 3986 l'élargissant encore plus aux URI, ainsi que la RFC 8089 sur les noms locaux de fichiers, et ainsi de suite.
Nous n'avons pas fini d'entendre parler de les LLM (grands modèles de langages) et de l'Intelligence Artificielle. Outre les sytèmes en ligne (souvent payants) il est possible d'interroger un modèle local (gratuitement) par l'intermédiaire d'un peu de JavaScript.
Pour ceci nous utiliserons :
Ollama est une application disponible pour Linux, macOS, Windows qui sert d'interface de gestion de LLM. Voyons la comme une sorte de Docker qui ira piocher dans un catalogue d'images disponibles en ligne, faciles à télécharger et à exécuter en une seule instruction ou presque en précisant bien le nom du modèle souhaité.
Les commandes essentielles après avoir téléchargé et installé Ollama :
ollama list
liste les modèles déjà téléchargésollama pull <modèle>
télécharge un nouveau LLMollama run <modèle>
exécuteollama stop <modèle>
met fin à l'exécutionollama rm <modèle>
supprimePour l'occasion, nous utiliserons llama 3.2
ollama pull llama3.2
Pour préciser une autre version du modèle avec nombre de paramètres (comprenez complexité et poids) différent, on pourra par exemple indiquer ollama pull llama3.2:1b
pour 1B soit un milliard de paramètres.
Jusque-là si tout va bien, nous pouvons d'ores et déjà discuter en mode texte brut par un ollama run llama3.2
.
Cette bibliothèque nous permet d'aller interroger Ollama installé localement en définissant le modèle, le message à lui envoyer et en traitant la réponse. De manière très basique on peut se servir de console.log mais ce n'est pas très intéressant car bloquant jusqu'à obtenir la totalité de la réponse ; la promptitude du modèle dépendra aussi de la puissance de votre machine et de votre mémoire vive disponible.
À l'aide d'un environnement Node.js (déjà installé n'est-ce pas ?), nous pouvons poursuivre.
Créer un dossier quelconque 📁 pour notre projet.
Installer la dépendnace avec npm install --save ollama
(ou avec pnpm)
kiwi.mjs
pour importer ollama
et appeler chat()
afin de lancer la discussionimport ollama from 'ollama'
const response = await ollama.chat({
model: 'llama3.2',
messages: [{ role: 'user', content: 'Quelle est la recette du cake au kiwi ?' }],
})
// ⚠️ Ceci peut prendre beaucoup de temps car on attend la réponse complète
console.log(response.message.content)
Il suffira de l'exécuter en ligne de commande avec node kiwi.mjs
.
Pour streamer la réponse, c'est-à-dire la restituer au fur et à mesure de l'ajout de mots par le LLM, on peut se servir de l'alternative en activant l'option stream.
import ollama from 'ollama'
const message = { role: 'user', content: 'Quelle est la recette du cake au kiwi ?' }
const response = await ollama.chat({ model: 'llama3.2', messages: [message], stream: true })
for await (const part of response) {
process.stdout.write(part.message.content)
}
Un bon nombre d'autres paramètres et méthodes existent dans cette interface, il suffira de consulter la documentation pour les découvrir.
C'est possible ! Une page interrogera le modèle via ollama :
Pour transformer le tout en une petite application web utilisable dans le navigateur...
express
npm install --save express
Ce qui va permettre de construire un couple client/serveur minimaliste avec deux fichiers :
Les explications sont fournies par des commentaires dans le code source suivant, à vous de jouer en vous l'appropriant.
server.mjs
import express from 'express';
import ollama from 'ollama';
const app = express(); // Instance d'Express
const port = 3000; // Port à l'écoute
app.use(express.json());
// On sert le dossier public en statique, dans lequel on place notre page index.html
app.use(express.static('public'));
// On accepte les requêtes POST vers /chat
app.post('/chat', async (req, res) => {
// Notre message sera envoyé dans le corps de la requête (body)
const message = { role: 'user', content: req.body.content };
// La réponse provenant du LLM est une promesse
const response = await ollama.chat({ model: 'llama3.2', messages: [message], stream: true });
// La réponse envoyée à la page web dispose d'en-têtes HTTP
//... permettant de faire persister la connexion
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
// Pour toute portion de réponse reçue, on la stream
for await (const part of response) {
res.write(`data: ${JSON.stringify(part.message)}\n\n`);
}
res.end();
});
// On écoute sur le port configuré
app.listen(port, () => {
console.log(`Serveur en écoute : http://localhost:${port}`);
});
public/index.html
correspondante :<!doctype html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>KiwIA</title>
<style>
/* à personnaliser selon vos envies */
body { font-family: system-ui; background: #222; color: #fff; padding: 2rem; }
#chat, [type=submit] { padding: 1rem; border-radius: 0.5rem; border: 1px solid #ccc; margin: 1rem 0; background: inherit; color: inherit; }
#chat { min-width: 20rem; font-size: inherit; }
#reponse { text-align: left; line-height: 2; }
</style>
</head>
<body>
<h1>Chat KiwIA 🥝</h1>
<form>
<input type="text" id="chat" placeholder="Votre message...">
<button type="submit">Envoyer</button>
</form>
<div id="reponse"></div>
<script>
const form = document.querySelector('form');
const input = document.querySelector('#chat');
const resultat = document.querySelector('#reponse');
// À la validation du formulaire
form.addEventListener('submit', async (e) => {
e.preventDefault();
// On récupère le contenu du message
const content = input.value.trim();
if (!content) return; // Si c'est vide on arrête là
// Petit message d'attente
resultat.textContent = 'Un instant et je suis à vous...';
// On vide le champ d'entrée
input.value = '';
try {
// Requête asynchrone en POST vers /chat
const response = await fetch('/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
// Corps de la requête en JSON
body: JSON.stringify({ content }),
});
resultat.textContent = '';
// On instancie une interface ReadableStream
const reader = response.body.getReader();
const decoder = new TextDecoder();
// Tant qu'on a du contenu...
while (true) {
const { done, value } = await reader.read(); // On lit
if (done) break;
const lines = decoder.decode(value).split('\n');
// On itère sur la réponse reçue pour alimenter le résultat
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(6));
resultat.textContent += data.content;
}
}
}
} catch (error) {
console.error('Error:', error);
resultat.textContent = 'Une erreur est survenue.';
}
});
</script>
</body>
</html>
node server.mjs
et on consulte l'adresse indiquée dans le navigateur pour atteindre la page HTML qui affiche le formulaire.On peut perfectionner le rendu en mettant en page le message renvoyé sous forme de markdown (mais cela dépend du modèle interrogé) plutôt que de l'afficher en texte brut.
On peut voir que construire une application web sollicitant un LLM est tout à fait envisageable avec les technologies d'aujourd'hui et les standards déjà en place (HTML/CSS, JavaScript, fetch, Streams API, etc). À partir de là tout est possible pour imaginer élaborer des interfaces qui vont dialoguer en texte clair ou par d'autres moyens plus subtiles, et réagir en conséquence. Vous pouvez aussi briefer votre LLM en amont et lui donner des instructions ou un contexte de réponse, voire en construire un nouveau (avec l'instruction FROM de Ollama).
Pour pousser l'horizon encore plus loin, faire tourner des LLM dans le navigateur lui-même est possible grâce à WebGPU (voir une démo ici : Qwen-2.5 on WebGPU sur Huggingface) avec une performance tout à fait honorable.
Les concepteurs de navigateurs préparent des interfaces aisées d'accès en JavaScript pour interroger un modèle local, tels que Google avec l'IA Gemini intégrée dans Chrome. Nous n'avons pas fini d'en entendre parler.
Retrouvez l'intégralité de ce tutoriel en ligne sur Alsacreations.com
Les variables CSS ont révolutionné la manière de gérer les styles dans le développement web.
Si vous en avez assez de répéter les mêmes valeurs CSS partout dans votre code, alors cet article est fait pour vous !
Vous allez découvrir ici comment les utiliser efficacement, pourquoi elles sont si pratiques et comment elles se comparent aux solutions comme Sass et LESS.
Allons-y !
Une variable CSS (aussi appelée "propriété personnalisée") est un moyen de stocker des valeurs réutilisables dans votre feuille de style.
Imaginez-la comme une boîte où vous pouvez ranger une couleur, une taille de police ou n'importe quelle autre valeur CSS, puis la réutiliser où vous le souhaitez.
Les variables CSS sont généralement définies au sein du sélecteur :root
.
Ce sélecteur représente le niveau le plus élevé de votre document et les variables ainsi définies seront accessibles partout dans votre fichier CSS.
:root {
--main-color: #3498db;
--font-size: 16px;
}
Dans cet exemple, deux variables sont créées : --main-color
et --font-size
.
Elles sont définies respectivement avec une couleur et une taille de police.
Pour utiliser une variable, vous devez appeler la fonction var()
.
Celle-ci prend le nom de la variable en argument et l'injecte dans les propriétés CSS que vous souhaitez modifier.
body {
background-color: var(--main-color);
font-size: var(--font-size);
}
Ici, var(--main-color)
applique la couleur principale définie à l'arrière-plan du body, tandis que var(--font-size)
ajuste la taille du texte.
Vous pouvez également fournir une valeur de secours (fallback) à une variable CSS.
C'est utile au cas où la variable ne serait pas définie ou si le navigateur ne la supportait pas.
body {
color: var(--text-color, #333);
}
Dans cet exemple, si la variable --text-color
n'est pas définie, la couleur par défaut #333
sera appliquée.
Les variables CSS peuvent être réutilisées sur plusieurs éléments.
h1 {
color: var(--main-color);
}
p {
border-color: var(--main-color);
}
En utilisant la même variable sur différents éléments (comme h1
et p
), vous assurez une cohérence visuelle tout en simplifiant les modifications futures.
Nous reviendrons justement sur les raisons d'utiliser les variables CSS dans la section suivante.
L'un des aspects les plus puissants des variables CSS est qu'elles peuvent être redéfinies localement pour des sections spécifiques de votre page.
Vous n'êtes pas limité à une seule valeur globale.
Par exemple, si vous souhaitez changer la couleur principale dans une certaine section de votre site, vous pouvez le faire en redéfinissant la variable uniquement pour cette partie.
.section-1 {
--main-color: #d32f2c;
}
.section-2 {
--main-color: #3fcef2;
}
Ensuite, dans vos styles, vous utilisez toujours var(--main-color)
comme d'habitude, mais la valeur s'adaptera en fonction de la section où elle est utilisée :
.section-1 h1 {
color: var(--main-color); /* Couleur rouge */
}
.section-2 h1 {
color: var(--main-color); /* Couleur cyan */
}
Grâce à cette flexibilité, vous pouvez facilement adapter le design de différentes sections de votre page sans devoir réécrire tout votre code CSS.
Maintenant que vous savez comment créer et utiliser les variables CSS, vous en voyez tout le potentiel et la puissance.
Je tiens toutefois à souligner pourquoi, selon moi, elles sont indispensables dans la création de styles CSS.
Plutôt que de répéter les mêmes valeurs partout dans votre code, les variables CSS vous permettent de centraliser des éléments récurrents comme les couleurs, les polices ou les espacements.
Ainsi, si vous devez modifier une valeur, vous n'avez qu'à la changer à un seul endroit, et l'ensemble de votre site sera mis à jour.
Un vrai gain de temps, surtout pour les projets de grande envergure.
Les variables CSS améliorent considérablement la lisibilité et la maintenance de votre code.
Par exemple, au lieu de chercher chaque occurrence d'une couleur pour la changer, vous modifiez simplement la valeur de la variable.
C'est également très pratique si vous travaillez en équipe : chacun peut rapidement comprendre et ajuster les styles sans risquer d'oublier une valeur cachée quelque part dans le CSS.
Si vous souhaitez proposer plusieurs thèmes de couleur ou de design pour un site, les variables CSS sont parfaites pour cela.
Vous pouvez déclarer différentes valeurs pour vos variables en fonction du thème sélectionné, et tout bascule automatiquement sans effort supplémentaire, ouvrant ainsi la porte à des interfaces utilisateur facilement personnalisables.
J'aborderai ce sujet plus en profondeur un peu plus loin dans l'article.
Les variables CSS sont compatibles avec la plupart des navigateurs modernes, ce qui signifie que vous pouvez les utiliser dès maintenant dans vos projets sans vous soucier des problèmes de compatibilité.
Les variables CSS sont un atout majeur pour tout développeur souhaitant optimiser son flux de travail, réduire les erreurs et créer des designs évolutifs.
Elles apportent une réponse simple et élégante à la complexité croissante des feuilles de style dans les projets modernes.
Les variables CSS et les préprocesseurs comme Sass et LESS permettent tous deux de gérer des valeurs réutilisables dans vos styles, mais ils présentent des différences majeures.
Les variables CSS sont directement comprises par les navigateurs modernes.
Pas besoin de compiler quoi que ce soit : vous les déclarez dans votre CSS et elles fonctionnent immédiatement.
En revanche, avec Sass et LESS, il faut compiler le code pour obtenir un fichier CSS utilisable.
Les variables CSS peuvent également être modifiées en temps réel, par exemple via JavaScript, ce qui les rend parfaites pour des thèmes dynamiques ou des interactions utilisateur.
document.documentElement.style.setProperty('--primary-color', '#e74c3c');
En comparaison, avec Sass et LESS, les variables sont statiques : une fois le code compilé, vous ne pouvez plus modifier ces valeurs sans recompiler les fichiers.
Je vous conseille d'utiliser uniquement un fichier CSS avec des variables CSS pour des pages statiques ou de petite envergure, car elles suffisent amplement dans ces cas-là.
Mais pour des projets plus vastes et structurés, l'utilisation d'un préprocesseur reste souvent plus intéressante.
Heureusement, il est possible de combiner les deux approches en utilisant des variables CSS au sein même de vos fichiers Sass ou LESS, ce qui permet de profiter du meilleur des deux mondes : la flexibilité des variables CSS et la puissance des préprocesseurs.
Que vous souhaitiez implémenter un mode clair et un mode sombre, ou bien proposer plusieurs variations de couleurs, les variables CSS vous permettent de gérer cela efficacement sans réécrire l'intégralité de votre feuille de style.
Tout commence par la déclaration de vos variables globales dans le sélecteur :root
.
:root {
--primary-color: #3498db;
--background-color: #ffffff;
--text-color: #333333;
}
Ces variables peuvent ensuite être utilisées dans vos styles réguliers.
body {
background-color: var(--background-color);
color: var(--text-color);
}
button {
background-color: var(--primary-color);
}
Pour appliquer un thème alternatif, comme un mode sombre, il suffit de redéfinir les valeurs des variables CSS dans une classe spécifique, par exemple .dark-theme.
.dark-theme {
--primary-color: #e74c3c;
--background-color: #2c3e50;
--text-color: #ecf0f1;
}
Ensuite, vous pouvez appliquer cette classe au niveau du body
ou d'une autre balise englobante dans votre HTML, et tous les styles qui utilisent ces variables seront mis à jour automatiquement, sans avoir à dupliquer votre code CSS.
body.dark-theme {
background-color: var(--background-color);
color: var(--text-color);
}
Vous pouvez créer autant de thèmes que nécessaire, en modifiant simplement les valeurs de ces variables CSS pour chaque classe de thème.
Par exemple, si vous souhaitez ajouter un thème clair, vous définissez simplement une autre classe avec des valeurs différentes :
.light-theme {
--primary-color: #3498db;
--background-color: #ffffff;
--text-color: #333333;
}
Cela vous permet de basculer entre plusieurs thèmes très facilement, uniquement en changeant la classe appliquée sur l'élément racine.
Pour aller plus loin dans la gestion des thèmes, vous pouvez consulter cet article sur les modes d'apparence Light mode et Dark mode.
L'utilisation des variables CSS est un excellent moyen de rendre votre code plus propre, maintenable et flexible.
Toutefois, pour en tirer le meilleur parti, il est essentiel de suivre quelques bonnes pratiques.
Voici quelques conseils pour utiliser les variables CSS de manière optimale.
:root
Comme mentionné précédemment, pour maximiser la portée de vos variables, il est conseillé de les déclarer dans le sélecteur :root
.
Ainsi, elles seront accessibles dans tout le document et pourront être réutilisées partout.
:root {
--primary-color: #3498db;
--font-size-base: 16px;
}
Choisir des noms de variables clairs et explicites est essentiel pour rendre votre code facile à comprendre et à maintenir.
Par exemple, préférez --primary-color
plutôt que --color1
, ce qui permet à quiconque lit votre code de comprendre immédiatement à quoi correspond la variable.
:root {
--primary-color: #3498db;
--secondary-color: #2ecc71;
--font-size-large: 24px;
}
Des noms explicites facilitent également les ajustements futurs.
Les variables CSS sont particulièrement utiles pour les valeurs que vous utilisez fréquemment, comme les couleurs, les tailles de police, ou les espacements.
:root {
--spacing-unit: 16px;
}
.container {
padding: var(--spacing-unit);
}
.card {
margin-bottom: var(--spacing-unit);
}
Pour une meilleure organisation, regroupez vos variables par type, comme les couleurs, les tailles de police ou les espacements. Cela vous permettra de structurer votre fichier CSS de manière plus lisible et d'y revenir plus facilement pour faire des modifications.
:root {
/* Couleurs */
--primary-color: #3498db;
--secondary-color: #2ecc71;
/* Tailles de police */
--font-size-base: 16px;
--font-size-large: 24px;
/* Espacements */
--spacing-small: 8px;
--spacing-medium: 16px;
--spacing-large: 32px;
}
Il est toujours recommandé de fournir une valeur de secours dans vos déclarations var()
, surtout si vous travaillez avec des variables CSS qui pourraient ne pas être définies dans certains contextes.
button {
background-color: var(--primary-color, #3498db);
}
Dans cet exemple, si --primary-color
n'est pas définie, la valeur #3498db
sera utilisée à la place.
Bien que les variables CSS soient très utiles, il est important de ne pas en abuser.
Évitez de créer une variable pour chaque petite valeur.
Concentrez-vous sur les éléments qui sont susceptibles de changer ou qui sont utilisés fréquemment à travers votre code.
Trop de variables peuvent rendre votre fichier CSS difficile à gérer.
Les variables CSS ont véritablement transformé la manière de gérer les styles dans le développement web moderne.
Elles apportent une flexibilité et une simplicité inégalées, permettant de centraliser et de modifier des valeurs globales sans dupliquer le code.
Que ce soit pour la gestion des couleurs, des tailles de police, ou même pour des thèmes dynamiques, elles offrent un moyen puissant de rendre votre CSS plus maintenable.
Retrouvez l'intégralité de ce tutoriel en ligne sur Alsacreations.com
Ah les carrousels… Fonctionnalité souvent demandée par les clients, car c’est dynamique, ça bouge, c’est fun etc. Mais en avez-vous réellement besoin pour votre site ?
De manière générale, la plupart des carrousels contiennent plusieurs diapositives (avec des images, vidéos ou autre) qui défilent lorsqu’on clique sur une flèche de navigation ou une pagination.
Cela permet aux utilisateurs et utilisatrices de maximiser la densité de l'information sans avoir à faire défiler les pages. Pratique non ? Oui, mais… Des études ont montré que la plupart des personnes ont tendance à ignorer les carrousels. Nous allons voir pourquoi.
Selon les recherches d'Erik Runyon, directeur technique des communications marketing à l'Université de Notre Dame, les carrousels affichent un taux de clic relativement faible. En ayant fait l’étude sur le site même de l’Université avec un carrousel statique (qui ne défile pas automatiquement), 84 % des clics ont été effectués sur les articles en position 1, le reste étant réparti assez équitablement entre les quatre autres (~4 % chacun).
En revanche, sur les carrousels automatiques, le taux de clic est plus élevé que sur les autres diapositives.
Une autre étude a été réalisée par Rashika Tasnim Keya en janvier 2022, étudiante à l’Université de Design de ICT, proposant aux internautes deux même sites e-commerce, mais qui affichent des produits différemments ; le premier site sans carousel, et le deuxième site avec carrousels pour faire défiler les nombreux produits.
Selon le graphique ci-dessus, une des questions posées aux internautes était de savoir s’ils trouvaient facilement les produits sur le site internet. Le site sans carrousel à remporté le plus de votes sur la réponse “Fortement d’accord”, alors que le site avec carrousels a remporté la réponse “Modéré”.
L’analyse a montré que la plupart des utilisateurs ont déclaré que l'interface sans carrousel était plus compréhensible que l'interface avec les carrousels. En effet, l’interface était plus claire, et leur recherche d'articles ou de produits a été plus précise et plus rapide que sur le site avec les carrousels.
On peut noter que l'expérience utilisateur est significativement différente selon l’interface avec ou sans carrousel, et que les personnes ont tendance à réaliser leur objectif plus facilement avec des composants simples d'utilisation.
Si nous prenons l’exemple d’un site e-commerce, l'objectif de l'internaute est de trouver des informations sur les produits, de comparer différentes gammes de produits et de prendre une décision d'achat. Cela signifie que l'utilité du carrousel dépend du contexte de l'utilisateur. L'utilisateur est donc plus susceptible d’utiliser les carrousels.
Mais étant donné que les utilisateurs sont moins enclins à cliquer sur les flèches pour afficher la diapositive suivante, il peut être plus efficace de changer automatiquement de diapositive après un certain temps ou de leur montrer des diapositives d'une page différente lorsqu'ils reviennent sur votre site. À noter cependant qu’au niveau de l’accessibilité, une diapositive automatique doit comporter obligatoirement un système de mise en pause et de relance du défilement.
Selon les règles du cerveau de John Medina, la vision prime sur les autres sens. Toutefois, un grand nombre de diapositives ne constitue pas un bon carrousel, car une injection excessive d'images visuelles entraîne une charge cognitive. Par conséquent, cinq diapositives ou moins, adaptées à la mémoire à court terme, amélioreront l'expérience de l'utilisateur. Il faut également faire attention à placer les flèches de manière intuitive. Les utilisateurs préfèrent naviguer d'une manière qui leur est familière en raison de leur expérience passée.
En conclusion, bien que les carrousels puissent sembler attrayants et dynamiques, leur efficacité est souvent remise en question. Les études montrent que ces éléments interactifs captent rarement l'attention des utilisateurs et peuvent même nuire à l'expérience utilisateur en compliquant la navigation et en augmentant la charge cognitive. De plus, les défis techniques liés à l'accessibilité et à la compatibilité multiplateforme rendent leur mise en œuvre complexe.
Ainsi, il est essentiel de bien réfléchir à leur utilité réelle dans le contexte spécifique de votre site. Si vous décidez tout de même d'opter pour un carrousel, il est crucial de l'utiliser de manière réfléchie, en prenant en compte les meilleures pratiques pour garantir une expérience utilisateur fluide et accessible.
Le mode sombre (dark mode) consiste à adopter des fonds de couleur sombre sur les interfaces utilisateur. Selon Wikipedia, ce mode serait utile pour réduire la fatigue visuelle et la consommation en énergie par rapport aux autres modes d'affichage.
Un très grand nombre de personnes en sont adeptes sur leur smartphone, mais également sur leur ordinateur ou tablette. Proposer aux utilisateurs la possibilité d'activer cette fonctionnalité est donc conseillée.
Le site de Wikipedia lui-même vient enfin d'annoncer le support du Dark mode pour ses pages de contenus, après de nombreuses années d'attente de ses lecteurs et lectrices.
Il est possible d'adapter les thèmes de couleur de son environnement sur l'ensemble de son système d'exploitation, par exemple pour Windows et MacOS :
Il est également possible de n'appliquer ce choix qu'au niveau du navigateur web, par exemple :
Enfin, en tant que développeur, les Devtools (inspecteur d'élément) de Google Chrome permettent d'émuler le mode sombre / clair via la combinaison de touche "ctrl+shift+p" et chercher "rendering".
Les Devtools de Firefox permettent aussi de tester via onglet Inspecteur, dans les boutons-icônes en haut des styles (cf. capture ci-dessous).
Les couleurs systèmes représentent une palette de couleurs web proposées par défaut pour les différentes éléments d'interface : couleur de texte, couleur de fond, couleur de lien, couleur de bordure d'un bouton, etc.
Ces couleurs sont accessibles (si employées correctement), conformes aux modes de contraste élevé et prêtes à s'adapter automatiquement au mode d'apparence (light et dark mode).
Il est même parfaitement possible de les utiliser sous forme de mot-clés en CSS comme les autres couleurs nommées :
html {
color: CanvasText;
background-color: Canvas;
}
color-scheme
Cette propriété indique au navigateur quel mode d'affichage est souhaité pour un élément donné (light ou dark).
Dans l'exemple ci-dessous, on indique que l'élément .formulaire
doit obligatoirement s'afficher en mode sombre. Le navigateur ajuste en conséquence les couleurs système, mais aussi les contrôles de formulaires et les scrollbars éventuelles sur cet élément.
.formulaire {
color-scheme: dark;
}
color-scheme
color-scheme: normal;
: Le navigateur n'adapte pas les couleurs (valeur par défaut).color-scheme: light;
: Le navigateur doit afficher l'élément en mode light.color-scheme: dark;
: Le navigateur doit afficher l'élément en mode dark.color-scheme: light dark;
: Le navigateur doit afficher l'élément selon les préférences utilisateur.<h1>Tiens, mais quelle est ma couleur ?</h1>
:root {
color-scheme: light dark;
}
Dans cet exemple, j'indique que l'ensemble du document (:root
) doit se conformer aux préférences utilisateur. Sans indiquer de couleurs particulières, ce sont les couleurs système qui s'appliquent et sont autorisées à s'adapter à ces préférences : en mode clair, le document a un fond clair et le titre <h1>
est de couleur sombre; et inversement en mode sombre.
Compatibilité de color-scheme
: https://caniuse.com/mdn-css_properties_color-scheme
Conclusion : appliquer color-scheme: light dark;
est un bon début pour toute page qui souhaite s'adapter au mode d'apparence, mais cette fonctionnalité n'agit que sur les couleurs système (et de l'interface du navigateur) donc c'est plutôt limité en terme de design.
@media (prefers-color-scheme: …)
La requête prefers-color-scheme
est un critère des spécifications Media Query permettant de détecter le Mode d'apparence de l'utilisateur (celui défini via ses préférences système et/ou son navigateur).
Cette fonctionnalité permet d'aller un cran plus loin que color-scheme
en appliquant des palettes de couleurs totalement personnalisées et adaptées au choix (au système, plus précisément) de nos visiteurs.
Nous détectons si l'OS ou le navigateur est configuré en mode sombre de cette manière :
@media (prefers-color-scheme: dark) {
/* ici des styles prévus pour Dark Mode */
}
Concrètement, cela pourrait se traduire ainsi pour nos pages web :
:root {
--color-primary: pink;
--color-primary--darkmode: hotpink;
@media (prefers-color-scheme: light) {
--color-base: var(--color-primary);
}
@media (prefers-color-scheme: dark) {
--color-base: var(--color-primary--darkmode);
}
}
Dans l'extrait ci-dessus :
--color-primary
et --color-primary--darkmode
--color-base
vaut --color-primary
--color-base
vaut --color-primary--darkmode
Nos différents composants bénéficieront par conséquent d'une couleur --color-base
dont la valeur s'adaptera automatiquement à la configuration du système, par exemple :
.card {
color: var(--color-base);
}
La vidéo ci-dessous illustre la mise en oeuvre de la media query prefers-color-scheme
:
Compatibilité de @media (prefers-color-scheme: …)
: https://caniuse.com/prefers-color-scheme
light-dark()
Cette valeur-fonction accepte deux paramètres de couleurs et renvoie l'une ou l'autre selon les préférences utilisateur (OS ou navigateur).
.card {
color: light-dark(pink, hotpink);
}
Autre exemple :
:root {
color-scheme: light dark;
--primary-color: light-dark(#fff, hotpink);
--primary-background: light-dark(hotpink, #fff);
}
body {
color: var(--primary-color);
background-color: var(--primary-background);
}
Compatibilité de light-dark()
: https://caniuse.com/mdn-css_types_color_light-dark
Un Switch est un composant de type "interrupteur". Il va au-delà de ce que proposent les spécifications CSS évoquées jusqu'alors, puisqu'il permet à l'internaute de choisir le thème de couleur qu'il préfère lorsqu'il visite votre site en particulier, indépendamment de ses préférences utilisateur dans son système d'exploitation.
Un design totalement inclusif devrait proposer ce genre de fonctionnalités pour tenter d'atteindre la meilleure expérience possible pour tous les usagers de votre site.
Les contraintes et le processus ne sont pas si simples :
Voici un exemple de Switch accessible sur Codepen : https://codepen.io/alsacreations/pen/ExBPExE
Le déroulé des événements lors du clic/touch sur le bouton est celui-ci :
data-theme-preference
sur <html>
(valeur "light"
ou "dark"
).aria-pressed
du bouton à "pressed"
.Le test pour connaître le choix de l'utilisateur porte sur l'attribut data-theme-preference
, on s'en servira ainsi côté CSS :
.card {
color: pink;
}
[data-theme-preference="dark"] .card {
color: hotpink;
}
Ou en syntaxe imbriquée :
.card {
color: pink;
[data-theme-preference="dark"] & {
color: hotpink;
}
}
Offrir une expérience d'environnement sombre et clair est de plus en plus courant et recommandé, rien que pour le confort de tous les usagers.
Cependant, techniquement, rien n'est vraiment magique car les contraintes sont nombreuses et les solutions sont toutes partielles.
Au final, le meilleur compromis semble être un Switch , assemblage de spécifications CSS et de JavaScript pour l'accessibilité et la mémorisation des choix utilisateur.
Les années passent, Pixels & Bretzels 🥨 se renouvelle (qui d'entre vous utilise encore le nom « WdStr » ?), mais garde le même cap : continuer de vous proposer régulièrement des conférences et rencontres, que ce soit dans votre cœur de métier ou pour vous faire prendre conscience de nouvelles problématiques.
Pour fêter cette 100e édition, Pixels & Bretzels pose ses octets chez Alsacréations. Il y aura des quiz et des lots à vous faire gagner ! ✨
Même si cela fait longtemps que vous n'êtes pas venu·e à l'un de nos événements, rejoignez-nous.
Via le site HelloAsso avec le lien suivant : trop fort je m'inscris à Pixels & Bretzels 100e édition
3 bonnes raisons de nous rejoindre :
Le mardi 19 mars 2024 à 19h chez
Alsacréations 10 place du Temple Neuf 67000 Strasbourg
Nous avons hâte de vous y retrouver ✌️
L'évènement est généreusement soutenu par de super sponsors :
L'arrivée de l'attribut et de l'API HTML popover facilite la gestion de tous les éléments qui doivent s'afficher au-dessus du contenu d'une page Web (fenêtre modale, tooltip, menu déroulant, etc.).
Cette nouvelle fonctionnalité vient s'ajouter à certaines déjà existantes, au point où l'on ne sait plus forcément où donner de la tête entre les éléments dits "Dialog", "Modal", "Popover" ou autres "Overlay".
Tentons de rassembler toutes les caractéristiques globales et techniques de l'ensemble de ces notions afin d'y voir plus clair.
Cet article a pour objectif de poser les bases et de définir les différents termes en jeu. Dans un deuxième temps un article détaillé sera consacré à <dialog>
et un autre à popover
.
Pour bien se rendre compte du nombre de fonctionnalités qui intéragissent, voici d'emblée un tableau récapitulatif des éléments que nous allons couvrir…
popover |
<dialog> (modal) |
<dialog> (non modal) |
<div role=dialog> |
|
---|---|---|---|---|
Overlay | oui | oui | oui | oui |
Modal | non | oui | non | non |
Inert | non | oui | non | non |
Top layer | oui | oui | non | non |
Focus trap | non | oui | oui | non |
Dismissible | echap ou manuel |
echap |
echap |
non |
Commentaires | Exclusif : ouvrir un popover ferme le précédent | Masqué par défaut, visible avec l'attribut open |
Masqué par défaut, visible avec l'attribut open |
Les différentes notions de ce tableau sont détaillées tout au long de cet article de synthèse : popover
, <dialog>
, overlay, modal, inert, top layer, focus trap, dismissible.
Le terme "overlay" ne représente pas véritablement un composant mais une caractéristique. Un élément "overlay" est simplement un élément qui se place au-dessus d'autres éléments dans la page.
Tous les éléments au sein de cette liste sont des overlays :
Dans cette même famille nous croiserons les termes de "pop-up" (nouvelle fenêtre au-dessus de la fenêtre actuelle du navigateur) et "pop-in" (élément au-dessus des autres éléments de la page, synonyme de "overlay", donc).
Selon les spécifications HTML, le terme "modal" ne désigne pas un composant en tant que tel, mais un ensemble de caractéristiques : un élément en overlay peut être "modal" ou "non-modal".
Un élément Modal est le seul à être interactif dans un document, il requiert toute l'attention de l'usager, tout le reste de la page doit être rendu inerte (inert
) car aucune autre action ne doit être possible mis à part réagir à cet élément (fermer, accepter, refuser, abandonner, etc.).
Par exemple, un bandeau de recueil de données personnelles doit être consenti par l'utilisateur (obligation légale RGPD) sans quoi il ne devrait pas pouvoir accéder au site. C'est donc un élément de type Modal. Un autre exemple pourrait être celui d'une une modale "login/password" si l'accès au site nécessite une authentification.
Un élément non-Modal, à l'inverse, laisse la possibilité à l'utilisateur de continuer à interagir avec la page. C'est généralement le cas des éléments tooltip, menu déroulant, dropdown, datepicker, notification, alerte, bandeau cookie.
inert
Un contenu inerte est un contenu avec lequel les utilisatrices et utilisateurs ne peuvent pas interagir. Il n'existe que visuellement, mais ne peut être atteint, cliqué, défilé ni consulté au moyen de technologies d'assistance.
Différentes techniques permettent de rendre une partie de page inerte :
inert
,aria-hidden=true
et de tabindex="-1"
,<dialog>
(voir cette section pour les détails).Top layer est une couche d'empilement au dessus de l'ensemble du document (existe depuis juin 2023). Cette couche n'est pas concernée par z-index
ni par overflow: hidden
. Les éléments s'empilent dans l'ordre d'apparition dans le Top layer.
Certains éléments sont placés automatiquement par défaut dans le top layer :
<dialog>
(sauf exceptions)popover
(sauf exceptions)::backdrop
Ce pseudo-élement s'applique uniquement aux objets placés dans le top layer. Il permet d'apporter des styles visuels "sous" un overlay en obscurcissant la page par exemple.
/* Cette ombre n'est affichée que si l'élément */
/* est dans le Top layer */
dialog::backdrop {
background: rgba(0,0,0,0.3);
}
Certains élément nécessitent que la navigation au clavier (touches Tab
ou Shift + Tab
) ne quitte pas le périmètre du composant : en atteignant le dernier élément focusable, on retourne en boucle au premier élément focusable au sein du composant.
C'est le comportement attendu et souhaité en accessibilité lorsque le composant est "modal" et que tout le reste du document est inerte.
Le focus trap doit toutefois être temporaire et disparaître quand le composant est quitté ou rejeté (dismissed).
Certains overlay nécessitent de pouvoir être fermés de manière automatique (via touche echap
ou au focus à l'extérieur du composant) et/ou de façon manuelle (en validant un bouton de soumission par exemple). Ce comportement se nomme "Dismissible".
Un Dialog est un composant de type overlay destiné à apporter une information à un usager et recueillir une interaction de sa part. Les spécifications proposent une API HTMLDialogElement
ainsi qu'un élément natif <dialog>
qui peut être "Modal" ou "non-Modal".
Le déclenchement se fait via l'API JavaScript :
show()
: Ouvre le dialog en non-ModalshowModal()
: Ouvre le dialog en Modalclose()
: Ferme le dialog et renvoie en option une valeurVoici un exemple concret :
<dialog id="modale">
<p>Une modale de type Modal</p>
</dialog>
const modal = document.querySelector("#modale");
modal.showModal();
Les particularités de l'élément <dialog>
sont :
role=dialog
par défautopen
aria-label
ou aria-labelledby
)echap
Selon son état (Modal ou non-Modal), d'autres caractéristiques s'ajoutent à la liste précédente.
Si le <dialog>
est "Modal" :
::backdrop
est applicable,inert
.Si le <dialog>
est "non-Modal" :
z-index
s'appliquent),::backdrop
ne s'applique pas,Les spécifications d'accessibilité ARIA proposent un attribut role="dialog"
pouvant être appliqué sur n'importe quel élément HTML autre que <dialog>
, mais sachez que cela implique par défaut :
aria-modal="true"
),echap
.En outre, sans API JavaScript showModal()
, l'élément <dialog>
est non-Modal :
<dialog id="dialog" open>
<p>je suis non-modal</p>
</dialog>
:modal
La pseudo-classe :modal
s'applique à tous les éléments à l'état Modal (par exemple un <dialog>
ouvert avec l'API showModal()
).
#modale:modal {
background: hotpink;
}
Un popover est un overlay composé d'un attribut popover
associé à un déclencheur :
<button popovertarget="tooltip">Ouvrir la tooltip</button>
<div popover id="tooltip">Choucroute et Saucisses de Strasbourg !</div>
Les particularités d'un élément pourvu d'un attribut popover
sont :
::backdrop
est applicable,role
spécifique ne lui est affecté. Le choix dépend du type de composant (role="alert"
par exemple),echap
par défaut,Le comportement dismissible (jetable) dépend de la valeur associée à l'attribut popover
:
<div popover=auto>
, valeur par défaut si omise) : c'est le comportement idéal pour tooltip, menu déroulant, dropdown, datepicker,<div popover=manual>
) : parfait pour des notifications ou des alertes.:popover-open
La pseudo-classe CSS :popover-open
représente un élément popover (c'est-à-dire un élément avec un attribut popover
) qui est dans l'état ouvert.
:popover-open {
/*...*/
}
Le nombre de possibilités offertes par les spécifications ne facilite pas le choix lorsqu'il s'agit d'intégrer un composant qui doit se placer au-dessus d'un contenu.
Entre les modales, les menus déroulants, les boîtes de dialogues et autres bandeaux d'alerte, il n'est guère aisé de piocher parmi <dialog>
, popover
ou une simple <div>
en position abolute dopée avec JavaScript.
Les réponses aux deux questions suivantes sont déterminantes dans votre choix final :
inert
)J'espère avoir éclairci la plupart des points d'ombres (même s'il en demeure sans doute) de ce vaste sujet des différents overlays. Pour finir, voici quelques articles de référence absolument indispensables pour en savoir plus.
Les images SVG sont désormais omniprésentes sur le web : illustrations, schémas, décoration, icônes, ce format vectoriel se prête bien à toutes sortes de formes simples car il est léger et étirable à l'infini produisant un beau résultat sur les écrans à forte densité de pixel (hdpi, retina...).
Si l'on peut en produire "à la main" à l'aide d'un éditeur de code, car il s'agit avant tout de XML, les images SVG sont souvent générées par des programmes de dessin/webdesign tels que Figma, Illustrator, Inkscape et divers. Ces programmes ne vont pas nécessairement réduire au maximum le poids de l'image, même si cela tend à s'améliorer. Ils n'hésiteront pas à ajouter des métadonnées inutiles ou des instructions de code superflues, provenant de la façon dont a été élaborée l'image (calques, précision des formes et des tracés).
Ainsi Figma proposera une petite fonctionnalité d'export bien pratique et rapide pour copier une forme ou un groupe en tant que code SVG. Celui-ci pourra être inclus de manière inline au code HTML.
La performance étant cruciale, et scrutée par les outils d'analyse, les robots d'indexation, il est courant de devoir faire attention non seulement au poids des images bitmap (jpeg, png, webp, avif) mais aussi à SVG, avec d'autres techniques compte tenu de sa spécificité vectorielle.
S'il y a quelques années, il fallait se pencher manuellement sur l'optimisation du code SVG, désormais bon nombre d'outils remplissent 99% des objectifs. La plupart étant listés par CSS Tricks : Tools for Optimizing SVG.
🤖 Pour un traitement automatisé, on pourra prévoir dans un workflow de pojet web d'intégrer svgo durant un processus de compilation ou simplement en ligne de commande. En voici un exemple via Nodejs mais cette bibliothèque se retrouve dans d'autres situations (extension Sketch, Visual Studio Code, SublimeText, Atom... plugin postCSS, Gulp, Webpack, etc.).
pnpm add -g svgo
# Des images sélectionnées
svgo image1.svg image2.svg -o image1.min.svg image2.min.svg
# Tout un dossier (récursif)
svgo -f images/svg/icons/src -o images/svg/icons/dist
🎨 Pour un export depuis Figma, on passe par une extension de la communauté Advanced SVG Export qui embarque l'algorithme de SVGO et dont les paramètres sont configurables.
👉 Pour un traitement image par image quelle que soit la source, utilisons le fameux SVGOMG, version humainement graphique de SVGO, qui acceptera soit un fichier soit un copier-coller direct (encore plus pratique), puis affichera l'aperçu et la possibilité de ré-exporter ou re-copier le code source amélioré.
Si la plupart des options par défaut conviennent, détaillons celles qui rejoignent les principales techniques à retenir lorsqu'on intervient manuellement sur du SVG :
Exemple de chemin avec trop de précision :
<path d="M24 18.55C26.2 17.45 27.5 16.15 28 14.55C28.6 12.65 27.8 10.55 25.8 8.34996C24.1 6.34996 22.3 5.64996 20.5 6.14996C19 6.54996 17.6 7.84996 16.3 9.84996C15.2 11.55 14.3 13.65 13.7 15.75C13.1 17.85 13 19.35 13.3 20.25C14.2 20.55 15.6 20.55 17.8 20.25C20.1 20.15 22.2 19.55 24 18.55ZM12 9.44996C13.1 7.24996 13.4 5.44996 12.8 3.94996C12.1 2.04996 10.3 0.849963 7.40002 0.249963C4.90002 -0.350037 3.00002 0.149963 1.90002 1.64996C1.00002 2.84996 0.500025 4.64996 0.700025 7.14996C0.800025 9.24996 1.30002 11.35 2.20002 13.55C3.00002 15.55 3.70003 16.85 4.50003 17.45C5.40003 17.25 6.60003 16.25 8.10003 14.75C9.90003 12.95 11.1 11.25 12 9.44996ZM25.6 33.75C28 34.15 29.8 33.85 31.1 32.85C32.6 31.65 33.2 29.55 32.9 26.55C32.7 23.95 31.6 22.45 29.9 21.75C28.4 21.25 26.6 21.45 24.3 22.35C22.4 23.15 20.5 24.25 18.8 25.65C17.1 27.05 16.1 28.15 15.9 29.15C16.5 29.85 17.6 30.75 19.5 31.75C21.6 32.85 23.6 33.55 25.6 33.75Z" fill="#ACC737"/>
Le voici optimisé avec moins de précision sur les coordonnées, et un résultat visuel semblable :
<path d="M24 18.6c2.2-1.2 3.5-2.5 4-4 .6-2-.2-4-2.2-6.3-1.7-2-3.5-2.7-5.3-2.2-1.5.4-2.9 1.7-4.2 3.7-1.1 1.8-2 3.8-2.6 6-.6 2-.7 3.6-.4 4.4.9.4 2.3.4 4.5 0a14 14 0 0 0 6.2-1.6ZM12 9.4c1.1-2.2 1.4-4 .8-5.5C12.1 2 10.3 1 7.4.2 4.9-.4 3 .2 1.9 1.6 1 2.8.5 4.6.7 7.1c.1 2.1.6 4.3 1.5 6.5.8 2 1.5 3.3 2.3 3.8.9-.1 2.1-1.1 3.6-2.6A20 20 0 0 0 12 9.3Zm13.6 24.4c2.4.4 4.2 0 5.5-1 1.5-1.1 2.1-3.2 1.8-6.2-.2-2.7-1.3-4.2-3-4.9-1.5-.4-3.3-.3-5.6.7-1.9.8-3.8 1.9-5.5 3.3-1.7 1.4-2.7 2.4-2.9 3.4.6.8 1.7 1.6 3.6 2.6 2.1 1.2 4.1 1.8 6.1 2Z" fill="#ACC737"/>
<!-- -->
dans le code source, métadonnées de l'éditeur d'image qui aime y laisser sa trace, et même éléments cachés (hidden
) : tout ce qui ne se "voit" de toute façon pas en production, et ignoré par le navigateur.xmlns
n'est pas nécessaire si le code SVG est inline dans le code HTML.style=""
(ex : 2 carrés verts auront les mêmes propriétés) pour gagner de la place. En revanche l'option merge paths peut empêcher de conserver tous les tracés individuels pour agir sur chacun : cela dépendra beaucoup des règles externes CSS et du code JavaScript qui pourrait interagir avec le SVG attendu. Si vous écrivez des propriétés de style qui doivent s'appliquer sur un élément en particulier, nommé par une classe, et que cette classe est retirée, cela n'aura plus d'effet.<g>
) qui ne sont pas nécessaires au-delà de l'édition, il faut juste faire attention à préserver l'ordre de superposition des formes.<path>
avec des coordonnées minimales.Si vous examinez toutes les options listées par SVGOMG, vous imaginerez aisément tout ce qui peut être entrepris pour alléger une image SVG. Pour constater les effets avant/après sur le code source, consultez l'onglet Markup et activez/désactivez l'option Show original.
Une opération toutefois devra nécessiter une vérification et intervention manuelle : si votre SVG contient des images bitmap (non vectorielles), ce qui est tout à fait possible pour des effets non reproductibles en vectoriel (cela se remarquera à son poids global lourd et à la présence de code en base64) alors il faudra étudier la possibilité de les remplacer par de vraies formes simplifiées.
Utilisez SVGOMG.
La pseudo-classe :has()
réalise le fantasme historique de pouvoir enfin "cibler le parent" en CSS… mais elle fait bien plus que ça !
:has()
est une pseudo-classe CSS issue des spécifications "Selectors level 4" où elle est décrite comme "sélecteur relationnel".
Le sélecteur :has()
cible un élément en relation avec la liste d'arguments qu'il contient au sein de ses parenthèses. Cela permet de cibler un parent ou ancêtre, mais également un frère précédent dans le DOM.
Commençons justement par une énumération d'exemples de ce qu'est capable de réaliser :has()
…
Je cible l'élément <a>
à condition qu'il contienne un descendant <img>
:
a:has(img) {
}
Je cible l'élément <a>
à condition qu'il contienne un enfant direct <img>
:
a:has(> img) {
}
Je cible n'importe quel élément du DOM à condition qu'il contienne un descendant <img>
:
:has(img) {
}
Je cible l'élément <button>
à condition qu'il contienne un descendant de classe .icon
ou de classe .text
:
button:has(.icon, .text) {
}
Je masque l'élément <svg>
s'il contient un descendant <symbol>
ou <defs>
:
svg:has(symbol, defs) {
display: none;
}
Je cible l'élément de classe .container
à condition qu'il contienne un enfant direct unique <img>
:
.container:has(> img:only-child) {
}
Je cible l'élément de classe .form-group
à condition qu'il contienne un élément <input>
à l'état coché :
.form-group:has(input:checked) {
}
Je cible l'élément <form>
à condition qu'il contienne un élément à l'état :focus
(quasi identique au sélecteur :focus-within
) :
form:has(:focus) {
}
Je cible l'élément <h1>
à condition que son frère suivant soit un <p>
:
h1:has(+ p) {
}
Je cible l'élément <label>
à condition que son frère suivant soit un <input>
:
label:has(+ input) {
}
La compatibilité de :has()
a été placée au niveau de Baseline 2023, c'est à dire que l'ensemble des navigateurs supportent cette fonctionnalité depuis (décembre) 2023.
Il est parfaitement possible de tester si un navigateur supporte :has()
à l'aide de la règle @supports()
:
@supports (selector(:has(*))) {
/* ici les styles si :has() est supporté */
}
@supports not (selector(:has(*))) {
/* ici les styles si :has() n'est pas supporté */
}
Bien qu'appartenant aux pseudo-classes, :has()
ne compte pas dans le calcul de la spécificité des sélecteurs CSS, en revanche son contenu est pris en compte :
div:has(img) {
/* spécificité (0,0,2) */
/* :has ne compte pas */
/* on compte les 2 éléments div et img */
}
div:has(img, #hero) {
/* spécificité (1,0,1) */
/* :has ne compte pas */
/* on compte le poids le plus fort dans la liste (ici l'id #hero) */
}
:has()
peut être combiné avec d'autres sélecteurs tels que :not()
, :where()
ou :is()
. Cependant il n'est pas autorisé d'imbriquer :has()
au sein d'un autre :has()
(en même temps qui voudrait faire ça ?).
Voici quelques exemples :
p:has(:not(span))
: p
qui contient tout descendant sauf un span
p:not(:has(span))
: p
qui ne contient pas de descendant span
p:where(:has(span))
: p
qui contient un descendant span
. Fonctionnellement identique à p:has(span)
mais avec une spécificité moindre (0,0,1):where(p:has(span))
: p
qui contient un descendant span
. Fonctionnellement identique à p:has(span)
mais avec une spécificité nulle (0,0,0):has()
est un sélecteur tout à fait révolutionnaire qui ouvre la voie vers de multiples nouvelles possibilités en CSS.
Il est difficile de rassembler tous les cas d'usage utiles que cette pseudo-classe permet aujourd'hui de résoudre. Voici cependant une petite sélection parmi mes préférés…
Je souhaite cibler body
et lui appliquer un effet de flou via backdrop-filter
, uniquement s'il contient un élément ayant à la fois la classe .modal
et .is-opened
:
body:has(.modal.is-opened) {
backdrop-filter: blur(8px);
}
Pour ce cas d'usage précis, il peut être pertinent d'envisage d'utiliser <dialog>
qui apporte nativement ces fonctionnalités d'obscurcissement ou de flou de page.
Je souhaite une gouttière pour espacer les éléments de ma "Card", mais uniquement si celle-ci possède une image :
.card {
display: grid;
grid-template-columns: auto 1fr;
}
.card:has(>img) {
gap: 20px;
}
Encore plus fort : une gouttière doit s'appliquer uniquement si le groupe de cards a exactement deux enfants :
.card-group:has(> :nth-child(2):last-child) {
gap: 20px;
}
Je veux une couleur de fond sur le parent du label et de la checkbox quand celle-ci est cochée :
.form-group:has(input:checked) {
background: #ddd;
}
Je souhaite adapter les styles et la taille des enfants selon leur nombre au sein de leur parent :
.parent {
&:has(> :nth-child(10)) { }
&:has(> :nth-child(20)) { }
&:has(> :nth-child(30)) { }
}
À partir d'une case à cocher située n'importe où dans le DOM, je veux inverser les couleurs des variables de la page au moment où cette checkbox est cochée :
<body>
...
n'importe où dans le DOM
...
<div>
<input type="checkbox" id="check" class="toggle">
<label for="check">toggle light/dark</label>
</div>
</body>
body {
--bg-color: #eee;
--text-color: #222;
background-color: var(--bg-color);
color: var(--text-color);
}
body:has(.toggle:checked) {
--bg-color: #222;
--text-color: #eee;
}
/* Je crée un Container Query sur le parent de .component (quel qu'il soit) */
:has(> .component) {
container-type: inline-size;
}
/* J'adapte les styles de .component selon la taille du parent */
.component {
@container (width > 500px) {
color: hotpink;
}
}
La portée et la puissance du sélecteur :has()
dépassent de loin toutes les autres façons historiques de cibler les éléments en CSS.
Grâce à :has() il est possible de cibler n'importe quel élément du DOM à partir de n'importe quel autre élément du DOM.
On pourrait se poser des questions légitimes sur les performances d'un sélecteur permettant de parcourir tout le DOM dans tous les sens.
Dans son article consacré à ce sélecteur, Jen Simmons explique que les travaux sur :has()
ont conduit à des spécifications dédiées aux performances et que les navigateurs ont été retravaillés pour s'adapter à ces spécifications.
Les résultats sont excellents (traduction d'un extrait de son article) :
Il est enfin possible d'implémenter un tel sélecteur avec des performances fantastiques, même en présence de grands arbres DOM et d'un grand nombre de sélecteurs
:has()
.
Aujourd'hui, il nous est possible de faire énormément de choses avancés en CSS. Cependant quelque chose qui semble ± simple n'est pas toujours aussi simple que ce que l'on espère, voir pas du tout. Le projet sur lequel je travaille affiche énormément de table de données (<table>
). Pour certaines d'entre elles, j'aimerais pouvoir mettre en surbrillance la ligne et la colonne de la cellule que je survole…
Il est évident qu'il existe de moches solutions avec Javascript (hum hum), mais il est aussi très simple de le faire avec CSS (codepen).
Très simplement, en utilisant la pseudo-classe hover sur la ligne (<tr>
table-row), on peut changer son background-color
.
tr:hover {
background: antiquewhite;
}
Un peu plus compliqué pour cette partie…
En Javascript, il est plutôt facile de récupérer l'index de la colonne pour ensuite utiliser CSS et une pseudo-classe :nth-child()
. Pour toutes les lignes, sélectionner la colonne numéro 4, et lui afficher un background différent.
En CSS, on ne peut pas connaître son index (à l'exception de first et last), remonter la structure et ensuite réappliquer plus loin.
Par contre, en combinant de simple propriétés comme position
, overflow
et un pseudo-élément ::before
, on pourrait créer l'illusion en s'étendant verticalement un peu plus loin que la cellule.
td {
position: relative;
&:hover::before {
content: "";
position: absolute;
inset: -100vh 0; /* https://developer.mozilla.org/fr/docs/Web/CSS/inset */
background: antiquewhite;
z-index: -1; /* On positione le pseudo-élément en arrière plan */
}
}
Avec inset: -100vh 0
(équivalent à top: -100vh; right: 0; bottom: -100vh; left: 0;
), on va prendre la largeur de la cellule et s'étendre en hauteur.
Maintenant, nous avons bien une surbrillance qui se fait sur les deux axes. Il y a cependant notre colonne qui s'étend un peu trop. En ajoutant un simple overflow: hidden
sur notre table, on contiendra tout ça.
Le format SVG peut paraître parfois un peu intimidant, et l'associer à des transitions ou des animations CSS semble encore plus audacieux pour bon nombre de personnes.
Cependant, dans certains cas, l'alchimie entre SVG et CSS est aussi bénéfique qu'extrêmement simple à mettre en oeuvre. Dans ce tutoriel, nous allons suivre étape par étape comment animer un bouton burger simple avec SVG et CSS.
La liste des outils nécessaires pour atteindre nos objectifs est particulièrement réduite puisqu'un simple éditeur de code fait le job (n'importe lequel fait l'affaire, Visual Studio Code étant mon choix personnel).
Pour aller plus loin, et en guise de bonus, on peut également piocher :
En trois mots, voici comment résumer SVG :
Si l'on y regarde de plus près, une "icône Burger" c'est bêtement trois rectangles horizontaux espacés et avec des coins arrondis.
Notre éditeur de code préféré est amplement suffisant pour s'aquitter de la tâche de dessiner des rectangles : on va tout d'abord dessiner un élément SVG vide avec une fenêtre de "100 x 100". C'est une dimension purement indicative car tout est proportionnel et adaptable en SVG.
<svg class="burger-icon" viewBox="0 0 100 100">
</svg>
.burger-icon {
width: 200px; height: 200px; /* taille du SVG */
border: 2px dotted #ddd; /* bordure = simple repère */
}
Le tracé de notre premier rectangle est un jeu d'enfant aussi : l'élément SVG rect
est fait pour ça, attribuons-lui des coordonnées (x=0
et y=0
) ainsi qu'une largeur de "100" et une hauteur de "20".
<svg class="burger-icon" viewBox="0 0 100 100">
<rect x="0" y="0" width="100" height="20" />
</svg>
Vous aurez compris qu'à partir d'un premier rectangle, il n'est pas difficile de produire les deux suivants. Et voilà !
<svg class="burger-icon" viewBox="0 0 100 100">
<rect x="0" y="0" width="100" height="20" />
<rect x="0" y="40" width="100" height="20" />
<rect x="0" y="80" width="100" height="20" />
</svg>
Pour ce qui est des coins arrondis, là aussi SVG a tout prévu sous la forme de l'attribut rx
, à qui une valeur de "5" semble tout à fait parfaite.
<svg class="burger-icon" viewBox="0 0 100 100">
<rect x="0" y="0" width="100" height="20" rx="5" />
<rect x="0" y="40" width="100" height="20" rx="5" />
<rect x="0" y="80" width="100" height="20" rx="5" />
</svg>
Le résultat est bluffant et on se rend compte de la puissance insoupçonnée d'un éditeur de code. Plus sérieusement, ce n'était vraiment pas compliqué, non ?
Par contre, ce qui est vraiment dommage c'est de répéter les mêmes choses plusieurs fois…
Mais justement, il se trouve que… la plupart des attributs SVG existent également sous forme de propriétés CSS ! Voici par conséquent comment nous allons pouvoir améliorer notre code actuel :
<svg class="burger-icon" viewBox="0 0 100 100">
<rect class="rect-1" />
<rect class="rect-2" />
<rect class="rect-3" />
</svg>
rect {
x: 0;
rx: 5px;
width: 100px;
height: 20px;
}
.rect-1 {
y: 0;
}
.rect-2 {
y: 40px;
}
.rect-3 {
y: 80px;
}
Autre avantage loin d'être anodin, ces propriétés CSS-SVG ont la bonne idée d'être animables : on peut par exemple effectuer une transition
sur la propriété… y
!
rect {
...
transition: y 1s;
}
.rect-1:hover {
y: 15px;
}
Nous allons à présent nous atteler à transformer notre icône SVG en un véritable "bouton Burger", fonctionnel et accessible.
Pour ce faire, on commence par placer le SVG dans un <button>
qui sera l'élément interactif au clic / touch et qui déclenchera l'animation.
<button class="burger-button">
<svg class="burger-icon" viewBox="0 0 100 100">
<rect class="rect-1" />
<rect class="rect-2" />
<rect class="rect-3" />
</svg>
</button>
Notre icône SVG est considérée comme purement décorative, car c'est le bouton qui portera l'information. Nous veillons à lui appliquer les attributs suivants :
aria-hidden="true"
focusable="false"
pour éviter de naviguer au sein du SVG.<title>
ni <desc>
ni d'attribut title
, aria-label
, aria-labelledby
, ni role="img"
...
<svg class="burger-icon" aria-hidden="true" focusable="false" viewBox="0 0 100 100">
</svg>
...
Le bouton, quant à lui, nécessite les éléments suivants :
aria-label
ou un texte masqué à la ".sr-only")aria-controls
pour lier à la cible et un attribut aria-expanded
pour signaler l'état du bouton. Dans notre cas, ce n'est pas nécessaire.<button class="burger-button" aria-label="Menu" data-expanded="false">
...
</button>
Voici le script JavaScript destiné à gérer l'interaction et la mise à jour des attributs data-
, et déclencher l'animation de l'icône :
(function () {
function toggleNav() {
// Define targets
const button = document.querySelector('.burger-button');
const target = document.querySelector('#navigation');
button.addEventListener('click', () => {
const currentState = target.getAttribute("data-state");
if (!currentState || currentState === "closed") {
target.setAttribute("data-state", "opened");
button.setAttribute("data-expanded", "true");
} else {
target.setAttribute("data-state", "closed");
button.setAttribute("data-expanded", "false");
}
});
} // end toggleNav()
toggleNav();
}());
aria-expanded
ou non ?L'utilisation de aria-expanded
sur un bouton n'est pas systématique, dans le cas d'un menu tout dépend de comment celui-ci va s'ouvrir :
inert
), alors le bouton reste un bouton simple, pas d'aria-expanded
.aria-expanded.
.Pour être très précis, nous n'allons pas employer une "animation" pour nos effets, mais une combinaison de trois "transitions", qui se révèleront amplement suffisantes pour notre besoin.
Voici le scénario étape par étape qui doit se réaliser :
button
doit déclencher une série de trois transitions;.rect-1
et .rect-3
qui se rejoignent au centre du SVG;.rect-2
qui traîne dans nos pattes. En terme de timing, cette transition doit se dérouler en même temps que la transition 1;.rect-1
et .rect-3
et doit de déclencher juste après les transitions précédentes).La propriété transition
est appliquée sur l'élément à l'état initial (hors événement) afin d'assurer une transition au retour lorsque l'événement est quitté.
/* transition sur la propriété y et opacity, durée 0.3s */
rect {
transition:
y 0.3s,
opacity 0.3s;
}
/* coordonnées y initiales */
.rect-1 {
y: 0;
}
.rect-2 {
y: 40px;
}
.rect-3 {
y: 80px;
}
Au clic, le bouton passe en data-expanded "true"
et on déplace verticalement deux rectangles au centre et on masque le 3e rectangle central.
[data-expanded="true"] .rect-1 {
y: 40px;
}
[data-expanded="true"] .rect-2 {
opacity: 0;
}
[data-expanded="true"] .rect-3 {
y: 40px;
}
Aux deux transitions précédentes, on ajoute une transition sur la propriété rotate
sans oublier de la faire débuter après un léger délai.
/* on attend un delai de 0.3s avant de commencer rotate */
rect {
transition:
y 0.3s,
opacity 0.3s,
rotate 0.3s 0.3s;
}
Au clic, les trois transitions se déclenchent.
[data-expanded="true"] .rect-1 {
y: 40px;
rotate: 45deg;
}
[data-expanded="true"] .rect-2 {
opacity: 0;
}
[data-expanded="true"] .rect-3 {
y: 40px;
rotate: -45deg;
}
⚠️ J'imagine que cela ne vous a pas échappé : tout se passe très bien à l'aller, mais malheureusement pas au retour. L'explication provient du fait que la transition se déroule dans le sens inverse au retour et que la rotation se déclenche trop tôt. Il va nous falloir une transition différente à l'aller et au retour et gérer des délais différents entre la transition et la rotation.
/* transition au retour (quand on perd le clic) */
/* on attend un delai de 0.3s avant de commencer y */
rect {
transition:
y 0.3s 0.3s,
opacity 0.3s,
rotate 0.3s;
}
/* transition à l'aller (quand on clique) */
/* on attend un delai de 0.3s avant de commencer rotate */
[data-expanded="true"] rect {
transition:
y 0.3s,
opacity 0.3s,
rotate 0.3s 0.3s;
}
Grâce à cette adaptation subtile, notre effet fonctionne parfaitement à l'aller et au retour lors de l'interaction.
Pour finir en beauté, le truc en plus consiste en une petite accélération sous forme de cubic-bezier
pour un effet de "rebond".
[data-expanded="true"] rect {
transition:
y 0.3s,
opacity 0.3s,
rotate 0.3s 0.3s cubic-bezier(.55,-0.65,0,2.32);
}
Voici les styles CSS complets de ce tutoriel.
Notez qu'ils prennent en compte les préférences utilisateur grâce au media query prefers-reduced-motion
: si la personne a choisi dans ses réglages système de réduire les animations, celles-ci ne seront tout simplement pas déclenchées.
Pour voir le résultat et aller plus loin, une petite collection CodePen de boutons burger animés a été rassemblée à cette adresse : https://codepen.io/collection/VYqwJK
.rect-1 {
y: 0;
}
.rect-2 {
y: 40px;
}
.rect-3 {
y: 80px;
}
[data-expanded="true"] .rect-1 {
y: 40px;
rotate: 45deg;
}
[data-expanded="true"] .rect-2 {
opacity: 0;
}
[data-expanded="true"] .rect-3 {
y: 40px;
rotate: -45deg;
}
/* transitions si acceptées */
@media (prefers-reduced-motion: no-preference) {
rect {
transition:
y 0.3s 0.3s,
opacity 0.3s,
rotate 0.3s;
}
[data-expanded="true"] rect {
transition:
y 0.3s,
opacity 0.3s,
rotate 0.3s 0.3s cubic-bezier(.55,-0.65,0,2.32);
}
}
Retrouvez l'intégralité de ce tutoriel en ligne sur Alsacreations.com
Article rédigé suite à un stage d'observation dans l'équipe d'Alsacréations
Lorsque l'on consulte un site web, nous pouvons tous voir le code source (avec ctrl + U » ou en faisant un clic droit puis inspecter).
Quelques sites en profitent pour dissimuler des easter eggs le plus souvent sous la forme d'art ASCII ou de messages à demi-cachés, que ce soit en HTML, JavaScript, ou CSS. Ces messages sont soit des petits clins d'oeil à destination d'autres personnes curieuses ou pratiquant le développement web, soit intentionnels pour mentionner des liens vers des pages de recrutement, des avertissements ou des ressources variées.
Voyons quelques exemples de différentes natures, en mettant de côté ceux qu'affichent les moteurs de recherche tels que Google avec des mots clés spécifiques (ex: cherchez "anagramme" ou "Askew").
Mozilla a placé dans son code un "godzilla" crachant du feu sur le logo, assorti d'un message :
Bonjour, enchanté ! Intéressé d’avoir un impact direct sur des centaines de millions d’utilisateurs ? Rejoins Mozilla, et viens faire partie de la communauté qui aide à construire un meilleur avenir pour le web. Visitez … pour en apprendre plus sur les emplois disponibles. Visitez … pour plus de façons de s'impliquer et aider à supporter Mozilla
_.-~-.
7'' Q..\
_7 (_
_7 _/ _q. /
_7 . ___ /VVvv-'_ .
7/ / /~- \_\\ '-._ .-' / //
./ ( /-~-/||'=.__ '::. '-~'' { ___ / // ./{
V V-~-~| || __''_ ':::. ''~-~.___.-'' _/ // / {_ / { /
VV/-~-~-|/ \ .'__'. '. ':: _ _ _ ''.
/ /~~~~||VVV/ / \ ) \ _ __ ___ ___ ___(_) | | __ _ .::'
/ (~-~-~\\.-' / \' \::::. | '_ ` _ \ / _ \_ / | | |/ _` | :::'
/..\ /..\__/ ' '::: | | | | | | (_) / /| | | | (_| | ::'
vVVv vVVv ': |_| |_| |_|\___/___|_|_|_|\__,_| ''
Hi there, nice to meet you!
Interested in having a direct impact on hundreds of millions of users? Join
Mozilla, and become part of a global community that’s helping to build a
brighter future for the Web.
Visit https://www.mozilla.org/careers to learn about our current job openings.
Visit https://www.mozilla.org/contribute for more ways to get involved and
help support Mozilla.
Sur la page d'accueil de Amazon, vous trouverez tout en bas de la page de code, un canard qui dit Meow. Sympa non ?
.__(.)< (MEOW)
\___)
~~~~~~~~~~~~~~~~~~
Sur theoatmeal se cache un gigantesque ptérodactyle, à vous de le trouver.
Vous pourrez également trouver le logo de l’Université du Michigan dans leur code source :
<!--
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMM MMMMMMMMMMMMMMMMM MMMMMMMMM
MMMMMMMM MMMMMMMMMMMMMMM MMMMMMMMM
MMMMMMMM MMMMMMMMMMM MMMMMMMMM
MMMMMMMM MMMMMMMMM MMMMMMMMM
MMMMMMMM MMMMMMM MMMMMMMMM
MMMMMMMMMMMM MMMMM MMMMMMMMMMMM
MMMMMMMMMMMM MMM MMMMMMMMMMMM
MMMMMMMMMMMM V MMMMMMMMMMMM
MMMMMMMMMMMM MMMMMMMMMMMM
MMMMMMMMMMMM ^ ^ MMMMMMMMMMMM
MMMMMMMMMMMM MM MM MMMMMMMMMMMM
MMMMMMMMMMMM MMMM MMMM MMMMMMMMMMMM
MMMMMMMMMMMM MMMMM MMMMM MMMMMMMMMMMM
MMMMMMMMMMMM MMMMMM MMMMMM MMMMMMMMMMMM
MMMMMMMM MMMM MMMM MMMMMMMMM
MMMMMMMM MMMMMVMMMMM MMMMMMMMM
MMMMMMMM MMMMMMMMMMM MMMMMMMMM
MMMMMMMM MMMMMMMMMMM MMMMMMMMM
MMMMMMMM MMMMMMMMMMM MMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
-->
Quelques marques comme KitKat ou Coca-Cola avaient aussi opté pour cet art caché, mais les ont supprimés depuis des refontes récentes.
Si vous souhaitez vous aussi ajouter un easter egg tel quel dans votre code, il y a une manière très simple de le faire : utiliser les commentaires HTML entre <!--
et finissant par -->
. Tout ce qui est placé entre n'est pas interprété par le navigateur. Vous trouverez aussi de nombreux sites vous permettant de générer de l'art avec les caractères.
Le Code Konami quant à lui correspond à une combinaison de touches successives, venant initialement d'un code de triche de jeu vidéo : « Haut Haut Bas Bas Gauche Droite Gauche Droite B A ». Vous pouvez essayer cette commande sur :
Un petit script konami-js vous premet de l'ajouter facilement.
Les fichiers robots.txt
sont connus pour être placés à la racine d'un site pour interdire le référencement de certaines portions aux robots d'indexation. Ces derniers peuvent contenir des commentaires, donc de l'ascii art. Notez par exemple celui de alsacreations.com ;)
Plus récemment pensés, les fichiers humans.txt décrivent les personnes "humaines" ayant contribué à un projet et sont un espace totalement libre pour y dissimuler (aussi) des easter eggs. Par exemple :
..-:\oo-. .:oo/:-.
-oooooooooooooooooooooo. __ ___
-oooooooooooooooooooooooo. OOOOOOOOO:-. `MM` _.._ _.._ _.._ ____ _ _. OOO.
-oooooooooooooooooooooooooo. OOOOOOOOOOOo: -/ooo-ooo. -/OOOOOO/. :/OOOOOO:. OOOO.oOo: .:oOOOO-.OOO.
`ooooooo/--/oooooo:--/ooooooo OOOO `OOOO `OooI `ooo:. ` /oOO/```-:..oOOO-``\OOO: OOOO/` ``.oOOO/```\OOO.
:oooooo- :oooo- :oooooo- OOOO .OOOo `OOOI `-OOOOOOo.`OOOO. -OOO: .OOOo OOOO. -OOOO` /OOO.
ooooooo:- -/oooo:- -/oooooo/ OOOOooooOooo- `OOOI ._ `]OO/ :OOOOoooOO.`OOOOOooOOOO- OOOO. `/OOOOooOOOOO.
ooooooooo//oooooooo//ooooooooo OOOOOOOOO:-` `OOOI `:OOoOO:-` `-:OOOO:-` `-:OOOO:-` OOOO. `-:OOO:-`OOO.
ooooooo/ooooooooooooo//oooooo/
`.:ooooo/``.------.`.\oooo/:.
`.-:` .:-.`
https://discord.com/company
La console JavaScript du navigateur peut révéler des messages, une fois ouverte (par F12 ou Ctrl+Shift+I). C'est un outil typique à destination des développeuses et développeurs, on y retrouve non seulement des erreurs ;) mais aussi parfois des messages de bienvenue ou encore d'avertissement comme Discord qui est assez explicite :
Prenez quelques instants pour ajouter à vos projets et sites ces petits messages cachés. Et précisez en commentaires si vous connaissez d'autres sites avec de telles astuces !