Démos

Toutes les démos

<>

Couverture du livre CSS3 Le design web moderne

Livre CSS3

Le design web moderne

Plus d'infos sur le site officiel

En vente sur Amazon, FNAC, Dunod

Suivre

Actualités du site CSS3Create

Actualités sur CSS et le web en général

Effet accordéon sur une image en CSS

Créer un effet de pliage avec les transformations CSS 3D

Création: février 2013Modifié:

Contenu
Avec JavaScript
Navigateurs
Firefox Safari Chrome Internet Explorer
Partagez
Facebook Twitter

Votez

34 votes4.51

Utiliser les transformations 3D CSS et un peu de JavaScript pour créer un effet d’accordéon (ou paperfold) sur une image de manière dynamique.

Code HTML de base

Pour réaliser cet effet, nous avons besoin d’une structure HTML minimaliste. Une simple image dans une balise <article> :

<article>
   <img src="..." alt="..." />
</article>
<p>
   <label>Pliage
       <input type="range" min="200" max="500" value="400" step="10" />
   </label>
</p>

En fait, l’image sera remplacée en JavaScript. Elle restera présente seulement si JS est desactivé.

Styles CSS

La majeure partie de l’effet va être réalisé en JavaScript pour nous permettre de modifier les styles CSS en temps-réel (lors de la modification du slider). Néanmoins, voici les styles nécessaires pour un bon départ :

Modification de la largeur et de la hauteur de l’élément principal :

article{
   width: 500px;
   height: 350px;
}

Initialisation du code JavaScript

Tout au long de cette démo, j’utilise le framework jQuery pour réaliser le JavaScript, notamment pour sa simplicité d’utilisation.

Commençons donc notre JS en récupérant la source de l’image (que nous allons ensuite masquer pour la réutiliser depuis background-image)

// recupere la source de l'image
var src = $('article img').attr('src');

Puis nous créeons une boucle qui va créer 10 éléments <div class="face">. Dans chaque élément, une <div> est présente et contient un élément <span>. Ces éléments sont ensuite ajoutés dans la balise <article> en remplacement de <img>.

var elements = '';
for( var i = 0; i < 10; i++){
   elements += '<div class="face">';
   elements += '<div><span></span></div>';
   elements += '</div>';
}

// remplace le contenu d'article
$('article').html(elements);

Ajout des CSS de base pour chaque face

Les faces qui viennent d’êtres injectées dans le DOM doivent êtres affichées les unes à coté des autres. Pour cela, nous utilisons display: inline-block. Nous spécifions aussi la taille de chaque face et un positionnement relatif (pour permettre un placement en absolu des enfants).

article .face{
   position: relative;
   display: inline-block;
   width: 10%;
   height: 100%;
}

Chaque enfant est alors positionné en absolu, et sa largeur est égale à 100% de la taille d’une face, soit 50px ici. (Cette valeur sera forcée pour être conservée à 50px en JavaScript, voir plus loin)

article .face div{
   position: absolute;
   width: 100%;
   height: 100%;
}

Application de l’image en arrière-plan de chaque face

Toujours lors de l’initialisation, nous devons donc afficher l’image en arrière-plan de chaque élément, puis modifier à chaque fois le background-position pour que l’ensemble des éléments affichés reconstituent l’image de départ. Cela va être fait en JS :

// recupere la taille d'une face
var widthFace = $('article').width() / 10;  // 50px

// pour chaque face
$('.face').each(function(){
   // recupere sa position au sein de son parent
   var pos = $('article .face').index($(this));
   // calcul du background-position
   var bgPos = pos * widthFace + 'px';
   
   // application du CSS pour chaque élément enfant
   $(this).find('div').css({
       width: widthFace + 'px',
       backgroundImage: 'url('+src+')',
       backgroundPosition: '-'+bgPos+' 0'  
   });
});

