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