Alimenter un bandeau d’actualités avec JavaScript et JSON
Dans le précédent article, « Créer un bandeau d’actualités défilant en HTML et CSS », nous avons construit un bandeau d’actualités défilant, s’appuyant uniquement sur HTML et CSS. Mais cette première version possède encore une faiblesse assez évidente : chaque actualité doit être écrite deux fois dans le HTML, en dur, puisque le défilement continu repose sur deux séries de contenus identiques.
Cela ne pose pas de réel problème pour une démonstration, mais devient vite une tâche fastidieuse dès que nous devons faire évoluer le bandeau. Nous allons donc explorer, au cours de cet article, une approche plus dynamique, s’appuyant sur un fichier JSON externe pour transmettre les actualités, et sur quelques lignes de JavaScript qui auront pour objectif de produire à la volée le HTML nécessaire.
Ce que nous allons modifier… et ce qui ne change pas
Le principe général du bandeau ne va pas changer. Nous conservons la structure et le fonctionnement mis en place dans le premier article : une zone visible, un ruban qui se déplace horizontalement, deux bandeaux identiques pour assurer la continuité du défilement, ainsi que les règles déjà prévues pour le survol, le focus et la réduction des mouvements.

Ce que nous allons adapter concerne surtout la manière dont le contenu sera préparé. Jusqu’à présent, les contenus d’actualités étaient écrits directement dans le HTML, puis recopiés une seconde fois pour produire l’impression d’une boucle. Désormais, les actualités seront regroupées dans un fichier JSON externe, JavaScript se chargera de charger ce fichier, de le parcourir et de mettre en place les deux bandeaux nécessaires.
Les rôles restent donc assez simples à distinguer. HTML fournit le point d’ancrage du composant, JSON contient les données, JavaScript les transforme en éléments utilisables dans la page, tandis que CSS continue à gérer la présentation et le mouvement. L’objectif n’est donc pas de fabriquer un nouveau composant, mais simplement d’en améliorer l’utilisation en séparant ce qui relève du contenu et de la présentation, afin de rendre l’ensemble plus souple à maintenir.
Organiser les données dans un fichier JSON
À y regarder de plus près, nous avons deux types d’informations nécessaires pour alimenter ou configurer notre composant. D’un côté, les actualités avec leurs contenus textuels, leurs hyperliens, éventuellement une image, etc… et de l’autre, le composant lui-même, avec ses couleurs, sa typographie et toutes les autres propriétés que nous pourrions imaginer.
Notre fichier JSON va donc devoir refléter cette dualité, en proposant deux ensembles distincts : bandeau, qui contiendra les informations propres à la configuration du composant, et actualites, qui regroupera les contenus à afficher.
{
"bandeau": {
},
"actualites": [
{
"text": "Une certaine idée du développement",
"link": "https://www.puce-et-media.com/une-certaine-idee-du-developpement/",
"target": "_blank"
},
// D’autres actualités seront ajoutées ici sous la forme de nouveaux objets.
]
}Pour les besoins de cet article, l’objet bandeau reste volontairement vide. Il servira plus tard à transmettre la configuration du composant. En revanche, actualites contient ici un tableau d’objets. Chaque objet représente une actualité et rassemble les informations dont JavaScript aura besoin pour construire le lien correspondant.
- text contient le texte qui sera affiché,
- link son adresse,
- et target précise le contexte dans lequel le lien devra s’ouvrir.
Nous pourrons, en fonction de l’évolution du composant ou de nos besoins, ajouter d’autres informations complémentaires. titre pourrait, lorsque cela sera utile, être repris dans l’attribut title du lien, tandis que classe permettrait d’ajouter une classe CSS spécifique à une actualité donnée.
Pour l’instant quelques propriétés simples suffisent largement à mettre en place le mécanisme. L’objectif de cette étape reste de comprendre comment employer de concert JavaScript et JSON pour adapter le fil d’actualités.
Alléger le HTML de départ
Nous pouvons repartir directement de la version finale obtenue dans le précédent article. Le fichier complet reste disponible à cette adresse : bandeau-v2.0.html. Sa structure contient déjà tout ce dont nous avons besoin pour assurer le fonctionnement du composant : la section .news-actualites, la zone .news-actualites__viewport et le ruban .news-actualites__track.
Ce qui va changer se trouve à l’intérieur du track. Dans la version précédente, nous y avions écrit deux séries complètes d’actualités, la seconde servant uniquement à assurer la continuité visuelle du défilement. Puisque JavaScript va désormais se charger de construire ces contenus à partir du fichier JSON, nous pouvons supprimer tout ce qui était jusqu’ici écrit en dur. Le HTML de départ devient alors beaucoup plus léger :
<section class="news-actualites">
<div class="news-actualites__viewport">
<div class="news-actualites__track">
<!--
Les actualités écrites en dur ont été retirées ici.
JavaScript reconstruira ce contenu dynamiquement
à partir des données du fichier JSON externe.
-->
</div>
</div>
</section>Le track reste donc bien présent, mais nous ne lui fournissons plus directement de contenu. Il devient simplement le point d’accueil dans lequel JavaScript viendra ajouter le premier ensemble d’actualités, puis sa copie.
Il nous reste enfin à appeler le fichier JavaScript chargé de cette construction. Nous pouvons le faire depuis le <head> en utilisant l’attribut defer :
<script src="actualites.js" defer></script>Avec defer, le navigateur peut charger le fichier JavaScript sans interrompre l’analyse du document HTML. Son exécution attendra que celui-ci ait été parcouru, ce qui nous permet notamment de travailler sur .news-actualites et son track une fois ces éléments disponibles, bandeau-v2.1.html.