Donc, pour chaque élément <div class="face">, nous récupérons sa position (index()), que nous multiplions par la taille d’une face. Cela nous permets de décaler l’image d’arrière-plan pour chaque élément. De plus, nous forçons la taille de chaque élément à rester 1/10e de la taille de départ de <article>. (Rappelez-vous en CSS nous avions fixé à 100% d’une face)

À cet instant, notre image semble s’afficher entièrement, mais il faut bien avoir en tête que l’image est en fait utilisée par les 10 faces et que seule la position de l’arrière-plan change.

Actions lors de la modification du slider

Maintenant que nos éléments sont initialisés, que doit-on faire lors de la modification du slider ? Plusieurs choses :

  • récupérer la valeur du champ
  • modifier la taille de l’élément <article> en fonction de la valeur récupérée
  • récupérer la largeur de chaque face
  • calculer l’angle en fonction de cette largeur

Le tout bien entendu lors de la détection de l’évènement.

// détection de l'evenement change() sur l'input
$('input').change(function(){
   // recup la valeur de l'attribut value
   var width = $(this).val();

   // modification de la taille de article
   $('article').width(width + 'px');

   // recupere la taille d'une face initiale (taille du premier element d'une face)
   widthFaceInitiale = $('article .face div').get(0).width();
   // la taille d'une face en cours
   widthFace = width / 10;
   
   // calcul de l'angle
   var angle = Math.acos(widthFace / widthFaceInitiale) * 180 / Math.PI;
}

Ce qui est le plus complexe ici, c’est le calcul de l’angle. Pour ce faire, utilisons ce petit schéma qui représente notre accordéon vu de dessus :

En noir, chaque <div class="face"> dont la largeur vaut widthFace (1/10e de l’élément parent). En bleu, les éléments enfants qui seront transformés et dont on cherche la valeur de l’angle. Ces éléments mesurent la taille fixé en JS lors de l’initialisation : 1/10e de l’article au départ : 50px.

Notre angle se calcule donc de cette façon : cosinus(angle) = widthFace / widthFaceInitiale.

Attention, en JavaScript les fonctions mathématiques sur les angles utilisent des radians. J’ai donc fait le choix ici de multiplier par 180, puis diviser par PI pour obtenir des angles en degrés. J’aurais pu aussi conserver cette valeur en radians, puis utiliser plus cette unité en CSS.

Le CSS pour nos transformations 3D

Maintenant que notre angle se calcule lorsque l’élément <article> est redimensionné, appliquons nos transformations 3D, mais avant définissons en CSS les propriétés de transformations :

/* Ajout de la perspective 3D sur chaque face */
article .face{
   ...
   perspective: 500px;
}

/* Les éléments transformés */
article .face div{
   ...

   /* calage à droite */
   right: 0;
   
   /* origine de la transformation */
   transform-origin: top right;
}

/* Les éléments transformés IMPAIRS */
article .face:nth-child(odd) div{
   ...

   /* calage à gauche */
   right: auto;
   left: 0;
   
   /* origine de la transformation */
   transform-origin: top left;
}

L’idée est la suivante : les éléments impairs sont positionnés à gauche au sein de leur parent, et seront transformés depuis le coin en haut à gauche. Les éléments pairs sont eux positionnés à droite dans leur parent et seront transformés depuis l’angle en haut à droite. Voir le schéma ci-dessus.

Application des transformations 3D sur chaque élément

Pour appliquer chaque transformation 3D, nous utilisons l’angle calculé plus haut. Cet angle nous permets d’appliquer une rotation sur l’axe Y à chaque élément. Voici le code JavaScript :

// détection de l'evenement change() sur l'input
$('input').change(function(){
   ...

   // pour chaque élément dans une face,
   // on modifie son CSS en ajoutant une rotation sur l'axe Y de la valeur de l'angle
   $('.face div').css({
       transform: 'rotateY('+angle+'deg)'
   });

   // pour chaque élément dans une face IMPAIRE,
   // l'angle est négatif
   $('.face:nth-child(odd) div').css({
       transform: 'rotateY(-'+angle+'deg)'
   });
   
}

Correction de la perspective

Bien que les choses aient été faites correctement, les éléments semblent ne pas êtres tout à fait caler. Il se peut que de petits espaces apparaissent ou que les éléments se chevauchent. Cela est du à l’origine de la perspective : ce que l’on appelle le point de fuite. Corrigeons cela avec la propriété perspective-origin depuis le CSS :

article .face{
   ...
   perspective-origin: 0% 70%;
}

article .face:nth-child(odd){
   ...
   perspective-origin: 100% 70%;
}

Nous ajoutons donc le point de fuite sur la droite des éléments impairs (100%) et sur la gauche des éléments pairs (0%). La valeur 70% correspond à la hauteur de ce point sur chaque face.

Ajout de réalisme : les ombres

Pour créer les ombres, et ainsi ajouter de la profondeur à notre démo, nous utilisons les éléments <span> au sein de chaque face. Ces éléments vont venir superposer les faces et nous y dessinons des dégradés CSS.

/* tous les spans */
article .face div span{
   position: absolute;
   width: 100%; height: 100%;
   background: black;
   background: linear-gradient(
                  to right,
                  transparent 0%,
                  rgba(0,0,0,.5) 90%,
                  black),
                  rgba(0,0,0,.5);
   opacity: 0;
}
/* les spans IMPAIRS */
article .face:nth-child(odd) div span{
   background: none;
   background: linear-gradient(
                  to left,
                  transparent 70%,
                  rgba(0,0,0,.4) 90%,
                  black);
}
/* le premier span */
article .face:first-child div span{
   background: none;
}

Ces éléments sont masqués par défaut avec opacity: 0. C’est pourquoi il nous suffit de modifier cette opacité en JavaScript. Pour cela, récupérons les valeurs minimales, maximales, et la différence entre les deux pour appliquer une opacité qui sera égale à 0 lorsque la taille de <article> est maximale et égale à 1 lorsque la taille sera minimale.

// détection de l'evenement change() sur l'input
$('input').change(function(){
   ....
 
   // min et max
   var min = $input.attr('min'); // 500
   var max = $input.attr('max'); // 200
   // delta entre min et max
   var delta = max - min; // 300
   // largeur de article - min
   var width0 = width - min; // entre 0 et 300

   // opacity entre 0 (taille au minimum) et 1 (taille au maximum)
   var opacity = (width0 / 1000) / (delta / 1000);
   // inversion: 0 (taille au maximum) et 1 (taille au minimum)
   opacity = 1 - opacity;

   // application de l'opacite
   $(' .face div span').css({
       opacity: opacity
   });
});

Conclusion

J’espère que ce tutoriel vous aura appris quelques petites choses sur l’utilisation des transformations 3D en CSS, ainsi que sur la façon d’interagir avec depuis JavaScript. Pour terminer, j’attire votre attention sur le fait que cela reste une expérience et qu’il vous faut prendre le maximum de soin lors de la mise en place de telles techniques.

Pensez notamment à des librairies comme Modernizr pour la détection de fonctionnalités (mettre en place les T3D seulement si le navigateur les reconnaît), et à l’utilisation des préfixes navigateurs (en CSS et en JS).

Note : j’utilise également le polyfill html5slider pour générer un <input type="range" /> sur Firefox !

Envie d'une nouvelle démo?

Un p'tit mot...

#1par jkneb, le 7 février 2013

ca rocks !

#2par Tib, le 22 février 2013

C’est fun ! As-tu d’autres site pour des tuto et astuces gratuites ?
Mis à part ceux présentés en bas de cette page (j’ai déjà fait le tour ^^).

Ce site est vraiment sympa !

#3par InternetDev, le 6 mars 2013

Bonnes astuces

#4par aaron, le 23 avril 2013

j’aime vraiment tous ce que vous faite . le disign ici est parfait


modération à priori

Ce forum est modéré à priori : votre contribution n'apparaîtra qu'après avoir été validée par un administrateur du site.

Un message, un commentaire ?
    Qui êtes-vous ? (optionnel)
  • (Pour créer des paragraphes, laissez simplement des lignes vides.)