PrefixFinder.
De quel préfixe vendeur votre visiteur a-t-il besoin ?

Le , par Bovino, Rédacteur
PrefixFinder
De quel préfixe vendeur votre visiteur a-t-il besoin ?

Mise à jour du 17/12/2013

Le script vient d'être mis à jour pour corriger un bogue de Safari (au moins la version 5.1.7 pour Windows) qui ne permet pas de lister les propriétés de l'objet HTMLElement.style.
La méthode pour récupérer le préfixe JavaScript a donc été réécrite (et du coup optimisée).

D'autre part, il est tenu compte désormais que certaines valeurs de propriétés peuvent nécessiter un préfixe. Si la syntaxe non préfixée de la valeur n'est pas trouvée, une recherche est faite sur une valeur préfixée (tester par exemple display avec la valeur box).

Enfin, il est possible d'intégrer une méthode supplémentaire au constructeur pour la gestion des at-rules, voici le code de cette méthode (à ajouter dans le prototype, voir la page d'exemple) :
Code javascript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
getPrefixedAt: function(prop){ 
    var testStyle = document.createElement('style'); 
    var myparent = document.getElementsByTagName('script')[0].parentNode; 
    myparent.appendChild(testStyle); 
    var testing = testStyle.sheet; 
    try{ 
        testing.insertRule(prop, 0); 
        myparent.removeChild(testStyle); 
        return prop.split(/[\s\{]/)[0]; 
    } 
    catch(e){ 
        try{ 
            prop = prop.replace(/^@/, '@'+this.prefixe.css); 
            testing.insertRule(prop, 0); 
            myparent.removeChild(testStyle); 
            return prop.split(/[\s\{]/)[0]; 
        } 
        catch(e){ 
            myparent.removeChild(testStyle); 
            return false; 
        } 
    } 
}
Attention : pour tester une at-rule, il est nécessaire de passer en paramètre une syntaxe minimale valide.
Par exemple, pour @keyframes, un nom est requis puis des accolades, il faudra donc tester @keyframes foo{} qui renverra @keyframes, @-webkit-keyframes ou false en fonction du navigateur.
----------

Présentation
PrefixFinder est un petit script utilitaire permettant de gérer facilement les préfixes vendeurs liés au navigateur de votre visiteur lorsque vous devez manipuler les styles CSS.

Fonctionnalités
PrefixFinder propose diverses fonctions utiles :
  • PrefixFinder.prefixe contient un objet composé de deux propriétés (.css et .js) indiquant le préfixe CSS et JavaScript du navigateur ;
  • PrefixFinder.getPrefixedProp(propriete) est une méthode retournant un objet composé de deux propriétés (.css et .js) indiquant pour la propriété CSS propriete sa syntaxe CSS et JavaScript pour le navigateur de l'utilisateur ;
  • PrefixFinder.getPrefixedPropValue(propriete, valeur) est une méthode retournant un objet composé de trois propriétés (.css, .js et .valeur) indiquant pour la propriété CSS propriete si la valeur valeur est reconnue pour le navigateur de l'utilisateur et s'il faut un préfixe pour cette valeur ;
  • PrefixFinder.listPrefixes contient un objet des valeurs déjà cherchées, les clés correspondant à la notation JavaScript non préfixée de la propriété ;
  • enfin, deux méthodes .camelize() et .deCamelize() permettent de retourner la notation camelCase de la chaine passée en paramètre ou l'inverse.


Utilisation
Pour mettre en place PrefixFinder sur votre site, il suffit d'intégrer le script dans votre page (si possible dans le <head> (le script n'a pas besoin que la page soit complètement chargée pour fonctionner) puis de définir une instance :
Code javascript : Sélectionner tout
var prefixer = new PrefixFinder();

Notes
Certaines propriétés CSS implémentent différemment différentes valeurs. Par exemple, la propriété transform peut nécessiter un préfixe pour les transformations 3D mais pas pour les transformations 2D, la méthode .getPrefixedPropValue() tient compte de cela.
Internet Explorer 8 et inférieurs renvoie une erreur si l'on essaye d'affecter une valeur non valide pour une propriété, c'est pourquoi le code comporte des instructions try catch.
Les préfixes JavaScript, contrairement aux préfixes CSS, ne sont pas normalisés et les navigateurs n'utilisent pas la même règle : certains restent conformes à la règle de transformation de nom CSS <=> JavaScript (lowerCamelCase) alors que d'autres préfèrent indiquer qu'il s'agit d'un préfixe en distinguant les noms lowerCamelCase pour les propriétés standards et les nom UpperCamelCase pour les propriétés préfixées, c'est pourquoi le script fait une recherche du préfixe réel et non dans une liste figée.
Si aucun préfixe n'a été trouvé, on considère qu'il s'agit d'Internet Explorer.

Compatibilité
Sauf erreur de ma part, le script est compatible tous navigateurs et toutes versions.

Pourquoi intégrer les notations CSS ?
S'agissant d'un script JavaScript, la manipulation des styles se fera en JavaScript via l'objet style des éléments HTML. De ce fait, seules les propriétés en notation JavaScript sont supposées être utiles.
Au moins deux cas de figure peuvent cependant nécessiter l'utilisation des propriétés en notation CSS.
  • Lorsque l'on modifie plusieurs propriétés de style simultanément, il est conseillé, pour des raisons de performance, d'utiliser la propriété cssText et non chaque propriété individuelle. Or cssText prend comme valeur une chaine dans laquelle la notation des styles est celle de CSS.
  • On peut prévoir d'utiliser ce script comme préprocesseur des feuilles de style en parsant l'objet document.styleSheets par exemple et en réécrivant chaque règle si besoin avec les valeurs préfixées. Cela permettra de simplifier l'écriture des feuilles de style en n'écrivant que les propriétés standards et en adaptant celles nécessitant un préfixe directement dans la page Web.


Exemple d'utilisation
Vous pouvez voir sur cette page (regarder la source) une mise en oeuvre de ce script.

N'hésitez pas à poster vos commentaires à la suite voire à proposer des améliorations au code ou à indiquer d'éventuels bogues.
N'hésitez pas non plus à demander à la suite des explications sur des parties du code que vous que vous ne comprendriez pas (que ce soit la syntaxe ou l'utilité ).

