Depuis un certain temps maintenant, de temps en temps, j'essaie d'implémenter un diaporama CSS uniquement, un qui:
La plupart des autres diaporamas CSS que j'ai rencontrés n'ont pas coché toutes ces cases.
Heureusement, cela m'a pris tellement de temps que les navigateurs eux-mêmes se sont améliorés sans fin, au point où cela est désormais assez largement possible, bien qu'avec quelques CSS-ismes "modernes". Au cas où il serait utile à quelqu'un d'autre, j'ai pensé le publier ici.
Alors, comment pouvez-vous créer un diaporama navigable, en utilisant uniquement CSS et le balisage suivant?
<ul class="css-slider">
<li class="slide"><img src="photos/a.jpg" /></li>
<li class="slide"><img src="photos/b.jpg" /></li>
<li class="slide"><img src="photos/c.jpg" /></li>
<li class="slide"><img src="photos/d.jpg" /></li>
<li class="slide"><img src="photos/e.jpg" /></li>
</ul>
MISE À JOUR: Il semble y avoir un bug avec Firefox 32 (Mac) qui signifie que les ellipses ne s'afficheront pas dans les masques SVG, cela entraîne l'échec de la réflexion ... et ne me lancez pas sur ce que Chrome 37.0.2062.120 (Mac) fait si vous passez la souris sur l'une des images du exemple mis en œuvre au pied de cette réponse.
- 18 septembre 2014.
Les diapositives sont constituées de deux parties principales, une partie visuelle et une partie interactive. La partie interactive reste à peu près statique et la partie visuelle est animée. Après avoir beaucoup joué, j'ai étendu la structure HTML initiale (voir ci-dessus) pour incorporer quelques wrappers supplémentaires autour du contenu de la diapositive. Cela permet des parties interactives et visuelles séparées, ainsi qu'une flexibilité supplémentaire pour d'autres capacités utiles, comme le centrage vertical et les réflexions.
<ul class="css-slider">
<li class="slide" tabindex="1" id="l1">
<span class="slide-outer">
<span class="slide-inner">
<span class="slide-gfx" id="s1">
<img src="photos/a.jpg" />
</span>
</span>
</span>
</li>
<li class="slide" tabindex="1" id="l2">
<span class="slide-outer">
<span class="slide-inner">
<span class="slide-gfx" id="s2">
<img src="photos/b.jpg" />
</span>
</span>
</span>
</li>
...
</ul>
Maintenant, pour que tout système se comporte comme un diaporama, vous devez avoir un moyen d'identifier la diapositive actuelle ou ciblée. Dans ce cas, je compte sur :focus
pour gérer cette distinction. Vous avez peut-être remarqué l'ajout de tabindex=“1”
ci-dessus, afin que la pseudo-classe :focus
soit appliquée à des éléments inattendus comme <li>
- cela était principalement nécessaire pour navigateurs Webkit, mais peuvent également aider d'autres agents.
Autrement dit, toutes les diapositives sont empilées les unes sur les autres, la diapositive focalisée actuelle est centrée sur la fenêtre et compte tenu du z-index
le plus élevé, la diapositive avant la diapositive focalisée est traduite hors écran vers la gauche et la diapositive après la diapositive mise au point est traduite hors écran vers la droite. Il convient de noter que les traductions xy n'affectent que le contenu visuel des diapositives, les parties interactives sont toujours superposées remplissant toute la zone de la fenêtre d'affichage, enfin presque ...
Je dis presque parce que pour que la mise au point soit déclenchée sur les diapositives suivantes et précédentes - via la souris ou le toucher - une partie de leurs couches interactives doit être accessible et cliquable par l'utilisateur. En utilisant un mélange de left
, right
et padding
les zones correctes peuvent être révélées sans déplacer les éléments visuels.
Ainsi, à mesure que la diapositive focalisée actuelle change, les zones des couches interactives qui sont accessibles le font également. Chaque fois qu'un utilisateur clique sur les flèches suivantes ou précédentes, il se concentre en fait sur l'élément <li>
adjacent, plutôt que de cliquer sur un lien qui effectue n'importe quel type d'action.
Pour que cela fonctionne sur les navigateurs Webkit, l'attribut tabindex
doit être utilisé pour que la pseudo-classe :focus
fonctionne sur l'élément de base que vous utilisez pour représenter une diapositive.
En raison des astuces utilisées, les diapositives seront lues dans un ordre dom inversé.
Vous pouvez naviguer dans le diaporama à l'aide de l'onglet, cependant, il reviendra en arrière, en raison du point 2. Si vous avez un Mac, vous devrez peut-être Ajuster vos paramètres os avant que l'onglet ne fonctionne .
En raison de l'astuce left
, right
pour exposer les flèches de navigation, il y a un léger problème visuel lors de la navigation vers l'avant - en ce sens que vous pouvez voir la flèche précédente suivante s'animer rapidement en place.
Étant donné que ce système fonctionne sur la base de :focus
chaque fois que le focus est perdu, le diaporama revient à la diapositive initiale, pour cette raison, les sous-liens ne fonctionneront pas dans vos diapositives - sauf si vous améliorez les interactions avec JavaScript.
Ma démo utilise des images d'arrière-plan SVG, elles sont évidemment facultatives et ne fonctionnent pas sur les anciens navigateurs.
IE7 et IE8 ne peuvent même pas comprendre
:last-child
ou:nth-child
donc non, cela ne fonctionne pas pour eux .
Dans la démo ci-dessous, vous verrez qu'il y a quelques fleurons JavaScript, ceux-ci aident à montrer ce que la construction peut faire, ou ils s'améliorent progressivement. Les cases à cocher, qui ne sont là que pour la démo, devraient vous permettre d'activer ou de désactiver certaines fonctionnalités. Ces fonctionnalités sont appliquées par des classes simples:
veuillez noter: le module complémentaire de réflexion repose sur des attributs de balisage arbitraires. Vous devrez ajouter des identifiants uniques à chaque .slide, puis étendre le CSS pour les prendre en compte.
Ok, donc pour commencer, voici la configuration de base. Tout d'abord, parce que mon diaporama contient des images, j'ai défini un style d'image de base, tout cela est facultatif.
.slide-gfx img {
max-width: 600px;
max-height: auto;
border-radius: 20px;
box-shadow: 0 0 80px rgba(255,255,255,1);
}
Le masque de curseur a été ajouté comme enveloppe à l'ensemble du diaporama, pour empêcher les barres de défilement de la fenêtre de s'afficher lors de l'utilisation du diaporama en plein écran. Ceci est à nouveau facultatif.
.css-slider-mask {
display: block;
overflow: hidden;
width: 100%;
height: 100%;
}
Nous obtenons maintenant la configuration réelle requise pour le curseur. Cette première partie est assez simple, sauf pour la partie display: none;
. Cela masque initialement le diaporama à tout le monde, mais est ensuite remplacé pour les navigateurs prenant en charge :nth-child
. Il est très probable que votre élément <body>
sera le 2ème enfant, mais vous devez vérifier avant d'utiliser cela.
.css-slider {
list-style: none;
margin: 0;
padding: 0;
width: 96%;
height: 100%;
margin-left: 2%;
z-index: 1;
}
.css-slider {
position: relative;
display: none;
}
body:nth-child(2) .css-slider {
display: block;
}
Ensuite, nous passons aux détails de la diapositive. En raison de l'inexistence d'un sélecteur général de frères et sœurs inversé (~
), tous les styles par défaut des diapositives représentent l'état futur (ou suivant). En effet, il n'existe pas de moyen réel de sélectionner les futures diapositives.
.css-slider .slide {
display: block;
position: absolute;
left: 40px;
top: 0;
right: 0;
bottom: 0;
padding-left: 0;
padding-right: 40px;
z-index: 100;
outline: 0; /* kill the focus rect! */
}
Encore une fois, par défaut, nous stylisons la flèche avant, puis nous remplaçons plus tard pour les diapositives actuelles et passées.
.css-slider .slide {
background: url('arrow-right.svg') no-repeat right center;
background-size: 25px auto;
}
.css-slider .slide:hover {
background-image: url('arrow-right-hover.svg');
cursor: pointer;
}
Maintenant, la diapositive ciblée, les éléments clés ici sont :focus
(comme je l'ai déjà expliqué) et :last-child
que je n'ai pas. Last Child est utilisé plutôt que First Child car, encore une fois, nous devons travailler en arrière (tout cela en raison de l'absence d'un inverseur General Sibling Selector~
). La raison pour laquelle l'un ou l'autre enfant est nécessaire est que nous puissions "concentrer" une diapositive initiale lorsqu'il n'y a pas de focus actuel, c'est-à-dire sur le chargement de la page.
.css-slider .slide:target,
.css-slider .slide:target:hover,
.css-slider .slide:focus,
.css-slider .slide:focus:hover,
.css-slider .slide:last-child,
.css-slider .slide:last-child:hover {
left: 40px;
right: 40px;
padding-left: 0;
padding-right: 0;
background: transparent;
z-index: 101;
cursor: default;
}
Maintenant, nous devons affecter toutes les diapositives qui se glissent dans le passé. J'ai évité de mentionner la pseudo-classe :target
avant maintenant, fondamentalement, cela a été implémenté pour supporter le "jump nav". Il y a deux raisons pour lesquelles je ne chanterai pas les louanges du "jump nav":
Quoi qu'il en soit, l'astuce pour sélectionner les diapositives du passé dépend du General Sibling Selector . La construction suivante signifie essentiellement sélectionnez .slide (s) que vous trouverez après le .slide qui a: focus.
.css-slider .slide:target ~ .slide,
.css-slider .slide:focus ~ .slide {
padding-left: 40px;
padding-right: 0;
left: 0;
right: 40px;
}
.css-slider .slide:target ~ .slide,
.css-slider .slide:focus ~ .slide {
background: url('arrow-left.svg') no-repeat left center;
background-size: 25px auto;
}
.css-slider .slide:target ~ .slide:hover,
.css-slider .slide:focus ~ .slide:hover {
background-image: url('arrow-left-hover.svg');
}
Ensuite, nous devons contrôler ce qui se passe exactement avec le contenu de nos diapositives. J'ai conçu ce système de sorte que si vous le souhaitez, vous pouvez laisser de côté la section d'animation. Cela signifie que les diapositives basculeront instantanément. Cette partie suivante s'occupe de cela.
.css-slider .slide .slide-outer {
display: none;
width: 100%;
height: 100%;
}
Slide-inner est uniquement utilisé pour gérer le centrage du contenu de la diapositive. Il repose sur l'affichage table
et l'affichage table-cell
.
.css-slider .slide .slide-outer .slide-inner {
display: table-cell;
vertical-align: middle;
text-align: center;
width: 100%;
height: 100%;
}
Slide-gfx est littéralement juste un wrapper contenant tout ce que vous décidez de mettre dans votre diaporama. La réflexion est générée à partir de ce conteneur, et quelques autres extras visuels y sont également attachés.
.css-slider .slide .slide-outer .slide-inner .slide-gfx {
position: relative;
display: inline-block;
z-index: 102;
text-align: left; /* override the centering back to defaults */
}
Cette partie est responsable du changement instantané lorsqu'aucune animation n'est incluse.
.css-slider .slide:target .slide-outer,
.css-slider .slide:focus .slide-outer,
.css-slider .slide:last-child .slide-outer {
display: block; /* if they don't support display table */
display: table;
}
En raison des déclarations :last-child
qui interviennent lorsque rien n'est ciblé, si vous n'effectuez plus de modifications, vous constaterez que certaines choses se brisent. En effet, :last-child
s'applique toujours, contrairement à :focus
. Pour corriger cela, nous devons annuler les modifications de :last-child
, mais uniquement lorsque quelque chose a été mis au point. C'est ce que fait le CSS suivant.
.css-slider .slide:target ~ .slide:last-child,
.css-slider .slide:focus ~ .slide:last-child {
cursor: pointer;
}
.css-slider .slide:target ~ .slide:last-child .slide-outer,
.css-slider .slide:focus ~ .slide:last-child .slide-outer {
display: none;
}
Jusqu'à présent, le CSS a été assez généralisé, mais il y a toujours quelque chose ...
Cette partie est uniquement nécessaire pour fixer la capacité de clic de la flèche "diapositive précédente", de sorte que le cadre précédent le plus récent flotte au-dessus de toute autre chose. Si vous ne vous souciez pas d'une action de diapositive précédente, vous pouvez supprimer cette étape, tant que vous masquez la flèche précédente. C'est assez ennuyeux car toute cette section arbitraire pourrait être supprimée si CSS supportait une version inversée du General Sibling Selector .
Fondamentalement, les éléments suivants prendront en charge jusqu'à 5 images, si vous en avez besoin, ajoutez-en plus. La bonne nouvelle, au moins, c'est que vous pouvez ajouter beaucoup plus de trames que vous n'en avez besoin sans aucun effet négatif réel. Évidemment, si vous dépassez 100 images, vous devrez ajuster d'autres z-index dans le reste du CSS.
.css-slider .slide:target ~ .slide:nth-child(1),
.css-slider .slide:focus ~ .slide:nth-child(1) { z-index:99; }
.css-slider .slide:target ~ .slide:nth-child(2),
.css-slider .slide:focus ~ .slide:nth-child(2) { z-index:98; }
.css-slider .slide:target ~ .slide:nth-child(3),
.css-slider .slide:focus ~ .slide:nth-child(3) { z-index:97; }
.css-slider .slide:target ~ .slide:nth-child(4),
.css-slider .slide:focus ~ .slide:nth-child(4) { z-index:96; }
.css-slider .slide:target ~ .slide:nth-child(5),
.css-slider .slide:focus ~ .slide:nth-child(5) { z-index:95; }
Comme je l'ai dit, l'animation est facultative, ainsi que tout le reste du CSS, c'est-à-dire les modules complémentaires visuels. Je vais inclure le reste ici avec moins de détails. La plupart est assez simple une fois que vous connaissez les astuces ci-dessus et les transitions ou animations CSS. La raison principale de son volume est, comme d'habitude, les préfixes des fournisseurs; que j'ai supprimé par souci de concision. Pour obtenir le CSS complet, vous pouvez évidemment le faire à partir de la démo ci-dessous.
veuillez noter: La plupart de ces modules complémentaires reposent sur des CSS assez modernes, c'est-à-dire des animations ou des SVG
/** --------------------------------------------------------------------------
* HANDLE THE SLIDE ANIMATION (optional)
* ------------------------------------------------------------------------ */
/* Override the default instant slide behaviour */
.css-slider .slide .slide-outer {
display: block !important;
display: table !important;
}
/* set up the transitions */
.css-slider .slide .slide-outer {
transition-property: opacity, transform;
transition-duration: 2s;
transition-timing-function: ease;
}
/* After state */
.css-slider .slide:target ~ .slide .slide-outer,
.css-slider .slide:target ~ .slide:last-child .slide-outer,
.css-slider .slide:focus ~ .slide .slide-outer,
.css-slider .slide:focus ~ .slide:last-child .slide-outer {
transform: translate(-150%,0);
transform: translate3D(-150%,0,0);
}
/* Before state */
.css-slider .slide .slide-outer {
transform: translate(200%,0);
transform: translate3D(200%,0,0);
}
/* Focused state*/
.css-slider .slide:target .slide-outer,
.css-slider .slide:focus .slide-outer,
.css-slider .slide:last-child .slide-outer {
transform: translate(0,0);
transform: translate3D(0,0,0);
}
/** --------------------------------------------------------------------------
* SMALL SCREEN FIX / SLIDE JERK (optional)
* ---------------------------------------------------------------------------
* When we shift 'left' and 'right' values -- in order to allow access to a future
* or past arrow -- this can cause a jump in the responsive scaling of the slide.
* if we transition the left value quickly, it can make this appear less jarring.
*/
.css-slider .slide {
transition-property: left, padding-left;
transition-duration: 1s;
transition-timing-function: ease;
}
/** --------------------------------------------------------------------------
* Add-on module : responsive images
* ------------------------------------------------------------------------ */
.with-responsive-images .slide-gfx img {
width: 100%;
height: auto;
}
/** --------------------------------------------------------------------------
* Add-on module : stop user selection
* ------------------------------------------------------------------------ */
/* if your slides don't need to be selectable, I recommend using this */
.with-selection-disabled {
user-select: none;
}
/** --------------------------------------------------------------------------
* Add-on module : initial fade in
* ------------------------------------------------------------------------ */
.with-fade-in .slide-gfx {
opacity: 0;
}
/* animate into visibility */
.with-fade-in .slide-gfx {
animation: css-slideshow-fade-in 2s;
animation-delay: 1s;
animation-fill-mode: forwards;
}
/* Vebdor animations */
@keyframes css-slideshow-fade-in { from { opacity: 0; } to { opacity: 1; } }
/** --------------------------------------------------------------------------
* Add-on module : slide reflection
* ------------------------------------------------------------------------ */
/* force our slide-gfx to be inline-block and relative positioned */
.with-reflection .slide-gfx {
display: inline-block !important;
}
/* reflection for webkit agents */
.with-reflection .slide-gfx > *:first-child {
-webkit-box-reflect: below 2px
-webkit-gradient(linear, left top, left bottom,
from(transparent), color-stop(0.9, transparent), to(white));
}
/* make sure internal images don't keep inline spacing/margin */
.with-reflection .slide-gfx img {
display: block;
}
/* generate the reflection */
.with-reflection .slide-gfx:after {
content: '';
position: absolute;
display: block;
mask: url("reflection-mask.svg#mask"); /* gradient fade the reflection */
transform: scaleY(-1); /* flip clone to appear as reflection */
opacity: 0.5; /* fade out reflection */
top: 100%;
width: 100%;
height: 60px;
z-index: 200;
margin-top: 2px;
}
/* again, due to element() requiring IDs we need arbitrary code
per each slide and each slide-gfx needs an id */
.with-reflection #s1:after { background: -moz-element(#s1) no-repeat left bottom; }
.with-reflection #s2:after { background: -moz-element(#s2) no-repeat left bottom; }
.with-reflection #s3:after { background: -moz-element(#s3) no-repeat left bottom; }
.with-reflection #s4:after { background: -moz-element(#s4) no-repeat left bottom; }
.with-reflection #s5:after { background: -moz-element(#s5) no-repeat left bottom; }
.with-reflection #s6:after { background: -moz-element(#s6) no-repeat left bottom; }
.with-reflection #s7:after { background: -moz-element(#s7) no-repeat left bottom; }
.with-reflection #s8:after { background: -moz-element(#s8) no-repeat left bottom; }
.with-reflection #s9:after { background: -moz-element(#s9) no-repeat left bottom; }
.with-reflection #s10:after { background: -moz-element(#s10) no-repeat left bottom; }
.with-reflection #s11:after { background: -moz-element(#s11) no-repeat left bottom; }
.with-reflection #s12:after { background: -moz-element(#s12) no-repeat left bottom; }
.with-reflection #s13:after { background: -moz-element(#s13) no-repeat left bottom; }
.with-reflection #s14:after { background: -moz-element(#s14) no-repeat left bottom; }
.with-reflection #s15:after { background: -moz-element(#s15) no-repeat left bottom; }
.with-reflection #s16:after { background: -moz-element(#s16) no-repeat left bottom; }
.with-reflection #s17:after { background: -moz-element(#s17) no-repeat left bottom; }
.with-reflection #s18:after { background: -moz-element(#s18) no-repeat left bottom; }
.with-reflection #s19:after { background: -moz-element(#s19) no-repeat left bottom; }
.with-reflection #s20:after { background: -moz-element(#s20) no-repeat left bottom; }
/** --------------------------------------------------------------------------
* Add-on module : slide zoom (optional, not compatible with slide float)
* ------------------------------------------------------------------------ */
.with-slide-zoom .slide .slide-gfx > *:first-child {
transition-property: max-width;
transition-duration: 2s;
transition-timing-function: ease-in-out;
transition-delay: 0.25s;
}
.with-slide-zoom .slide .slide-gfx > *:first-child:hover {
max-width: 1000px;
}
/** --------------------------------------------------------------------------
* Add-on module : slide float (optional, not compatible with slide zoom)
* ------------------------------------------------------------------------ */
/* inital transition set-up */
.with-slide-float:not(.with-slide-zoom) .slide .slide-gfx > *:first-child,
.with-slide-float-hover:not(.with-slide-zoom) .slide .slide-gfx > *:first-child {
transition-property: transform;
transition-duration: 2s;
transition-timing-function: ease-in-out;
}
/* we need a delay for the non-hover version */
.with-slide-float:not(.with-slide-zoom) .slide .slide-gfx > *:first-child {
transition-delay: 2s;
}
/* initial levitation on focus */
.with-slide-float:not(.with-slide-zoom) .slide:target .slide-gfx > *:first-child,
.with-slide-float:not(.with-slide-zoom) .slide:focus .slide-gfx > *:first-child,
.with-slide-float-hover:not(.with-slide-zoom) .slide .slide-gfx > *:first-child:hover {
transform: translate(0,-40px);
transform: translate3D(0,-40px,0);
}
/* trigger the float animation after 4s */
.with-slide-float:not(.with-slide-zoom) .slide:target .slide-gfx > *:first-child,
.with-slide-float:not(.with-slide-zoom) .slide:focus .slide-gfx > *:first-child,
.with-slide-float-hover:not(.with-slide-zoom) .slide .slide-gfx > *:first-child:hover {
animation: css-slideshow-levitate 4s;
animation-direction: alternate;
animation-fill-iteration-count: infinite;
animation-timing-function: ease-in-out;
animation-delay: 2s;
}
/* longer delay for automatic version i.e. non-hover */
.with-slide-float:not(.with-slide-zoom) .slide:target .slide-gfx > *:first-child,
.with-slide-float:not(.with-slide-zoom) .slide:focus .slide-gfx > *:first-child {
animation-delay: 4s;
}
/* Vebdor animations for the float */
@keyframes css-slideshow-levitate {
from { transform: translate(0,-40px); transform: translate3D(0,-40px,0); }
to { transform: translate(0,-20px); transform: translate3D(0,-20px,0); }
}
/** --------------------------------------------------------------------------
* Add-on module : ground shadow (optional)
* ------------------------------------------------------------------------ */
.with-shadow .slide .slide-gfx:before {
content: '';
background: url('slide-shadow.svg') no-repeat center center;
position: absolute;
bottom: -10px;
left: -20px;
right: -20px;
height: 20px;
z-index: -1;
opacity: 0.7;
}
.with-shadow.with-slide-float .slide .slide-gfx:before,
.with-shadow.with-slide-float-hover .slide .slide-gfx:before {
transition-property: opacity;
transition-duration: 2s;
transition-timing-function: ease-in-out;
}
.with-shadow.with-slide-float .slide .slide-gfx:before {
transition-delay: 2s;
}
.with-shadow.with-slide-float .slide:target .slide-gfx:before,
.with-shadow.with-slide-float .slide:focus .slide-gfx:before,
.with-shadow.with-slide-float .slide:last-child .slide-gfx:before,
.with-shadow.with-slide-float-hover .slide .slide-gfx:hover:before {
opacity: 0.1;
animation: css-slideshow-shadow 4s;
animation-delay: 4s;
animation-direction: alternate;
animation-fill-iteration-count: infinite;
animation-timing-function: ease-in-out;
}
.with-shadow.with-slide-float-hover .slide .slide-gfx:hover:before {
animation-delay: 2s;
}
/* Vebdor animations for the float */
@keyframes css-slideshow-shadow { from { opacity: 0.1; } to { opacity: 0.7; } }
http://codelamp.co.uk/css-slideshow/v0.2/
veuillez noter: la navigation par saut, c'est-à-dire les points circulaires, reposent sur un petit peu de JavaScript. Le reste est purement CSS.
Comme je l'ai dit, il a fallu pas mal de temps pour affiner ce système. Pour ceux qui pourraient être intéressés, voici ma (dernière) version 0.1 - il y en avait beaucoup auparavant;) Cela a pris une approche légèrement différente et a impliqué des parties mobiles visuelles et interactives. En fin de compte, je l'ai mis au rebut parce qu'il impliquait plus de traitement par navigateur et était donc beaucoup plus maladroit; quelque chose que cette démo de couleur unie ne révèle pas.