Montrer le contenu des notes de bas de page sans faire des allers-retours vers le bas de la page.
En lisant un tweet de @nhoizey, j’ai découvert un plugin jQuery1 qui détecte les liens vers les notes de bas de page et affiche le contenu desdites notes dans un popover
Je me suis donc essayé à créer une version «light» de ce plugin sans la dépendance à jQuery2. Les options et animations présentes dans le plugin ne sont pas reproduites, si elles vous sont nécessaires…
Le script détecte les liens avec un attribut rel="footnote"
, récupère le contenu de la note correspondante et ajoute ce contenu à l’ancêtre de type block 3 le plus proche.
J’ai utilisé querySelector
et addEventListener
, le script est donc compatible avec les navigateurs qui les supportent : IE>8
Ce script est basé sur le code généré par Markdown Extra pour les notes de bas de page et ne conviendra peut-être pas à toutes les situations.
Le code utilisé pour les liens est :
<p>
[...] plugin jQuery <sup id="fnref:1"><a href="#fn:1" rel="footnote">1</a></sup> qui [...]
</p>
Le script se base sur le sélecteur [rel=footnote]
pour trouver les liens. Si vous utilisez une classe (ou autre) il faudra modifier le script en conséquence…
Le code des notes de bas de page généré par Markdown Extra est :
<div class="footnotes">
<hr>
<ol>
<li id="fn:1">
<p>bigfoot <a href="http://cmsauve.com/labs/bigfoot/"> A jQuery plugin for empowering footnotes</a> <a href="#fnref:1" rev="footnote">↩</a></p>
</li>
</ol>
</div>
Ici, seul le l’id (ici : fn:1
) est utilisé pour cibler l’élément et en récupérer le contenu, aucune modification ne devrait être nécessaire.
Mis à part les positionnements (position relative et absolute, top, left etc.) aucun style n’est appliqué par le script. Un peu de CSS est donc nécessaire, à adapter à vos besoins, par exemple :
/** .js-footnote est la classe appliquée à la div «clone» de la note
cette div est positionnée en absolute par le script
*/
.js-footnote{
z-index: 2 ;
padding: 8px;
min-width: calc(75% - 32px);
max-width: calc(100% - 32px);
background: #ececec;
box-shadow: 0 0 4px #444;
}
/* suppriment les margin top et bottom du premier et dernier enfant de .js-footnote */
.js-footnote :first-child{
margin-top: 0;
}
.js-footnote :last-child{
margin-bottom: 0;
}
/* cache le lien retour de la note */
.js-footnote a[rev]{
display: none;
}
/**
l'attribut data-footnote est ajouté par le script afin de vérifier qu'un lien a déjà été cliqué
*/
[data-footnote]{
position: relative;
}
/* le pseudo élément :before est stylé et placé au dessus du contenu du lien pour signifier la fermeture de la note au click */
[data-footnote]:before{
position: absolute;
top: 0;
left: 0;
z-index: 3;
min-width: 1.5em;
min-height: 1.5em;
border-radius: .25em;
background: #844;
color: #fff;
content: 'x';
text-align: center;
}
TODO
Revoir le placement de la div.js-footnote lorsque le contenu de la note est court (parfois éloigné du lien)
Revoir le placement lors d’un resize , les notes sont (honteusement) cachées lorsque la fenêtre est redimensionnée en largeur…
Le script: footnotes.js
Je suis preneur de suggestions ou améliorations à apporter à ce script… via Twitter @eQRoeil
Un exemple avec l'article de Nicolas Hoizey sur 24joursdeweb4
J'ai créé un snippet dans les DevTools de Chrome pour tester footnote.js sur cet article.
Les liens vers les notes de bas de page sont de la forme <a href="#cite-..."
, il faut donc changer le sélecteur comme ceci var notes = document.querySelectorAll('a[href^="#cite"]');
pour cibler ces liens (liens dont l'attribut href commence par #cite)
L'autre modification effectuée est liée aux styles ; nécessaire pour les DevTools, il est préférable d'appliquer ces styles via CSS (.js-footnote
)
Le résultat (basique) obtenu après ces modifications
/*! jsFootnotes 28/12/2013 http://soqr.fr/footnotes/
* @author : Pascal Cauhépé @eQRoeil
* @desc : add the footnote content next to the link to the footnote Chrome DevTools snippet
*/
;(function jsFootnotes() {
if (!('querySelector' in document && 'addEventListener' in window)) return;
/*
* change links selector here if needed...
*/
/*
===
a[rel="footnote"] est changé par a[href^="#cite"]
===
*/
var notes = document.querySelectorAll('a[href^="#cite"]');
/**
* find closest block parent of a[rel="footnote"] a[href^="#cite"]
*/
function findBlockParent(el) {
while (el.parentNode) {
el = el.parentNode;
if (el.tagName === 'P' || el.tagName === 'LI' || el.tagName === 'BLOCKQUOTE' || el.tagName === 'DIV' || el.tagName === 'BODY') return el;
}
return null;
}
/**
* remove .js-footnote
*/
function removeNotes() {
var footnotes = document.querySelectorAll('.js-footnote');
for (var i = 0; i < footnotes.length; i++) {
var f = footnotes[i];
if (f.parentNode) {
f.parentNode.removeChild(f);
}
}
}
/**
* add div.js-footnote (with footnote content) to the closest parent
*/
function showFootnote(noteLink) {
var footnoteId = noteLink.hash.substr(1);
var footnoteContent = document.getElementById(footnoteId).innerHTML;
var blck = findBlockParent(noteLink);
if (getComputedStyle(blck, null).getPropertyValue("position") == "static") {
blck.style.position = 'relative';
}
var jsFootnoteTop = Math.round(noteLink.getBoundingClientRect().top - blck.getBoundingClientRect().top + noteLink.getBoundingClientRect().height);
var jsFootnoteRight = 8;
if (noteLink.getBoundingClientRect().left > 0.75 * blck.getBoundingClientRect().width) {
jsFootnoteRight = Math.round(blck.getBoundingClientRect().width - noteLink.getBoundingClientRect().left);
}
/*
===
J'ai ajouté un background-color et un border dans le script pour une utilisation dans le snippet Chrome DevTools
===
*/
blck.insertAdjacentHTML('beforeend', '<div class="js-footnote" style="position:absolute;top:' + jsFootnoteTop + 'px;right:' + jsFootnoteRight + 'px;background-color:#fff;border: 2px dotted gray;">' + footnoteContent + '</div>');
noteLink.setAttribute("data-footnote", "shown");
}
function removeOrShowFootnote(ev) {
ev.preventDefault();
removeNotes();
var noteLink = this;
// if already opened : remove
if (noteLink.getAttribute('data-footnote') && noteLink.getAttribute('data-footnote') == 'shown') {
noteLink.removeAttribute('data-footnote');
removeNotes();
} else {
// remove data-footnote attribute when not removed (if a new note is opened when another not closed)
if (document.querySelector('[data-footnote]')) {
document.querySelector('[data-footnote]').removeAttribute('data-footnote');
}
// show it
showFootnote(noteLink);
}
}
if (notes && notes.length > 0) {
for (var i = 0; i < notes.length; i++) {
notes[i].addEventListener('click', removeOrShowFootnote, false);
}
}
/*
* When window is resized and width changed : remove opened footnotes
* __bad__ solution (should place footnotes at the right place instead)
*/
var bodyW = document.documentElement.getBoundingClientRect().width;
window.addEventListener('resize', function() {
if (document.documentElement.getBoundingClientRect().width !== bodyW) {
removeNotes();
if (document.querySelector('[data-footnote]')) {
document.querySelector('[data-footnote]').removeAttribute('data-footnote');
}
}
}, false);
})();
et Nicolas semble un peu gêné aussi;-) ↩
position: relative
est ajouté à l’ancêtre de type block si celui-ci n’est pas positionné, le contenu de la note est inséré dans une div.js-footnote en position: absolute
↩
l’article avec le plus grand nombre de footnotes de l’histoire des internets ↩