Code
Code javascript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
var PrefixFinder = function(){ 
    this.init(); 
}; 
PrefixFinder.prototype = { 
    navFullySupported: true, 
    listPrefixes: {}, 
    prefixe: {}, 
    tester: document.createElement('div').style, 
    init: function(){ 
        if(window.getComputedStyle){ 
            var allStyles = window.getComputedStyle(document.documentElement, ''), 
                    pre = (Array.prototype.slice.call(allStyles).join('|')); 
            var tmp = pre.match(/\|-([^-]+)-([^|]+)\|/) || (allStyles.OLink === '' && ['', 'o']); 
            this.prefixe.css = '-'+tmp[1]+'-'; 
            tmp[2] = this.camelize(tmp[2], true); 
            if((tmp[1] + tmp[2]) in this.tester){ 
                this.prefixe.js = tmp[1]; 
                return true; 
            } 
            tmp[1] = this.camelize(tmp[1], true); 
            if((tmp[1] + tmp[2]) in this.tester){ 
                this.prefixe.js = tmp[1]; 
                return true; 
            } 
        } 
        this.navFullySupported = false; 
        this.prefixe = {css: '-ms-', js: 'ms'}; 
    }, 
    camelize: function(st, full){ 
        st = st.replace(/-([a-z])/g, function(reg, camel){ 
            return camel.toUpperCase(); 
        }); 
        if(full){ 
           return st.replace(st.charAt(0), st.charAt(0).toUpperCase()); 
        } 
        return st 
    }, 
    deCamelize: function(st){ 
        return st.replace(/[A-Z]/g, function(camel, pos){ 
            return (pos == 0 ? '' : '-') + camel.toLowerCase(); 
        }); 
    }, 
    getPrefixedProp: function(prop){ 
        prop = this.deCamelize(prop); 
        var jsProp = this.camelize(prop); 
        if(this.listPrefixes[jsProp]){ 
            return this.listPrefixes[jsProp]; 
        } 
        if(jsProp in this.tester){ 
            this.listPrefixes[jsProp] = {css: prop, js: jsProp}; 
            return {css: prop, js: jsProp}; 
        } 
        var jsPropPref = this.prefixe.js + jsProp.replace(jsProp.charAt(0), jsProp.charAt(0).toUpperCase()); 
        if(jsPropPref in this.tester){ 
            this.listPrefixes[jsProp] = {css: this.prefixe.css + prop, js: jsPropPref}; 
            return {css: this.prefixe.css + prop, js: jsPropPref}; 
        } 
        this.listPrefixes[jsProp] = {css: false, js: false}; 
        return {css: false, js: false}; 
    }, 
    getPrefixedPropValue: function(prop, valeur){ 
        prop = this.deCamelize(prop); 
        var tester = document.createElement('div').style; 
        var jsProp = this.camelize(prop); 
        test:if(jsProp in tester){ 
            try { 
                tester[jsProp] = valeur; 
            } catch (e) { 
                break test; 
            } 
            if(tester[jsProp] != ''){ 
                return {css: prop, js: jsProp, valeur: tester[jsProp]}; 
            } 
        } 
        jsProp = this.prefixe.js + this.camelize(prop, true); 
        test:if(jsProp in tester){ 
            try { 
                tester[jsProp] = valeur; 
            } catch (e) { 
                break test; 
            } 
            if(tester[jsProp] != ''){ 
                return {css: this.prefixe.css + prop, js: jsProp, valeur: tester[jsProp]}; 
            } 
        } 
        if(valeur.indexOf(this.prefixe.css) == -1){ 
            return this.getPrefixedPropValue(prop, this.prefixe.css + valeur); 
        } 
        var ret = this.getPrefixedProp(prop); 
        ret.valeur = false; 
        return ret; 
    }, 
    getPrefixedAt: function(prop){ 
        var testStyle = document.createElement('style'); 
        var myparent = document.getElementsByTagName('script')[0].parentNode; 
        myparent.appendChild(testStyle); 
        var testing = testStyle.sheet; 
        try{ 
            testing.insertRule(prop, 0); 
            myparent.removeChild(testStyle); 
            return prop.split(/[\s\{]/)[0]; 
        } 
        catch(e){ 
            try{ 
                prop = prop.replace(/^@/, '@'+this.prefixe.css); 
                testing.insertRule(prop, 0); 
                myparent.removeChild(testStyle); 
                return prop.split(/[\s\{]/)[0]; 
            } 
            catch(e){ 
                myparent.removeChild(testStyle); 
                return false; 
            } 
        } 
    } 
};


Vous avez aimé cette actualité ? Alors partagez-la avec vos amis en cliquant sur les boutons ci-dessous :


 Poster une réponse

Avatar de SylvainPV SylvainPV - Rédacteur/Modérateur https://www.developpez.com
le 02/12/2013 à 18:34
S'agissant d'un script JavaScript, la manipulation des styles se fera en JavaScript via l'objet style des éléments HTML. De ce fait, seuls les propriétés en notation JavaScript sont supposées être utiles.

Rien compris à ce passage
Je crois comprendre qu'on nous demande de passer par l'élément HTML <style>, puis dans la deuxième phrase on nous dit de passer par JavaScript et element.style.property. Alors que les best-practices ne sont en faveur d'aucun de ces deux choix, mais plutôt l'utilisation d'une feuille de style externe importée grâce à une balise <link>
Avatar de Bovino Bovino - Rédacteur https://www.developpez.com
le 02/12/2013 à 18:54
Bah... à l'origine, c'était clair dans ma tête !

Ce que je voulais dire, c'est que dans la plupart des scripts qui manipulent les styles d'un élément, on fait par exemple
Code : Sélectionner tout
1
2
3
HTMLElement.style.display = 'none'; 
HTMLElement.style.opacity = 0; 
HTMLElement.style.backgroundImage = 'img1.png';
et donc que seules les notations JavaScript sont utiles dans ce cas, mais précisément, il est préférable d'écrire plutôt une seule ligne
Code : Sélectionner tout
HTMLElement.style.cssText = ';display: none; opacity: 0; background-image = "img1.png"';
Dans ce cas, la propriété .css de l'objet retourné pourra être utile.

l'utilisation d'une feuille de style externe importée grâce à une balise <link>

Oui, pour importer une feuille de style, mais pas pour modifier dynamiquement le style d'un élément particulier.
Avatar de Bovino Bovino - Rédacteur https://www.developpez.com
le 02/12/2013 à 19:10
Bon... il me reste encore un point que j'aimerais régler, c'est la gestion des at-rules mais leur gestion est particulière et leur syntaxe hétéroclite...

Pour l'instant, la seule solution trouvée est la suivante :
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
getPrefixedAt: function(prop){ 
    var testStyle = document.createElement('style'); 
    document.getElementsByTagName('head')[0].appendChild(testStyle); 
    var testing = testStyle.sheet; 
    try{ 
        testing.insertRule(prop, 0); 
        document.getElementsByTagName('head')[0].removeChild(testStyle); 
        return prop; 
    } 
    catch(e){ 
        try{ 
            prop = prop.replace(/^@/, '@'+this.prefixe.css); 
            testing.insertRule(prop, 0); 
            document.getElementsByTagName('head')[0].removeChild(testStyle); 
            return prop; 
        } 
        catch(e){ 
            document.getElementsByTagName('head')[0].removeChild(testStyle); 
            return false; 
        } 
    } 
}
Mais ça oblige à passer la chaine complète d'une syntaxe supposée valide et l'affectation d'une règle non valide renvoyant une erreur, ça oblige en plus d'utiliser des blocs try catch peu élégants...
Exemples :
Code : Sélectionner tout
1
2
3
PrefixFinder.getPrefixedAt('@keyframes foo{}'); 
PrefixFinder.getPrefixedAt('@supports (animation){}'); 
PrefixFinder.getPrefixedAt('@charset "utf-8"');
Avatar de SylvainPV SylvainPV - Rédacteur/Modérateur https://www.developpez.com
le 03/12/2013 à 10:15
Ah OK donc ça marche uniquement pour les styles dynamiques en JavaScript. C'est assez limitant pour le coup. En fait, à part pour les aspects vraiment dynamiques comme un choix utilisateur ou l'action d'un plug-in sur un élément, j'ai toujours pris l'habitude de manipuler des classes en JS plutôt que le style directement. Comme ça, on ne mélange pas CSS et JS, et le designer peut modifier à sa guise le CSS sans toucher à mes sources.

Ca serait top si l'outil parcourait également les éléments <style> et les stylesheets du document. J'ai aussi ça sous la main si ça peut aider :

Test de support d’un sélecteur CSS

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
function isStyleSelectorSupported(selector){ 
 var style = document.createElement("style"); 
 style.innerHTML = selector + "{ }"; 
 document.body.appendChild(style); 
 var rule = style.sheet.cssRules[0]; 
 var isSupported = rule != null && !/:unknown/i.test(rule.selectorText); 
 document.body.removeChild(style); 
 return isSupported; 
} 
 
isStyleSelectorSupported(":nth-child(odd)")
Test de support d’une propriété CSS

Code : Sélectionner tout
1
2
3
4
5
function isStylePropertySupported(property){ 
 return property in document.createElement("p").style; 
} 
 
isStylePropertySupported("boxSizing")
Test de support d’une valeur particulière d’une propriété CSS

Code : Sélectionner tout
1
2
3
4
5
6
7
8
function isStylePropertyValueSupported(property, value){ 
 var style = document.createElement("p").style; 
 try { style[property] = value ; } 
 catch(e){ return false; } 
 return style[property] === value; 
} 
 
isStylePropertyValueSupported("boxSizing","border-box")
Avatar de Bovino Bovino - Rédacteur https://www.developpez.com
le 03/12/2013 à 11:07
Non... ça ne marche pas uniquement pour les styles dynamiques... Ca permet de savoir pour une propriété CSS ou une propriété et une valeur donnée quelle sera la syntaxe en CSS et en JavaScript.

Ensuite, rien ne t'empêche d'utiliser le script pour parser tes feuilles de style et apporter les préfixes adaptés pour chaque règle.
Un peu comme expliqué dans le premier message en fait.
On peut prévoir d'utiliser ce script comme préprocesseur des feuilles de style en parsant l'objet document.styleSheets par exemple et en réécrivant chaque règle si besoin avec les valeurs préfixées. Cela permettra de simplifier l'écriture des feuilles de style en n'écrivant que les propriétés standards et en adaptant celles nécessitant un préfixe directement dans la page Web.

Avatar de SylvainPV SylvainPV - Rédacteur/Modérateur https://www.developpez.com
le 03/12/2013 à 21:56
Désolé, j'ai vraiment compris de travers le but du script. En fait je n'avais pas compris que le script en lui-même n'ajoutait aucun préfixe aux styles existants, mais que c'était au développeur de se servir de l'API proposée pour appliquer les bons préfixes. Dans la démarche, c'est l'opposé de PrefixFree de Lea Verou, qui ajoute les préfixes automatiquement sans que le développeur ne le sache, mais du coup ne fonctionne pas pour les styles dynamiques.
Avatar de Zefling Zefling - Membre émérite https://www.developpez.com
le 04/12/2013 à 11:28
Et pour un cas comme ça, ça passe ?

Code : Sélectionner tout
1
2
3
4
5
6
7
display : -moz-box; 
display : -webkit-box; 
display : -o-box; 
display : -ms-flexbox; 
display : -webkit-flex; 
display : -ms-flex; 
display : flex;
Avatar de Bovino Bovino - Rédacteur https://www.developpez.com
le 04/12/2013 à 11:43
Il y a une page d'exemple (Préfixes CSS / JavaScript), tu peux tester chacune de ces valeurs.
Avatar de Bovino Bovino - Rédacteur https://www.developpez.com
le 17/12/2013 à 16:21
Le script vient d'être mis à jour (voir premier message de cette discussion) pour corriger un bogue de Safari (au moins la version 5.1.7 pour Windows) qui ne permet pas de lister les propriétés de l'objet HTMLElement.style.
La méthode pour récupérer le préfixe JavaScript a donc été réécrite (et du coup optimisée).

D'autre part, il est tenu compte désormais que certaines valeurs de propriétés peuvent nécessiter un préfixe. Si la syntaxe non préfixée de la valeur n'est pas trouvée, une recherche est faite sur une valeur préfixée (tester par exemple display avec la valeur box).

Enfin, il est possible d'intégrer une méthode supplémentaire au constructeur pour la gestion des at-rules, voici le code de cette méthode (à ajouter dans le prototype, voir la page d'exemple) :
Code javascript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
getPrefixedAt: function(prop){ 
    var testStyle = document.createElement('style'); 
    var myparent = document.getElementsByTagName('script')[0].parentNode; 
    myparent.appendChild(testStyle); 
    var testing = testStyle.sheet; 
    try{ 
        testing.insertRule(prop, 0); 
        myparent.removeChild(testStyle); 
        return prop.split(/[\s\{]/)[0]; 
    } 
    catch(e){ 
        try{ 
            prop = prop.replace(/^@/, '@'+this.prefixe.css); 
            testing.insertRule(prop, 0); 
            myparent.removeChild(testStyle); 
            return prop.split(/[\s\{]/)[0]; 
        } 
        catch(e){ 
            myparent.removeChild(testStyle); 
            return false; 
        } 
    } 
}
Attention : pour tester une at-rule, il est nécessaire de passer en paramètre une syntaxe minimale valide.
Par exemple, pour @keyframes, un nom est requis puis des accolades, il faudra donc tester @keyframes foo{} qui renverra @keyframes, @-webkit-keyframes ou false en fonction du navigateur.
Offres d'emploi IT
Chef projet big data - pse flotte H/F
Safran - Ile de France - Évry (91090)
Architecte sécurité des systèmes d'information embarqués H/F
Safran - Ile de France - 100 rue de Paris 91300 MASSY
Ingénieur intégration, validation, qualification du système de drone H/F
Safran - Ile de France - Éragny (95610)

Voir plus d'offres Voir la carte des offres IT
Responsable bénévole de la rubrique CSS : Xavier Lecomte -