Merci de désactiver votre bloqueur de publicités.
Les seules pubs ici sont discrètes (barre latérale sur PC, sous le contenu sur mobile).Elles ne gênent pas la lecture et peuvent même vous offrir des avantages (réduc, mois gratuit…).
HTML/CSS/JS : Intégrer facilement des iframes sans compromettre la vie privée
Difficulté
Ce tutoriel accessible vous permettra de copier-coller un système complet et de le personnaliser selon vos besoins.
Intégrer une vidéo YouTube, Dailymotion ou tout autre contenu embarqué est très simple. Le faire sans compromettre la vie privée de vos visiteurs et en respectant le RGPD est déjà un peu plus compliqué.
Lorsque j'ai refait breat.fr, je n'ai trouvé aucune solution simple qui empêchait réellement la collecte de données avant le consentement du visiteur. J'ai donc décidé de créer mon propre système.
La plupart des tutoriels que j'ai trouvés masquaient le problème plus qu'ils ne le résolvaient. Ici, l'approche est différente : tant que le visiteur n'a pas donné son accord, aucune iframe n'existe dans la page. Elle n'est créée qu'au moment où il choisit explicitement d'afficher le contenu.
Le HTML
Pour le HTML, je me suis inspiré des shortcodes de WordPress ainsi que des tableaux de configuration. Je suis assez fainéant : moins j'ai de code à écrire, mieux je me porte, et cela limite également les risques d'oubli.
<div class="rgpd-embed" data-provider="youtube" data-code="VIDEO-CODE"></div>
Explication du code HTML
class="rgpd-embed": conteneur du placeholder.data-provider="youtube": indique quel service doit être intégré.data-code="VIDEO-CODE": identifiant du contenu à intégrer. Dans le cas de YouTube, il s'agit du code de la vidéo, par exempleIU0q-RXET8c.
C'est tout. Rien d'autre n'est nécessaire pour intégrer une vidéo provenant de YouTube, Dailymotion ou de tout autre service utilisant un identifiant similaire.
Maintenant, prenons l'exemple d'une page web complète, comme une page GitHub Pages. C'est notamment ce que j'ai fait dans ce guide de jeu vidéo Stellaris : Arbre technologique :
<div class="rgpd-embed" data-fullscreen="1" data-provider="githubpages" data-src="https://exemple.com"></div>
Contrairement à YouTube ou Dailymotion, GitHub Pages ne fournit pas d'identifiant permettant de construire automatiquement l'URL d'intégration. On utilise donc directement l'adresse à charger grâce à data-src.
J'ai également ajouté l'attribut data-fullscreen="1" afin d'afficher un bouton permettant de basculer l'iframe en plein écran. Cet attribut est facultatif et peut être utilisé avec n'importe quel fournisseur si vous en avez besoin.
Le CSS
Ce CSS est facultatif au fonctionnement du système. Je le fournis simplement afin que le tutoriel soit complet et directement utilisable. Vous pouvez le modifier, l'adapter à votre charte graphique ou même le remplacer entièrement si vous le souhaitez.
/* ========================================================================
Vie privée (RGPD) Placeholder
======================================================================== */
.rgpd-embed {
background: var(--background-color-primary);
border-radius: var(--border-radius-4-sides);
box-shadow: var(--shadow-4-sides);
color: var(--text-color-primary);
margin-bottom: 1em;
min-height: 255.71px;
padding: 1em;
}
.rgpd-embed h3 {
margin-top: 0;
}
.rgpd-placeholder {
box-sizing: border-box;
display: grid;
height: 100%;
padding: 1em;
place-items: center;
text-align: center;
width: 100%;
}
.rgpd-actions {
align-items: center;
display: flex;
gap: 1em;
justify-content: center;
margin-top: .75em;
}
.rgpd-fullscreen {
background: rgba(0, 0, 0, .65);
border: 0;
border-radius: var(--border-radius-4-sides);
color: var(--text-color-primary);
cursor: pointer;
padding: 1em;
position: absolute;
right: 1em;
top: 1em;
z-index: 2;
}
.rgpd-load {
background: #f1d600;
border: 0;
border-radius: var(--border-radius-4-sides);
cursor: pointer;
font-size: var(--font-size-normal);
font-weight: 700;
padding: .5em 1em;
}
.rgpd-remember {
align-items: center;
cursor: pointer;
display: inline-flex;
font-size: var(--font-size-normal);
gap: .5em;
opacity: .85;
user-select: none;
}
.rgpd-remember:hover {
opacity: 1;
}
.rgpd-remember>.fa-square-check {
color: var(--success-color);
}
Le JS
En ce qui concerne le JavaScript, le cœur du système, là aussi je suis allé au plus simple sans pour autant rogner sur la robustesse :
// ========================================================================
// Vie privée (RGPD) Placeholder
// ========================================================================
document.addEventListener('DOMContentLoaded', () => {
const KEY = 'thirdparty_allow';
const CODEPEN_USER = 'XXXXX';
// prefs en mémoire (évite JSON.parse à chaque clic)
let prefs = (() => {
try {
return JSON.parse(localStorage.getItem(KEY)) || {};
} catch {
return {};
}
})();
const savePrefs = () => localStorage.setItem(KEY, JSON.stringify(prefs));
const PROVIDERS = {
codepen: {
label: 'CodePen',
src: ({
code,
tab = 'result',
editable = 'true'
}) => `https://codepen.io/${CODEPEN_USER}/embed/${encodeURIComponent(code)}`
+ `?default-tab=${encodeURIComponent(tab)}`
+ `&editable=${encodeURIComponent(editable)}`
},
dailymotion: {
label: 'Dailymotion',
src: ({ code }) =>
`https://geo.dailymotion.com/player.html?video=${encodeURIComponent(code)}`
},
githubpages: {
label: 'GitHub Pages'
},
youtube: {
label: 'YouTube',
src: ({ code }) =>
`https://www.youtube.com/embed/${encodeURIComponent(code)}`
}
};
const getProvider = (box) => PROVIDERS[box.dataset.provider];
const hasFullscreen = (box) => box.dataset.fullscreen === '1';
const updateRememberUI = (box, active) => {
const icon = box.querySelector('.rgpd-icon');
if (!icon) return;
icon.classList.toggle('fa-square', !active);
icon.classList.toggle('fa-square-check', active);
};
const ensurePlaceholder = (box) => {
if (box.querySelector('.rgpd-placeholder')) return;
const p = getProvider(box);
const label = p?.label || (box.dataset.provider || '');
box.insertAdjacentHTML('afterbegin', `
<div class="rgpd-placeholder">
<h3>${t('rgpd.embed.title')} <span class="rgpd-provider-name-title">${label}</span></h3>
<p>${t('rgpd.embed.text')}</p>
<div class="rgpd-actions">
<button type="button" class="rgpd-load">${t('rgpd.embed.show')}</button>
<div class="rgpd-remember" role="button" tabindex="0">
<span aria-hidden="true" class="rgpd-icon fa-regular fa-square fa-lg"></span>
<span class="rgpd-text">
${t('rgpd.embed.remember')}
<span class="rgpd-provider-name-checkbox">${label}</span>
</span>
</div>
</div>
</div>
`);
};
// C'est ici que l'iframe est réellement créée
const buildIframe = (src, providerLabel) => {
const iframe = document.createElement('iframe');
iframe.src = src;
iframe.title = `${t('rgpd.embed.iframe')} ${providerLabel}`;
iframe.loading = 'lazy';
iframe.setAttribute(
'allow',
'accelerometer; encrypted-media; fullscreen; gyroscope; picture-in-picture'
);
return iframe;
};
const buildSrc = (box) => {
if (box.dataset.src) return box.dataset.src;
const p = getProvider(box);
const code = box.dataset.code;
if (!p || !code) return '';
return p.src({
code,
tab: box.dataset.tab,
editable: box.dataset.editable
});
};
const updateFullscreenButtons = () => {
document.querySelectorAll('.rgpd-fullscreen').forEach((btn) => {
const box = btn.closest('.embed-container');
const isFullscreen = document.fullscreenElement === box;
btn.innerHTML = isFullscreen
? '<span class="fa-solid fa-2x fa-compress"></span>'
: '<span class="fa-solid fa-2x fa-expand"></span>';
const label = isFullscreen
? t('ui.fullscreen.hide')
: t('ui.fullscreen.show');
btn.setAttribute('aria-label', label);
btn.setAttribute('title', label);
});
};
const buildFullscreenButton = (box) => {
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'rgpd-fullscreen';
btn.addEventListener('click', () => {
if (document.fullscreenElement === box) {
document.exitFullscreen?.();
} else {
box.requestFullscreen?.();
}
});
return btn;
};
const loadBox = (box) => {
const p = getProvider(box);
const src = buildSrc(box);
if (!src) return;
box.querySelector('.rgpd-placeholder')?.remove();
box.classList.remove('rgpd-embed');
box.classList.add('embed-container');
const iframe = buildIframe(
src,
p?.label || box.dataset.provider || ''
);
box.appendChild(iframe);
if (hasFullscreen(box)) {
box.appendChild(buildFullscreenButton(box));
}
updateFullscreenButtons();
};
const initBox = (box) => {
const provider = box.dataset.provider;
if (!provider) return;
ensurePlaceholder(box);
const active = prefs[provider] === true;
updateRememberUI(box, active);
if (active) loadBox(box);
};
const initAll = () => {
document.querySelectorAll('.rgpd-embed').forEach(initBox);
};
document.addEventListener(
'fullscreenchange',
updateFullscreenButtons
);
// Le DOM est prêt, on initialise les placeholders
initAll();
// Gestion des clics
document.addEventListener('click', (e) => {
const loadBtn = e.target.closest('.rgpd-load');
const rememberBtn = e.target.closest('.rgpd-remember');
if (!loadBtn && !rememberBtn) return;
const box = e.target.closest('.rgpd-embed');
if (!box) return;
const provider = box.dataset.provider;
if (!provider) return;
if (rememberBtn) {
prefs[provider] = !prefs[provider];
savePrefs();
updateRememberUI(box, prefs[provider] === true);
return;
}
if (loadBtn) {
loadBox(box);
}
});
// Support du clavier sur "Toujours autoriser"
document.addEventListener('keydown', (e) => {
if (e.key !== 'Enter' && e.key !== ' ') return;
const rememberBtn = e.target.closest?.('.rgpd-remember');
if (!rememberBtn) return;
const box = rememberBtn.closest('.rgpd-embed');
if (!box) return;
e.preventDefault();
const provider = box.dataset.provider;
if (!provider) return;
prefs[provider] = !prefs[provider];
savePrefs();
updateRememberUI(box, prefs[provider] === true);
});
});
Explication du JS
PROVIDERS: liste les services pris en charge (CodePen, Dailymotion, GitHub Pages, YouTube, etc.) ainsi que la manière de construire leur URL d'intégration.ensurePlaceholder(): crée dynamiquement le placeholder affiché avant le consentement de l'utilisateur.buildSrc(): construit automatiquement l'URL d'intégration à partir des informations fournies dans le HTML. Cela permet d'utiliser une syntaxe simplifiée commedata-code="IU0q-RXET8c"au lieu d'avoir à écrire l'URL complète.buildIframe(): crée l'élément<iframe>. C'est ici que réside le principal intérêt de ce système : tant que cette fonction n'est pas appelée, aucune iframe n'existe dans la page.loadBox(): remplace le placeholder par l'iframe après l'action de l'utilisateur.prefs,savePrefs()etlocalStorage: mémorisent les préférences du visiteur afin d'éviter de lui demander son consentement à chaque visite lorsqu'il coche la caseToujours autoriser le service ....data-fullscreen="1": ajoute un bouton permettant de basculer l'iframe en plein écran. Cette fonctionnalité est facultative et indépendante du fournisseur utilisé.
Le point le plus important à retenir est que l'iframe n'est jamais présente dans le code HTML initial. Elle est créée uniquement après une action explicite du visiteur. Cela garantit qu'aucune requête n'est envoyée vers le service tiers avant son consentement.
Actuellement, mon script ne gère que 4 services tiers, mais vous pouvez facilement en ajouter d'autres selon vos besoins.
Ce script utilise mon système de traduction. Si vous ne gérez qu'une seule langue, vous pouvez remplacer les appels à t() par du texte brut. Les clés utilisées sont fournies ci-dessous :
L'internationalisation (i18n)
{
"en": {
"rgpd.embed.iframe": "Displaying external content hosted by",
"rgpd.embed.remember": "Always allow the service",
"rgpd.embed.show": "Show content",
"rgpd.embed.text": "To respect your privacy, click “Show content” to load it.",
"rgpd.embed.title": "External content hosted by",
"ui.fullscreen.hide": "Exit fullscreen",
"ui.fullscreen.show": "Show in fullscreen"
},
"fr": {
"rgpd.embed.iframe": "Affichage du contenu externe hébergé par",
"rgpd.embed.remember": "Toujours autoriser le service",
"rgpd.embed.show": "Afficher le contenu",
"rgpd.embed.text": "Pour respecter votre confidentialité, cliquez sur “Afficher le contenu” pour le charger.",
"rgpd.embed.title": "Contenu externe hébergé par",
"ui.fullscreen.hide": "Quitter le plein écran",
"ui.fullscreen.show": "Afficher en plein écran"
}
}
Le résultat
Une fois le système mis en place, le visiteur voit un placeholder à la place du contenu externe. L'iframe n'est créée qu'après une action explicite de sa part. Libre à vous de le vérifier à l'aide des outils de développement de votre navigateur (DevTools), je vous y encourage même :
Exemple avec YouTube
Exemple avec GitHub Pages
Cet exemple utilise également l'attribut data-fullscreen="1" afin d'ajouter un bouton permettant de basculer le contenu en plein écran.
Commentaires