Pour revenir plus en détail sur les différentes manières de charger un script, nous pouvons nous reporter à l’article « Mieux comprendre le chargement du JavaScript dans une page web ».
Indiquer la source des actualités avec un attribut data-*
Une fois chargé, JavaScript doit savoir où aller chercher les données qui permettront de reconstruire le contenu du ruban. Nous pourrions bien sûr inscrire directement le chemin du fichier JSON dans le script, mais cela reviendrait à lier celui-ci à une seule configuration. Dès que nous voudrions utiliser un second composant sur la même page, avec d’autres actualités, il faudrait alors modifier ou dupliquer une partie du code.
Les attributs data-* permettent justement d’éviter cela. Ils nous offrent la possibilité d’ajouter à un élément HTML des informations qui lui sont propres, sans modifier son rôle ni sa structure. Nous pouvons donc indiquer directement sur chaque instance du composant le fichier JSON qu’elle doit utiliser :
<section
class="news-actualites"
data-news-src="actualites.json"
>
<div class="news-actualites__viewport">
<div class="news-actualites__track">
<!-- Le contenu sera construit ici par JavaScript. -->
</div>
</div>
</section>L’attribut data-news-src n’a pas de signification particulière pour le navigateur. Il devient en revanche accessible depuis JavaScript à travers la propriété dataset. La partie placée après data- est convertie en notation camelCase : data-news-src devient ainsi newsSrc. JavaScript peut donc parcourir tous les composants présents dans la page et récupérer, pour chacun d’eux, la valeur transmise avec dataset :
const banners = document.querySelectorAll('.news-actualites');
banners.forEach(function (banner) {
const newsSrc = banner.dataset.newsSrc;
});
/*
data-news-src devient ici dataset.newsSrc.
Chaque composant conserve ainsi sa propre source de données, tout en utilisant le même script JavaScript.
*/Le chemin du fichier JSON n’est donc plus inscrit en dur dans JavaScript. C’est chaque composant qui indique lui-même la source qu’il doit utiliser. Le même script pourra ainsi continuer de travailler avec plusieurs instances :
<section
class="news-actualites"
data-news-src="actualites.json">
<div class="news-actualites__viewport">
<div class="news-actualites__track"></div>
</div>
</section>
<section
class="news-actualites"
data-news-src="evenements.json">
<div class="news-actualites__viewport">
<div class="news-actualites__track"></div>
</div>
</section>Préparer plusieurs niveaux de personnalisation
Nous avons jusqu’ici utilisé le fichier JSON pour transmettre les informations indispensables à chaque actualité : son texte, son lien et son contexte d’ouverture. Mais rien ne nous oblige à nous arrêter là. Le JSON peut aussi transporter des informations destinées à faire varier la présentation d’un élément particulier. Par exemple, un objet de datas.actualites peut contenir une propriété classe :
{
"actualites": [
{
"text": "Une certaine idée du développement",
"link": "https://www.puce-et-media.com/une-certaine-idee-du-developpement/",
"target": "_blank",
"classe": "news-important"
},
// D’autres actualités
]
}Cette classe pourra plus tard être ajoutée uniquement à l’item construit à partir de cet objet. Elle ne concerne donc pas le bandeau entier, mais une actualité précise.
Nous pourrions être tentés d’ajouter également ce type d’information dans l’objet bandeau du fichier JSON. Mais ici, un attribut data-* semble plus adapté : il permet au composant d’indiquer directement la feuille de styles complémentaire qu’il doit utiliser, tout en laissant datas.bandeau disponible pour un autre rôle.
Nous pourrons ainsi réserver cet objet aux propriétés CSS propres au composant, couleurs, typographie, dimensions, durée ou espacements, que nous exploiterons plus en détail dans le prochain article. Le composant lui-même peut donc indiquer sa feuille de styles avec data-style :
<section
class="news-actualites"
data-news-src="actualites.json"
data-style="actualites.css"
>Comme pour data-news-src, JavaScript peut récupérer cette information avec dataset. À ce stade, nous ne chargeons encore ni la classe issue du JSON ni la feuille CSS complémentaire ; nous nous contentons de vérifier que les informations sont bien disponibles :
const banners = document.querySelectorAll('.news-actualites');
banners.forEach(function (banner) {
console.log(
'Source JSON :',
banner.dataset.newsSrc
);
console.log(
'Feuille CSS complémentaire :',
banner.dataset.style
);
});
Nous disposons donc déjà de deux pistes distinctes : personnaliser une actualité particulière avec classe, ou associer une feuille de styles complémentaire au composant avec data-style, bandeau-v2.2.html. Nous mettrons réellement ces deux possibilités en pratique dans l’article suivant, lorsque nous travaillerons sur la personnalisation du bandeau.
Les attributs
data-*jouent donc un rôle assez simple : ils permettent au HTML de transmettre à JavaScript les informations nécessaires à chaque instance, sans enfermer ces chemins dans le script. Le composant reste générique, tandis que sa source de données et éventuellement, sa feuille de styles complémentaire, peuvent varier d’un bandeau à l’autre.
Charger le fichier JSON avec fetch()
Nous avons désormais un composant capable d’indiquer lui-même la source de ses données avec data-news-src. JavaScript peut donc parcourir les différents bandeaux de la page et charger, pour chacun d’eux, le fichier JSON qui lui est associé.
Pour cela, nous allons utiliser fetch(), qui permet d’effectuer une requête vers une ressource externe. Nous avions déjà abordé plus largement ce principe dans L’Évolution des Requêtes HTTP et des Opérations Asynchrones ; la documentation de l’API Fetch sur MDN permettra également d’en approfondir le fonctionnement.
const banners = document.querySelectorAll('.news-actualites');
banners.forEach(function (banner) {
// Chaque bandeau indique lui-même le fichier JSON à charger.
const newsSrc = banner.dataset.newsSrc;
fetch(newsSrc)
// Nous vérifions que le serveur a bien répondu à la requête.
.then(function (response) {
if (!response.ok) {
throw new Error('Impossible de charger le fichier JSON.');
}
// Le contenu JSON est converti en données JavaScript.
return response.json();
})
// Les données sont maintenant disponibles pour ce bandeau.
.then(function (datas) {
console.log(datas);
})
// Une erreur de chargement ou de traitement reste visible.
.catch(function (error) {
console.error(error);
});
});À ce stade, nous nous contentons volontairement d’afficher datas dans la console. Nous pouvons ainsi vérifier que chaque composant récupère bien son propre jeu de données avant de commencer à construire son contenu.
Dans le fichier bandeau-v2.3.html, nous utiliserons donc deux composants, l’un consacré aux actualités et l’autre aux événements :
<section
class="news-actualites"
data-news-src="actualites.json"
>
<div class="news-actualites__viewport">
<div class="news-actualites__track"></div>
</div>
</section>
<section
class="news-actualites"
data-news-src="evenements.json"
>
<div class="news-actualites__viewport">
<div class="news-actualites__track"></div>
</div>
</section>Le même script traitera les deux composants, mais chacun chargera son propre fichier JSON.

