RegalSnippets

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 !

Github repository

forEach NodeList et HTMLCollection

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 :

//forEach NodeList & HTMLCollection polyfill NodeList.prototype.forEach||(NodeList.prototype.forEach=Array.prototype.forEach), HTMLCollection.prototype.forEach||(HTMLCollection.prototype.forEach=Array.prototype.forEach);

C'est grace à ce polyfill que je peux facilement appliquer des Listeners et c'est la base :

document.querySelectorAll('.flickity-carousel').forEach(function(elt){     elt.addEventListener('click', FUNCTION); }

onAnimationEnd et onTransitionEnd

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 :

Element.prototype.matches||(Element.prototype.matches=Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector),Element.prototype.closest||(Element.prototype.closest=function(e){var t=this;if(!document.documentElement.contains(t))return null;do{if(t.matches(e))return t;t=t.parentElement||t.parentNode}while(null!==t&&1==t.nodeType);return null});

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);

Syntaxe d'appel au scrollEnd :

window.addEventListener('scrollEnd', function() {     console.log("scrollEnd"); });

resizeEnd

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); });

Syntaxe d'appel au resizeEnd :

window.addEventListener('resizeEnd', function() {     //console.log("resizeEnd"); });

objectfit

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 :

//classList polyfill /for IE9-) !function(){function t(t){this.el=t;for(var n=t.className.replace(/^\s+|\s+$/g,"").split(/\s+/),i=0;i<n.length;i++)e.call(this,n[i])}function n(t,n,i){Object.defineProperty?Object.defineProperty(t,n,{get:i}):t.__defineGetter__(n,i)}if(!("undefined"==typeof window.Element||"classList"in document.documentElement)){var i=Array.prototype,e=i.push,s=i.splice,o=i.join;t.prototype={add:function(t){this.contains(t)||(e.call(this,t),this.el.className=this.toString())},contains:function(t){return-1!=this.el.className.indexOf(t)},item:function(t){return this[t]||null},remove:function(t){if(this.contains(t)){for(var n=0;n<this.length&&this[n]!=t;n++);s.call(this,n,1),this.el.className=this.toString()}},toString:function(){return o.call(this," ")},toggle:function(t){return this.contains(t)?this.remove(t):this.add(t),this.contains(t)}},window.DOMTokenList=t,n(Element.prototype,"classList",function(){return new t(this)})}}(); //RequestAnimationFrame Polyfill !function(){for(var n=0,i=["ms","moz","webkit","o"],e=0;e<i.length&&!window.requestAnimationFrame;++e)window.requestAnimationFrame=window[i[e]+"RequestAnimationFrame"],window.cancelAnimationFrame=window[i[e]+"CancelAnimationFrame"]||window[i[e]+"CancelRequestAnimationFrame"];window.requestAnimationFrame||(window.requestAnimationFrame=function(i){var e=(new Date).getTime(),a=Math.max(0,16-(e-n)),o=window.setTimeout(function(){i(e+a)},a);return n=e+a,o}),window.cancelAnimationFrame||(window.cancelAnimationFrame=function(n){clearTimeout(n)})}();

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 !).

//browsers detection var isiPad = navigator.userAgent.match(/iPad/i) != null; var isSafari = /constructor/i.test(window.HTMLElement) || (function (p) { return p.toString() === "[object SafariRemoteNotification]"; })(!window['safari'] || (typeof safari !== 'undefined' && safari.pushNotification)); var isMac = navigator.platform.indexOf('Mac') > -1; var isIEorEDGE = navigator.appName == 'Microsoft Internet Explorer' || (navigator.appName == "Netscape" && navigator.appVersion.indexOf('Edge') > -1);

Tout en un

J'ai regroupé tous ces petits morceaux de code dans un fichier qu'il ne vous reste plus qu'à télécharger !
snippets.js