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- Caption
font-size, color,background-colorandpaddingcan all be specified.
- Caption
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.