Pour les essais en local, mieux vaut servir la page avec un serveur HTTP plutôt que de l’ouvrir directement avec
file://.fetch()pourra ainsi accéder normalement aux fichiers JSON ; en cas de problème, le chemin de la ressource et la réponse du serveur seront les premiers points à vérifier. Pour mettre rapidement cet environnement en place, nous pouvons nous reporter à « Installer et configurer un serveur web en local » ou, pour une solution plus légère basée sur Node.js, à « Créer un serveur Node rapide pour servir des fichiers statiques ».
Pour approfondir le fonctionnement de cette API, la documentation API Fetch de MDN constitue une bonne référence technique. Pour replacer fetch() dans l’évolution plus générale des requêtes HTTP et des traitements asynchrones en JavaScript, nous pouvons également nous reporter à l’article « L’Évolution des Requêtes HTTP et des Opérations Asynchrones ».
Construire le premier bandeau
Dans le chapitre précédent, nous nous sommes contenter d’afficher les données dans la console pour vérifier que le fichier JSON était correctement chargé. Nous pouvons maintenant remplacer ce test par l’appel à une fonction chargée de construire le ruban. Adaptons le code :
// Les données sont maintenant disponibles pour ce bandeau.
.then(function (datas) {
console.log(datas);
})
// remplaçons ce fragment par un appel à la fonction construireBandeau()
.then(function (datas) {
// Les données du fichier JSON sont maintenant disponibles.
// Nous les transmettons, avec le composant concerné,
// à la fonction chargée de construire le premier bandeau.
construireBandeau(banner, datas);
})Nous disposons désormais de deux ensembles de données : datas.actualites, qui contient les informations nécessaires à la construction des items, et datas.bandeau, qui regroupe la configuration propre au composant. Pour l’instant, nous allons nous concentrer sur datas.actualites afin de construire le premier bandeau contenu dans le ruban. Définissons la fonction que nous venons d’invoquer :
// Nous commençons ici par construire les actualités.
function construireBandeau(banner, datas) {
// Création du premier bandeau qui accueillera les actualités.
const content = document.createElement('div');
content.classList.add('news-actualites__content');
// Parcours de toutes les actualités présentes dans le fichier JSON.
datas.actualites.forEach(function (actualite) {
// Chaque actualité devient un lien dans le bandeau.
const item = document.createElement('a');
item.classList.add('news-actualites__item');
item.href = actualite.link;
item.target = actualite.target;
// Le texte provenant du JSON est ajouté comme simple contenu texte.
item.textContent = actualite.text;
// Un séparateur est créé après chaque item.
const separator = document.createElement('span');
separator.classList.add('news-actualites__separator');
separator.textContent = '—';
// L’item et son séparateur sont ajoutés à la suite dans le bandeau.
content.append(item, separator);
});
// Chaque composant possède son propre ruban.
// Nous récupérons celui du bandeau en cours de traitement
// pour y insérer le contenu que nous venons de construire.
const track = banner.querySelector('.news-actualites__track');
track.append(content);
}Le principe reste volontairement simple. Nous commençons par créer un bandeau vide, puis forEach() parcourt datas.actualites. À chaque passage dans le tableau, JavaScript construit un nouvel item à partir des données disponibles, lui associe son séparateur, puis ajoute l’ensemble dans .news-actualites__content. Le texte, lui, est injecté avec textContent : il est donc traité comme du contenu textuel provenant du fichier JSON, et non comme du HTML à interpréter, bandeau-v2.4.html.

