Pour faciliter les développements Javascript Vanilla, j'utilise un certain nombre de polyfills, fonctions et variables qui me sont aujourd'hui indispensables, alors je partage !
Pour de nombreux navigateurs, les éléments NodeList et HTMLCollection retournés par un document.querySelectorAll() n'héritent pas de la méthode forEach, c'est bien dommage.
Pour boucler dans des NodeList et HTMLCollection, j'utilise un petit polyfill très simple :
Pour détecter la fin d'une animation ou d'une transition CSS, chaque navigateur a sa propre méthode basée sur leurs préfixes (-webkit-, o, moz).
Pour simplifier l'appel à ces événements, j'utilise la petite fonction suivante :
//animation and transion end event
function whichAnimationEvent(){var n,i=document.createElement("fakeelement"),t={animation:"animationend",OAnimation:"oAnimationEnd",MozAnimation:"animationend",WebkitAnimation:"webkitAnimationEnd"};for(n in t)if(void 0!==i.style[n])return t[n]}function whichTransitionEvent(){var n,i=document.createElement("fakeelement"),t={transition:"transitionend",OTransition:"oTransitionEnd",MozTransition:"transitionend",WebkitTransition:"webkitTransitionEnd"};for(n in t)if(void 0!==i.style[n])return t[n]}var animationEvent=whichAnimationEvent(),transitionEvent=whichTransitionEvent();
Pour détecter la fin d'une transition, une seule syntaxe commune :
ELT.addEventListener(transitionEvent, step2);
function step2(event) {
if(event.propertyName == 'transform'){//Il vaut toujours mieux tester la propriété
ELT.removeEventListener(transitionEvent, step2);//Et supprimer l'événement
...ACTIONS...
}
};
closest
Très pratique pour vérifier si un élément a bien un parent particulier.
Mais IE11- et Edge 12 à 14 ne comprennent pas la syntaxe.
Ce polyfill règle le problème :
On peut ainsi par exemple appliquer un onclick sur le document et cibler uniquement un élément et ses enfants (ici .push-video) :
document.addEventListener('click', function (e) {
if (e.target.closest('.push-video')) {
...ACTIONS...
return;//return pour arrêter le if
}
});
Ou encore retrouver un élément présent au-dessus de la target d'une action (ici la target est pushVideo et l'élément à retrouver un .accordion-desc :
var elem = pushVideo.closest('.accordion-desc');
setTimeout(function(){
acc.slideDown(elem);
}, 500);
SlideUp et slideDown comme jQuery
Cette fonctionnalité jQuery très pratique pour les accordéons n'existe pas de base en Javascript Vanilla mais avec cette petite fonction, c'est un jeu d'enfant de l'implémenter :
function slideDown(elem) {
var h = 0;
elem.children.forEach(function(elt){
h = h + elt.getBoundingClientRect().height;
});
elem.style.maxHeight = h + 'px';
}
function slideUp(elem) {
elem.style.maxHeight = '0';
}
Il faut juste ajouter une transition à la propriété max-height des éléments à cibler.
.element{
transition: max-height $speed $ease;
}
triggerevent
Pour déclencher l'action d'un élément. Cette fonction est fondamentale car elle est utilisée dans plusieurs autres snippets, dont les indispensables onResizeEnd et onScrollEnd :
//triggerEvent
function triggerEvent(el, type){
if ('createEvent' in document) {
var e = document.createEvent('HTMLEvents');
e.initEvent(type, false, true);
el.dispatchEvent(e);
}
else {
var e = document.createEventObject();
e.eventType = type;
el.fireEvent('on' + e.eventType, e);
}
}
Syntaxe d'appel au onclick d'un élément :
triggerEvent(ELEMENT, 'click');
scrollEnd
Dans certains cas, il n'est pas utile de surcharger la machine avec des appels en boucle tout au long du scroll de la page.
L'événement onScrollEnd est là pour nous :
//wait until scroll is done
window.addEventListener('scroll', function() {
if(this.scrollTO) clearTimeout(this.scrollTO);
this.scrollTO = setTimeout(function() {
triggerEvent(this, 'scrollEnd');
}, 500);
}, false);
Un des éléments indispensable au responsive design, c'est le onResize de la fenêtre.
Il est rarement nécessaire de déclencher des événements tout au long du resize, il vaut mieux attendre la fin du resize :
//wait until resize is done
window.addEventListener('resize', function() {
if(this.resizeTO) clearTimeout(this.resizeTO);
this.resizeTO = setTimeout(function() {
triggerEvent(this, 'resizeEnd');
}, 500);
});
La propriété CSS object-fit permet de forcer une image à remplir son conteneur, comme une image de background. C'est très pratique pour le responsive mais IE11< et Edge 12-15 ne le comprennent pas.
Pas de panique! avec cette petite fonction, tout rentre dans l'ordre pour ces récalcitrants :
function objectFit(){
if('objectFit' in document.documentElement.style === false) {
var liste = document.querySelectorAll('.js-object-fit');
liste.forEach(function(elt){
var imgUrl = elt.querySelector('img').getAttribute('src');
if (imgUrl) {
elt.style.backgroundImage = 'url(' + imgUrl + ')';
elt.style.backgroundPosition = (elt.getAttribute('data-bg-pos')) ? elt.getAttribute('data-bg-pos') : '50% 50%';
elt.style.backgroundRepeat = 'no-repeat';
elt.style.backgroundSize = 'cover';
elt.classList.add('compat-object-fit');
}
});
}
};
objectFit();
Il faut juste appliquer la classe js-object-fit aux blocs et rajouter la classe .compat-object-fit à sa CSS :
.compat-object-fit{
img{
opacity: 0;
}
}
Il faut aussi penser à appeler la fonction objectFit(); lorsqu'on charge du contenu dynamiquement via AJAX.
AJAX
Pour éviter d'avoir à ré-écrire à chaque fois les appels AJAX, j'ai fait cette petite fonction facile à utiliser :
//ajaxCall
function ajaxCall(url, params, method, callback){
var xhr = new XMLHttpRequest();
var hurle = (method.toLowerCase() === 'get') ? url + '?' + params : url;
xhr.open(method, hurle);
var postparams = (method.toLowerCase() === 'post') ? params : null;
xhr.send(postparams);
xhr.onreadystatechange = function () {
var DONE = 4; // readyState 4 means the request is done.
var OK = 200; // status 200 is a successful return.
if (xhr.readyState === DONE) {
if (xhr.status === OK) {
//console.log(xhr.responseText); // 'This is the returned text.'
callback(xhr.responseText);
}
else {
console.log('Error: ' + xhr.status); // An error occurred during the request.
}
}
};
}
Appel à la fonction, ici avec un retour JSON :
function callbackWelcomeBanner(data){
var json = JSON.parse(data);
var arr = json.welcome;
for (var i=0; i<arr.length; i++){
var htmlToWrite = htmlBannerTemplate.replace('XXX_image_XXX', arr[i].image).replace('XXX_title_XXX', arr[i].title).replace('XXX_desc_XXX', arr[i].desc);
cib.innerHTML += htmlToWrite;
}
objectFit();
}
ajaxCall('json/welcome-banner.txt', '', 'get', callbackWelcomeBanner);
classList et RequestAnimationFrame
Si on veut cibler les vieux navigateurs IE9 et inférieurs qui ne comprennent ni classList ni RequestAnimationFrame,
ces polyfills sont là pour nous aider :
On peut ainsi utiliser classList et RequestAnimationFrame en toute quiétude.
Variables utiles pour responsive
Un des éléments indispensable au responsive design, c'est le onResize de la fenêtre.
Il est rarement nécessaire de déclencher des événements tout au long du resize, il vaut mieux attendre la fin du resize :
var w = window,
d = document,
e = d.documentElement,
g = d.getElementsByTagName('body')[0];
var windowHeight = w.innerHeight||e.clientHeight||g.clientHeight;
var windowWidth = w.innerWidth||e.clientWidth||g.clientWidth;
var mobileLimit = 650;
var desktopLimit = 1024;
var isMobileContext = (windowWidth > mobileLimit) ? false : true;
window.addEventListener('resizeEnd', function() {
windowHeight = w.innerHeight||e.clientHeight||g.clientHeight;
windowWidth = w.innerWidth||e.clientWidth||g.clientWidth;
isMobileContext = (windowWidth > mobileLimit) ? false : true;
});
Browsers sniffing
Oui, je sais, c'est mal... Mais c'est parfois la seule solution pour résoudre un problème spécifique à Safari, MacIntosh ou Edge (et c'est pas comme si c'était si rare !).