*Le premier film de Pedro Almodovar s'appelait"Pepe, Luci, Bom et autres filles du quartier"
Ducky Smockton ? Non, mais presque
- Développeur Web autodidacte/ FAMP par curiosité
(Une amie m'a présentée FreeBSD, et on est dev'nu copains - avec FreeBSD, pas avec mon amie !)
- Enseignant en SVT (3 ans) par nécessité (il faut manger)
- Développeur Web/Java EE par goût du "luxe"
(les pâtes au beurre, c'est bien, mais les pâtes alla bolognese, c'est mieux)
- Responsable technique par inadvertance (j'vous expliqu'rai)
- Développeur Front ces derniers temps par choix
(j'suis payé pareil avec moins de responsabilités et plus de fun !)
</ma-vie>
- parce que j'aime partager (et pas que les chouquettes)
- parce que je connais bien le sujet (on fait c'qu'on peut)
- parce que j'ai beaucoup travaillé sur du code "historique"
- parce que j'aime me la péter devant un public en délire
(une seule est vraie, je vous laisse deviner)
CSS, qu'est-ce ?
Un langage standard ouvert du web pour le HTML et le XML
(Woah le scoop, eh ! J'suis v'nu pour ça ?)
Pourquoi le CSS ?
Pour séparer le contenu de la forme (Wouhouuu nan mais il ay sayrieux ?)
Par qui ?
Le W3C
Où en est-on ?
CSS2.1 Recommandation en juin 2011
CSS3 découpés en modules, la plupart déjà en recommandation
CSS4 n'existe pas, des modules de niveau 4 sont en brouillon, qui "héritent" de CSS3 (selector, background, etc.), et d'autres de niveau 1 (Flexbox...)
/* Règle */
.classe { /* Sélecteur */
background-color: #555; /* Déclaration */
/* propriété: valeur(s); */
background: #555 url(/chemin/de/croix.png) no-repeat center center;
}
/* Sélecteur universel */
*
/* Sélecteurs de type */
div
/* Sélecteurs descendants */
div span
/* Sélecteurs d'enfant */
ul > li
/* La pseudo-classe */
li:first-child
/* Les pseudo-classes de lien */
a:link
a:visited
/* Les pseudo-classes dynamiques */
a:active
a:hover
a:focus
/* La pseudo-classe :lang() */
h1:lang(fr)
/* Les sélecteurs adjacents */
h2 + p
/* Sélecteurs d'attribut */
abbr[title]
/* Sélecteurs d'attribut */
fieldset[foo="warning"]
label[foo~="warning"]
aside[lang|="en"]
/*************************
* Seulement en HTML *
*************************/
/* Sélecteurs de classe */
/* Identique à DIV[class~="warning"] */
.warning
/* Sélecteurs d'ID */
#myid
/* Les pseudo-éléments */
:first-line
:first-letter
:before
:after
/* 1 */
a {
background-color: yellow;
}
/* 2 */
#content p a {
background-color: red;
}
/* 3 */
p #wannabee-black {
background-color: green;
}
/* 4 */
#wannabee-black {
background-color: black;
}
/* 5 */
a.perfect-color {
background-color: blue;
}
/* 6 */
div.main p a.perfect-color {
background-color: orange;
/* which is the new black */
}
<body>
<div class="main" id="content">
<p>
This is <a id="wannabee-black" class="perfect-color">fun</a>!
</p>
</div>
</body>
Un petit jeu
pour se mettre dans le bain :
Quelle sera la couleur de fond
du mot "fun" ?
Version simple
et pseudo-classe*
et attribut**
et pseudo-classe*
et attribut**
header #better #faster .stronger .work-is-never:hover
et pseudo-classe*
et attribut**
header #better #faster .stronger .work-is-never:hover
(Saylemal !)
Voyons pourquoi...
process.argv.forEach(function (val) {
if (val === 'prod' || val === 'express:prod') {
app.set('env', 'production');
} else if (val.indexOf('port=') > -1) {
try {
var serverPort = parseInt(val.split('=')[1], 10);
app.set('serverPort', serverPort);
winston.info('Server port = ' + serverPort);
} catch (e) {
winston.warn('Cannot parse server port');
}
} else if (val.indexOf('apiHost=') > -1) {
try {
apiHost = val.split('=')[1];
winston.info('Api host = ' + apiHost);
} catch (e) {
winston.warn('Cannot parse "--apiHost=X.X.X.X"');
}
} else if (val.indexOf('coreApiHost=') > -1) {
try {
coreApiHost = val.split('=')[1];
winston.info('Core Api host = ' + coreApiHost);
} catch (e) {
winston.warn('Cannot parse "--coreApiHost=X.X.X.X"');
}
} else if (val.indexOf('clientId=') > -1) {
try {
clientId = val.split('=')[1];
winston.info('Client id = ' + clientId);
} catch (e) {
winston.warn('Cannot parse "--clientId=XXXX"');
}
} else if (val.indexOf('managerId=') > -1) {
try {
managerId = val.split('=')[1];
winston.info('Manager id = ' + managerId);
} catch (e) {
winston.warn('Cannot parse "--managerId=XXXX"');
}
} else if (val.indexOf('login=') > -1) {
try {
login = val.split('=')[1];
winston.info('CCMD login = ' + login);
} catch (e) {
winston.warn('Cannot parse "--login=lorem.ipsum"');
}
}
});
Un exemple
"That's what they say... that's not what they mean"*
header #better #faster .stronger .work-is-never:hover {
(...)
}
"That's what they say... that's not what they mean"*
Ce que ça dit :
header #better #faster .stronger .work-is-never:hover {
(...)
}
@if isMouseOver() and isElementWithClass(work-is-never) {
@if insideElementWithClass(stronger) {
@if insideElementWithId(faster) {
@if insideElementWithId(better) {
@if inside(header) {
(...)
}
}
}
}
}
"That's what they say... that's not what they mean"*
Ce que ça dit :
Ce que ça veut dire :
header #better #faster .stronger .work-is-never:hover {
(...)
}
@if isMouseOver() and isElementWithClass(work-is-never) {
@if insideElementWithClass(stronger) {
@if insideElementWithId(faster) {
@if insideElementWithId(better) {
@if inside(header) {
(...)
}
}
}
}
}
"That's what they say... that's not what they mean"*
Ce que ça dit :
*Jack Shephard dans "Lost" à propos de ses tatouages
Ce que ça veut dire :
Le couplage fort, saylemal !
(mais ça, vous le savez)
header {
#better {
#faster {
.stronger {
.work-is-never:hover {
(...)
}
}
}
}
}
<html>
<head>
</head>
<body>
<header>
<div id="better">
<div id="faster">
<div class="stronger">
<a class="work-is-never">
</a>
</div>
</div>
</div>
</header>
</body>
</html>
Le couplage fort, saylemal !
(mais ça, vous le savez)
#edit-video-popup #video-popup-container #video-settings #embedded-video-menu #video-formats .radio-button.ok:after {
(...)
}
#toast-container .ng-toast__list .alert.toast-warning .toast-title + .toast-message > div:not(:empty) {
(...)
}
Encourager la réutilisation de code, et écrire des feuilles de styles plus efficaces, et plus facile à maintenir et modifier.
<div class="signupform signup-modal invisible">
<div id="signup-modal">
<h2><img src="https://d2ed0w4q03gsmw.cloudfront.net/s3/assets/4b367e1/images/logos/aboutme_logo_black.svg" /></h2>
<h3 class="action">It’s free and only takes minutes to set up a page. Join now or <a href="https://about.me/">learn more</a>.</h3>
<div class="signup">
<form action="/register" id="signup_signupmodal" method="post" onsubmit="return false">
<input name="_authentication_token" type="hidden" value="140652078138150610996182975593007636835" />
<fieldset class="first_name name">
<label class="label" for="signupmodal_first_name">First name</label>
<input type="text" name="first_name" id="signupmodal_first_name" class="input fullwidth large first_name" value="" maxlength="32" />
</fieldset>
<fieldset class="last_name name">
<label class="label" for="signupmodal_last_name">Last name</label>
<input type="text" name="last_name" id="signupmodal_last_name" class="input fullwidth large last_name" value="" maxlength="32" />
</fieldset>
<fieldset class="email">
<label class="label" for="signupmodal_email">Email address</label>
<input type="text" name="email" id="signupmodal_email" class="input fullwidth large email" value="" maxlength="254" />
</fieldset>
<input type="hidden" value="signupmodal" name="location" class="signuplocation" />
<fieldset class="buttons large">
<div class="alreadyjoined right invisible button-item"><span class="">Already joined? </span><a href="https://about.me/login" class="not-deferred">Log In</a></div>
<button type="submit" class="button test130106 large dark blue signup-submitbutton not-deferred">
<span class="default-text">Join for FREE</span>
<span class="loading-text">Join for FREE</span>
</button>
</fieldset>
<div class="socialsignup">
<h4>or continue with…</h4>
<div class="buttons">
<a href="https://about.me/facebook/login?perm=basic" class="button signupbutton large facebook glyph-facebook not-deferred">Facebook</a>
<a href="https://about.me/twitter/login" class="button signupbutton large twitter glyph-twitter not-deferred">Twitter</a>
</div>
</div>
</form>
</div>
</div>
</div>
/* BAD! */
#widget-popup button {
width: 200px;
height: 50px;
padding: 10px;
border: solid 1px #ccc;
background: linear-gradient(#ccc, #222);
box-shadow: rgba(0, 0, 0, .5) 2px 2px 5px;
}
#widget-popup .box {
width: 400px;
overflow: hidden;
border: solid 1px #ccc;
background: linear-gradient(#ccc, #222);
box-shadow: rgba(0, 0, 0, .5) 2px 2px 5px;
}
#widget-popup .tooltip {
width: 500px;
min-height: 200px;
overflow: auto;
border: solid 1px #ccc;
background: linear-gradient(#ccc, #222);
box-shadow: rgba(0, 0, 0, .5) 2px 2px 5px;
}
/* GOOD! */
.button {
width: 200px;
height: 50px;
}
.box {
width: 400px;
overflow: hidden;
}
.tooltip {
width: 500px;
min-height: 200px;
overflow: auto;
}
.sf-default-theme {
border: solid 1px #ccc;
background: linear-gradient(#ccc, #222);
box-shadow: rgba(0, 0, 0, .5) 2px 2px 5px;
}
/* BAD! */
#sidebar h3 {
font-family: Arial, Helvetica, sans-serif;
font-size: .8em;
line-height: 1;
color: #777;
text-shadow: rgba(0, 0, 0, .3) 3px 3px 6px;
}
/* BAD! */
#sidebar h3 {
font-family: Arial, Helvetica, sans-serif;
font-size: .8em;
line-height: 1;
color: #777;
text-shadow: rgba(0, 0, 0, .3) 3px 3px 6px;
}
#sidebar h3, #footer h3 {
font-family: Arial, Helvetica, sans-serif;
font-size: 2em;
line-height: 1;
color: #777;
text-shadow: rgba(0, 0, 0, .3) 3px 3px 6px;
}
#footer h3 {
font-size: 1.5em;
text-shadow: rgba(0, 0, 0, .3) 2px 2px 4px;
}
/* BAD! */
#sidebar h3 {
font-family: Arial, Helvetica, sans-serif;
font-size: .8em;
line-height: 1;
color: #777;
text-shadow: rgba(0, 0, 0, .3) 3px 3px 6px;
}
#sidebar h3, #footer h3 {
font-family: Arial, Helvetica, sans-serif;
font-size: 2em;
line-height: 1;
color: #777;
text-shadow: rgba(0, 0, 0, .3) 3px 3px 6px;
}
#footer h3 {
font-size: 1.5em;
text-shadow: rgba(0, 0, 0, .3) 2px 2px 4px;
}
#sidebar h3.alternative {
font-size: .9em;
color: #45a;
}
<div class="media">
<a href="http://twitter.com/sormieres" class="img">
<img src="sormieres.jpg" alt="me" width="40" />
</a>
<div class="bd">
@smartfocus 9 minutes ago
</div>
</div>
.media {
margin: 10px;
}
.media,
.bd {
overflow: hidden;
_overflow: visible;
zoom: 1;
}
.media .img {
float: left;
margin-right: 10px;
}
.media .img img {
display: block;
}
The media object
.row et .col-*-*
.text-left, .text-center, etc.
Atomic CSS
(Beaucoup de) règles avec un sélecteur simple : une classe,
et avec une seule déclaration.
Très réutilisables. Tout ce qui suit n'est pas une bonne idée, mais il y a du bon à prendre (.hidden et .pr et .pa, j'aime bien)
.pt-0 {
padding-top: 0;
}
.ta-c {
text-align: center;
}
.pr {
position: relative;
}
.pa {
position: absolute;
}
.hidden {
display: none;
}
Toujours plus loin
/* Règle concernant le bloc */
.block {}
/* Règle concernant l'élément à l'intérieur du bloc */
.block__element {}
/* Une règle définissant une variante du bloc
(Ne définit que les propriétés qui ont des valeurs différentes !) */
.block--modifier {}
/* Une règle définissant une variante de l'élément
(Ne définit que les propriétés qui ont des valeurs différentes !) */
.block__element--modifier {}
Principes :
.person {}
.person__hand {}
.person--female {}
.person--female__hand {}
.person__hand--left {}
Une analogie pour bien comprendre*
.media {}
.media__img {}
.media__img--rev {}
.media__body {}
Media object version BEM
Grille CSS version BEM (CSSWizardry)
.grid {
margin-left:-$base-spacing-unit;
list-style:none;
margin-bottom:0;
}
.grid__item {
display:inline-block;
width:100%;
padding-left:$base-spacing-unit;
vertical-align:top;
}
.one-half {
width: 50% !important;
}
.one-third {
width: 33.333% !important;
}
.two-thirds {
width: 66.666% !important;
}
(...)
.one-tenth {
width: 10% !important;
}
(...)
<ul class="grid">
<li class="grid__item">
Toute la largeur du parent
</li>
<li class="grid__item one-half">
La moitié de la largeur du parent
</li>
<li class="grid__item one-half">
La moitié de la largeur du parent
</li>
<li class="grid__item one-third">
Un tiers de la largeur du parent
</li>
<li class="grid__item two-thirds">
Deux tiers de la largeur du parent
</li>
</ul
#edit-reco-popup {
(...)
.parameters-container {
(...)
.parameter-list-container {
(...)
.scrollable {
(...)
.nano > .nano-content {
(...)
.parameters {
(...)
.parameter-item {
(...)
.input-container {
(...)
input {
display: inline-block;
vertical-align: top;
border: none;
outline: none;
line-height: 30px;
height: 30px;
min-width: 200px;
padding: 0 @gutter;
overflow: hidden;
}
.personalization-field {
vertical-align: top;
height: 30px;
width: 30px;
cursor: pointer;
background-color: @grey2;
border-left: 1px solid @grey5;
.border-radius(0 2px 2px 0px)
}
(...)
#edit-video-popup {
(...)
#video-popup-container {
(...)
#video-settings {
(...)
.input-container {
(...)
.input {
display: inline-block;
vertical-align: middle;
border: none;
line-height: 28px;
height: 28px;
outline: none;
min-width: 300px;
}
.personalization-field {
vertical-align: middle;
background-color: @grey2;
height: 30px;
width: 30px;
cursor: pointer;
border-left: 1px solid @grey5;
.border-radius(0 2px 2px 0px)
}
(...)
#edit-reco-popup {
(...)
.parameters-container {
(...)
.parameter-list-container {
(...)
.scrollable {
(...)
.nano > .nano-content {
(...)
.parameters {
(...)
.parameter-item {
(...)
.input-container {
(...)
input {
display: inline-block;
vertical-align: top;
border: none;
outline: none;
line-height: 30px;
height: 30px;
min-width: 200px;
padding: 0 @gutter;
overflow: hidden;
}
.personalization-field {
vertical-align: top;
height: 30px;
width: 30px;
cursor: pointer;
background-color: @grey2;
border-left: 1px solid @grey5;
.border-radius(0 2px 2px 0px)
}
(...)
#edit-video-popup {
(...)
#video-popup-container {
(...)
#video-settings {
(...)
.input-container {
(...)
.input {
display: inline-block;
vertical-align: middle;
border: none;
line-height: 28px;
height: 28px;
outline: none;
min-width: 300px;
}
.personalization-field {
vertical-align: middle;
background-color: @grey2;
height: 30px;
width: 30px;
cursor: pointer;
border-left: 1px solid @grey5;
.border-radius(0 2px 2px 0px)
}
(...)
Un bon exemple de mauvaise pratique :
On peut utiliser les imbrications LESS/SASS/Stylus intelligemment
.input-container {
vertical-align: middle;
display: inline-block;
border: 1px solid @grey5;
margin-left: @halfGutter;
.border-radius(@uiBorderRadius);
&--warn {
border-color: @yellow1;
color: @yellow1;
}
&__input {
display: inline-block;
vertical-align: middle;
border: medium none;
height: 32px;
line-height: 32px;
width: 300px;
outline: medium none;
vertical-align: middle;
padding-left: @halfGutter;
padding-right: @halfGutter;
.border-radius(2px);
&[disabled] {
color: @grey6;
background-color: @grey3;
}
&--left {
.border-radius(2px 0 0 2px);
}
&--right {
.border-radius(0 2px 2px 0);
}
&--middle {
.border-radius(0);
}
}
&__icon {
vertical-align: middle;
background-color: @grey2;
display: inline-block;
height: 30px;
width: 29px;
border-left: 1px solid @grey5;
cursor: pointer;
background-size: @sizeS;
background-repeat: no-repeat;
background-position: 50% 50%;
.border-radius(0 2px 2px 0px);
&--left {
border-right: 1px solid @grey5;
border-left: none;
.border-radius(2px 0 0 2px);
}
&--perso {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAiVJREFUeNrEl7lLA0EUxmeDiUU84omojaCIVyEGtUllYSkeqIVFIFUaURAs/AssRBQkVQqxFAtFtBGxFIyKqNhY2AhqDOKFR9T1G5iFdZhsZi/z4Mcsb+f4due9t7OKqqpEs1gsRnRWD2ZBL/Dr/ArJYNFolM6h6lwvYAtM496VaIwnw1zt4AAMcIubtQIwDI4grE1WgA+sgQBxzkrAKkR4ZQSMgDrivDWCQRkB/cQ9G+IdeYJOHQLfDdgFaYlFlkE+6AEV3L2gjIBqgS8ELmUeEdEeZhnViuY029z8FvgFom5lF+eEnKFJce6sQegVzJWysed32Tp4JCZJ2xDwyTuwNflGAnzEfSs0ElAsGPBsY7FXgS9gJCAkGHBhQ4BobNefNMSeTLDrShq8ggGbNgRsgwjnW8SaTWjvtTowbzDBoU0B6+ActOh8pWBGJguowjHwY3V11IIvNseTlTSkOZx0IOqvjeqBkYBmELe7Ot5CEjRkuk9jYJJd17Ag1B9A+kA32HewDtC0XmIlnijckWwczQI3YA5MST6taTX8h2cnw/HMtAWDQf3ZkCQSCUUmBh4EfcrcrMu8gDfyz+ZxqI9l44OwCM2jII9rs00Uj8dVMwtrMcE/Ha1YquAYVe7WGxCdCWl+VnF/QnvgGHyDsNsCTjgBhH1MtA+K6wI22P+gKYtEIoq+EFmtA9RWtDKZqzSktXoUfOSyDtCg62S/1u9uCvgVYABLIX8iqQXEMQAAAABJRU5ErkJggg==");
}
}
}
Avez-vous noté le & ?
.input-container {
vertical-align: middle;
display: inline-block;
border: 1px solid #bcbcbc;
margin-left: 7px;
border-radius: 3px;
background-clip: padding-box;
}
.input-container--warn {
border-color: #f58124;
color: #f58124;
}
.input-container__input {
display: inline-block;
border: medium none;
height: 32px;
line-height: 32px;
width: 300px;
outline: medium none;
vertical-align: middle;
padding-left: 7px;
padding-right: 7px;
border-radius: 0;
background-clip: padding-box;
}
.input-container__input[disabled] {
color: #969696;
background-color: #e9e9e9;
}
.input-container__input--left {
border-radius: 2px 0 0 2px;
background-clip: padding-box;
}
.input-container__input--right {
border-radius: 0 2px 2px 0;
background-clip: padding-box;
}
.input-container__input--middle {
border-radius: 0;
background-clip: padding-box;
}
.input-container__icon {
vertical-align: middle;
background-color: #f2f2f2;
display: inline-block;
height: 30px;
width: 29px;
border-left: 1px solid #bcbcbc;
cursor: pointer;
background-size: 16px;
background-repeat: no-repeat;
background-position: 50% 50%;
border-radius: 0 2px 2px 0px;
background-clip: padding-box;
}
.input-container__icon--left {
border-right: 1px solid #bcbcbc;
border-left: none;
border-radius: 2px 0 0 2px;
background-clip: padding-box;
}
.input-container__icon--perso {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAiVJREFUeNrEl7lLA0EUxmeDiUU84omojaCIVyEGtUllYSkeqIVFIFUaURAs/AssRBQkVQqxFAtFtBGxFIyKqNhY2AhqDOKFR9T1G5iFdZhsZi/z4Mcsb+f4due9t7OKqqpEs1gsRnRWD2ZBL/Dr/ArJYNFolM6h6lwvYAtM496VaIwnw1zt4AAMcIubtQIwDI4grE1WgA+sgQBxzkrAKkR4ZQSMgDrivDWCQRkB/cQ9G+IdeYJOHQLfDdgFaYlFlkE+6AEV3L2gjIBqgS8ELmUeEdEeZhnViuY029z8FvgFom5lF+eEnKFJce6sQegVzJWysed32Tp4JCZJ2xDwyTuwNflGAnzEfSs0ElAsGPBsY7FXgS9gJCAkGHBhQ4BobNefNMSeTLDrShq8ggGbNgRsgwjnW8SaTWjvtTowbzDBoU0B6+ActOh8pWBGJguowjHwY3V11IIvNseTlTSkOZx0IOqvjeqBkYBmELe7Ot5CEjRkuk9jYJJd17Ag1B9A+kA32HewDtC0XmIlnijckWwczQI3YA5MST6taTX8h2cnw/HMtAWDQf3ZkCQSCUUmBh4EfcrcrMu8gDfyz+ZxqI9l44OwCM2jII9rs00Uj8dVMwtrMcE/Ha1YquAYVe7WGxCdCWl+VnF/QnvgGHyDsNsCTjgBhH1MtA+K6wI22P+gKYtEIoq+EFmtA9RWtDKZqzSktXoUfOSyDtCg62S/1u9uCvgVYABLIX8iqQXEMQAAAABJRU5ErkJggg==");
}
Voici le CSS produit : du joli BEM, donc lisible, et peu spécifique, donc réutilisable
Note : le code peut encore largement être amélioré, ce code est là surtout pour montrer qu'on peut utiliser les imbrications intelligemment en gardant des sélecteurs peu spécifiques mais en rassemblant dans le code SASS/LESS/Stylus les styles apparentés.
Scalable and Modular Architecture for CSS
Principe :
Découper les styles en catégories... et donc en fichiers
Principe :
"SMACSS sous extasy anti-dépresseurs pré-processeurs"
Inverted Triangle CSS
/* object.less : Uniquement des classes, en restant peu spécifique */
.ui-list {
margin: 0;
padding: 0;
list-style: none;
}
.ui-list__item {
padding: @spacing-unit;
}
/* components.less : Toujours uniquement des classes, mais en étant plus explicite */
.carousel {
height: 32px;
max-height: 32px;
vertical-align: middle;
&__item {
margin: 0px 7px;
color: #BCBCBC;
cursor: pointer;
transition: color 200ms ease 0s;
}
}
/* trumps.less : seul fichier où on trouvera des !important */
.one-half {
width: 50% !important;
}
.hidden {
display: none !important;
}
Parce qu'il n'y a pas que div et span, dans la vie (et le HTML) !
<header> (plusieurs fois dans la même page !)
<main> (un seul par page)
<section> (plusieurs fois dans la même page, doit avoir un header)
<footer> (plusieurs fois dans la même page !)
<nav> (navigation principale uniquement)
<aside>
<dialog>