Si vous laissez défiler l’animation, vous constaterez qu’à la fin du premier bandeau, un espace vide apparaît. C’est tout à fait normal pour l’instant : notre fonction n’a construit qu’un seul bandeau dans le ruban. Le second, nécessaire pour assurer la continuité du défilement, n’existe pas encore.
Avant de le mettre en place, nous pouvons toutefois profiter de cette première construction pour compléter proprement les liens. Certaines informations du fichier JSON ne sont pas obligatoires, mais peuvent être utilisées lorsqu’elles sont présentes : classe pour ajouter un style particulier à une actualité, titre pour renseigner l’attribut title, et target pour déterminer si le lien doit s’ouvrir dans un nouvel onglet.
Dans ce dernier cas, lorsque target vaut _blank, nous pouvons également ajouter automatiquement rel="noopener". Le JSON décrit donc l’intention, tandis que JavaScript se charge de produire le lien complet. Ajoutons ce traitement dans la fonction, juste après item.textContent = actualite.text;
// Si une classe complémentaire est prévue dans le JSON,
// nous l’ajoutons à l’item.
if (actualite.classe) {
item.classList.add(actualite.classe);
}
// Si un titre est fourni, il complète l’attribut title du lien.
if (actualite.titre) {
item.title = actualite.titre;
}
// Un lien ouvert dans un nouvel onglet reçoit également rel="noopener".
if (actualite.target === '_blank') {
item.rel = 'noopener';
}Produire la copie nécessaire au défilement
Notre premier bandeau est maintenant construit et ses liens sont complétés, bandeau-v2.5.html. Il reste à recréer la seconde partie du ruban, celle qui permet au défilement de donner l’impression d’une boucle continue.
Dans la première version du composant, cette seconde série était écrite à la main. Nous pouvons désormais éviter cette duplication en utilisant cloneNode(true), qui permet de copier un élément avec tout son contenu.
// Nous pouvons modifier le code actuel
const track = banner.querySelector('.news-actualites__track');
track.append(content);
// En le remplaçant par celui ci
const track = banner.querySelector('.news-actualites__track');
// Créons une copie avec tous ses éléments enfants.
const clone = content.cloneNode(true);
// Les deux bandeaux sont ajoutés à la suite dans le ruban.
track.append(content, clone);Le paramètre true indique ici que nous voulons une copie profonde : le conteneur .news-actualites__content est dupliqué avec tous les items et séparateurs qu’il contient.
Le ruban retrouve donc la structure attendue par le CSS : deux bandeaux identiques placés l’un à la suite de l’autre. L’animation peut continuer à déplacer .news-actualites__track jusqu’à translateX(-50%), comme dans la version précédente. À ce point précis, le second bandeau a pris la place du premier à l’écran, et la boucle peut recommencer sans rupture visible, bandeau-v2.6.html.

