Yesterday I pushed the latest version of CSS Slidy, the framework-free, super-simple responsive image slider. I’ve also uploaded a working example on CodePen. The new version offers almost a dozen new options.
Markup for the slider remains very straightforward, with the additional option of captions:
<div id="slidy-container">
<figure id="slidy">
<img src="antelope-canyon.jpg" alt data-caption="Antelope Canyon, Arizona">
<img src="canyonlands.jpg" alt data-caption="Canyonlands Vista, Arizona" >
<img src="mesa-arch.jpg" alt data-caption="Mesa Arch sunrise, Moab, Utah">
<img src="wave-canyon.jpg" alt data-caption="Canyonlands, Arizona">
</figure>
</div>
To keep things even simpler, it’s also possible to use the image’s alt text for caption content, but the practice is not generally recommended: alt describes the image (for example, to the blind); caption content may be completely different. (I’ve left alt values empty in the code above for the purposes of illustration).
The initial CSS is very easy:
#slidy-container { width: 70%; margin: 0 auto; overflow: hidden; }
At the bottom of the page, link to the JavaScript slidy code:
<script src="cssslidy.js"></script>
Then, call it:
<script>cssSlidy();</script>
When called, CSSslidy uses default settings, which I have designed to be a good compromise for most designs. However, you can change those options at will:
- the slider images can be driven to the left or right by switching the
slidyDirectionoption. - Every image can display a caption. To retain accessibility, caption text is drawn from
data-captionattribute values by default. You can also usealtvalues, or choose not to display captions at all- Captions can be shown at the top or bottom of the slider
- Captions can be displayed permanently, or hidden until mouseover / touch
- Hidden captions can be transitioned into visibility by fading or sliding them in.
- Caption
font-size, color,background-colorandpaddingcan all be specified.
Inherited from the 1.5 update, you can also specify your own custom names for the animation and page elements.
An example of CSSSlidy called with all options modified:
<script>
cssSlidy({
timeOnSlide: 5,
timeBetweenSlides: .5,
slidyContainerSelector: 'custom-container',
slidySelector: 'custom-slidy',
captionSource: 'data-caption',
captionBackground: 'rgba(0,0,0,0.9)',
captionColor: '#ff0',
captionFont: 'Brittanic Bold, Hevetica, sans-serif',
captionPosition: 'top',
captionAppear: 'perm',
captionSize: '16px',
captionPadding: '1em',
fallbackFunction: function(){ alert("Oh noes! You can't animate!"); }, cssAnimationName: 'custom-animation'
});
</script>
The CSSslidy code, in its entirety (still less than 4K when minimized):
var cssSlidy = function(newOptions) {
var options = (function() {
var mergedOptions = {},
defaultOptions = {
timeOnSlide: 3,
timeBetweenSlides: 1,
slidyContainerSelector: '#slidy-container', // name of slider container
slidySelector: '#slidy', // name of slider
slidyDirection: 'left', // options: left, right
fallbackFunction: function() {},
cssAnimationName: 'slidy',
captionSource: 'data-caption', // options: data-caption, alt, none
captionBackground: 'rgba(0,0,0,0.3)',
captionColor: '#fff',
captionFont: 'Avenir, Avenir Next, Droid Sans, DroidSansRegular, Corbel, Tahoma, Geneva, sans-serif',
captionPosition: 'bottom', // options: top, bottom
captionAppear: 'slide', // options: slide, perm, fade
captionSize: '1.6rem',
captionPadding: '.6rem'
};
for (var option in defaultOptions) mergedOptions[option] = defaultOptions[option];
for (var option in newOptions) mergedOptions[option] = newOptions[option];
return mergedOptions;
})(),
CS = this;
CS.animationString = 'animation';
CS.hasAnimation = false;
CS.keyframeprefix = '';
CS.domPrefixes = 'Webkit Moz O Khtml'.split(' ');
CS.pfx = '';
CS.element = document.getElementById(options.slidySelector.replace('#', ''));
CS.init = (function() {
// browser supports keyframe animation w/o prefixes
if (CS.element.style.animationName !== undefined) CS.hasAnimation = true;
// browser supports keyframe animation w/ prefixes
if (CS.hasAnimation === false) {
for (var i = 0; i < CS.domPrefixes.length; i++) {
if (CS.element.style[CS.domPrefixes[i] + 'AnimationName'] !== undefined) {
CS.pfx = domPrefixes[i];
CS.animationString = pfx + 'Animation';
CS.keyframeprefix = '-' + pfx.toLowerCase() + '-';
CS.hasAnimation = true;
// determines CSS prefix required for CSS animations
break;
}}}
if (CS.hasAnimation === true) {
var images = CS.element.getElementsByTagName("img"),
L = images.length,
fig = document.createElement('figure'),
who, temp;
while(L) {
temp = fig.cloneNode(false);
who = images[--L];
// wraps all images in the slider with <figure> tags
if (options.captionSource!=="none")
caption = who.getAttribute(options.captionSource);
who.parentNode.insertBefore(temp, who);
if (caption !== null) {
content = document.createElement('figcaption');
content.innerHTML = caption;
// places captions in each <figure> element, if required
}
temp.appendChild(who);
if (caption !== null) {
temp.appendChild(content);
}}
var figs = CS.element.getElementsByTagName("figure");
var firstFig = figs[0];
// get the first figure inside the "slidy" element.
figWrap = firstFig.cloneNode(true);
// copy it.
CS.element.appendChild(figWrap);
// add the clone to the end of the images
var imgCount = images.length,
// count the number of images in the slide, including the new cloned element
totalTime = (options.timeOnSlide + options.timeBetweenSlides) * (imgCount - 1),
// calculate the total length of the animation by multiplying the number of _actual_ images by the amount of time for both static display of each image and motion between them
slideRatio = (options.timeOnSlide / totalTime) * 100,
// determine the percentage of time an individual image is held static during the animation
moveRatio = (options.timeBetweenSlides / totalTime) * 100,
// determine the percentage of time for an individual movement
basePercentage = 100 / imgCount,
// work out how wide each image should be in the slidy, as a percentage.
position = 0,
// set the initial position of the slidy element
css = document.createElement("style");
// start marking a new style sheet
css.type = "text/css";
css.id = options.slidySelector.replace('#', '') + "-css";
css.innerHTML += options.slidyContainerSelector + " { overflow: hidden; }\n";
css.innerHTML += options.slidySelector + " { text-align: left; margin: 0; font-size: 0; position: relative; width: " + (imgCount * 100) + "%; }\n";
// set the width for the inner slider container
css.innerHTML += options.slidySelector + " figure { float: left; margin: 0; position: relative; display: inline-block; width: " + basePercentage + "%; height: auto; }\n";
// set the width and size pf the inner <figure> elements
css.innerHTML += options.slidySelector + " figure img { width: 100%; }\n";
css.innerHTML += options.slidySelector + " figure figcaption { position: absolute; width: 100%; background-color: " + options.captionBackground + "; color: " + options.captionColor + "; font-family: " + options.captionFont + ";";
var captions = document.getElementsByTagName("figcaption");
var captionHeight = captions[0].offsetHeight*2 + parseInt(window.getComputedStyle(captions[0]).fontSize, 10);
if (options.captionPosition == "top") {
switch (options.captions) {
case 'fade':
css.innerHTML += " top: 0; opacity: 0;";
break;
case 'slide':
.innerHTML += " top: -"+captionHeight+"px; ";
break;
default:
css.innerHTML += " top: 0;";
}
} else {
switch (options.captionAppear) {
case 'fade':
css.innerHTML += " bottom: 0; opacity: 0;";
break;
case 'slide':
css.innerHTML += " bottom: -"+captionHeight+"px; ";
break;
default:
css.innerHTML += " bottom: 0;";
}}
css.innerHTML += " font-size: " + options.captionSize + "; padding: " + options.captionPadding + "; " + keyframeprefix + "transition: .5s; }\n";
css.innerHTML += options.slidySelector + ":hover figure figcaption { opacity: 1; ";
if (options.captionPosition == "top") { css.innerHTML += " top: 0px;"; } else { css.innerHTML += " bottom: 0px;"; }
css.innerHTML += " }\n";
css.innerHTML += "@" + keyframeprefix + "keyframes " + options.cssAnimationName + " {\n";
if (options.slidyDirection == "right") {
for (i = imgCount - 1; i > 0; i--) {
position += slideRatio;
// make the keyframe the position of the image
css.innerHTML += position + "% { left: -" + (i * 100) + "%; }\n";
position += moveRatio;
// make the postion for the _next_ slide
css.innerHTML += position + "% { left: -" + ((i - 1) * 100) + "%; }\n";
}} else {
// the slider is moving to the left
for (i = 0; i < (imgCount - 1); i++) {
position += slideRatio;
// make the keyframe the position of the image
css.innerHTML += position + "% { left: -" + (i * 100) + "%; }\n";
position += moveRatio;
// make the postion for the _next_ slide
css.innerHTML += position + "% { left: -" + ((i + 1) * 100) + "%; }\n";
}}
css.innerHTML += "}\n";
css.innerHTML += options.slidySelector + " { ";
if (options.slidyDirection == "right") { css.innerHTML += "left: " + imgCount*100+"%" }
else { css.innerHTML += "left: 0%; " }
css.innerHTML += keyframeprefix + "transform: translate3d(0,0,0); " + keyframeprefix + "animation: " + totalTime + "s " + options.cssAnimationName + " infinite; }\n";
// call on the completed keyframe animation sequence
if (options.cssLocation !== undefined) options.cssLocation.appendChild(css);
else document.body.appendChild(css);
} else {
// fallback function
options.fallbackFunction();
}
})();
}
The one potential quirk is dealing with caption font-size in responsive design: because the script places the CSS for the slider at the end of the page, any styles placed at the start of the page that affect font-size will have to be raised in priority by using !important. For example:
@media screen and (max-width: 600px) {
#slidy-container figcaption { font-size: 1rem !important; }
}
I hope you find the new features useful, and look forward to your feedback!
Photographs by Eliyasj, Vanessa Kay, Krasimir Ganchev and Charles “Skip” Martin, licensed under Creative Commons.