Cette fois ci, il n’y a plus de rupture visuelle, mais il reste toutefois une différence importante avec notre ancienne version : ce clone est pour l’instant une copie exacte, y compris du point de vue de l’accessibilité. Nous allons donc devoir le rendre uniquement visuel avant de considérer le mécanisme comme terminé.
Conserver les règles d’accessibilité
La copie que nous venons de créer est utile pour le défilement, mais elle ne doit pas devenir un second contenu à parcourir pour les technologies d’assistance. Dans la version précédente du composant, ce rôle était déjà traité manuellement : le second bandeau recevait aria-hidden="true" et ses liens étaient retirés de la navigation au clavier avec tabindex="-1".
Nous allons simplement reproduire ce comportement au moment où JavaScript construit le clone :
// Le second bandeau sert uniquement à assurer la continuité visuelle.
clone.setAttribute('aria-hidden', 'true');
// Ses liens ne doivent pas être accessibles au clavier.
clone.querySelectorAll('a').forEach(function (link) {
link.tabIndex = -1;
});aria-hidden="true" masque le clone aux technologies d’assistance, tandis que tabindex="-1" évite que ses liens soient atteints une seconde fois au clavier. Les deux opérations restent donc complémentaires.
Cette approche permet aussi de conserver telles quelles les règles CSS déjà mises en place pour prefers-reduced-motion. Dans la version précédente, le bandeau marqué avec le code suivant, était automatiquement masqué lorsque les animations étaient réduites. :
.news-actualites__content[aria-hidden="true"] {
display: none;
}Autrement dit, nous ne changeons pas les règles d’accessibilité du composant. JavaScript se contente désormais de générer les attributs qui étaient auparavant écrits à la main dans le HTML, bandeau-v2.7.html.
Conclusion : une seule source pour alimenter le bandeau
Nous sommes partis d’un composant qui fonctionnait déjà, mais dont chaque actualité devait être écrite à la main dans le HTML. En déplaçant ces données dans un fichier JSON externe, puis en laissant JavaScript construire les bandeaux nécessaires de manière dynamique, nous avons rendu le composant beaucoup plus simple à alimenter et à faire évoluer.
Nous avons volontairement laissé de côté tout ce qui concerne la personnalisation du composant lui-même. Pourtant, notre fichier JSON contient déjà un objet bandeau qui ouvre une autre possibilité : ne plus seulement alimenter le composant, mais commencer à le configurer. Ce sera justement le point de départ du prochain article « Personnaliser un bandeau d’actualités avec JSON et CSS ».
