<div class="slider position-relative">
<div class="slider-port" role="region" aria-label="slider" tabindex="0">
<ul class="slides sibling-margins-skip">
<li class="slide">
<div class="card">
<div class="card-img-scalable">
<img data-lazyimage="../../img/unsplash/alex-knight-4NJoYCuWDok-unsplash.jpg" alt="alt tag for the image" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' /%3E"> </div>
<div class="card-body">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<p><a href="#">Learn More</a></p>
</div>
</div>
</li>
<li class="slide">
<div class="card">
<div class="card-img-scalable">
<img data-lazyimage="../../img/unsplash/beasty--HxIhfS_dUk-unsplash.jpg" alt="alt tag for the image" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' /%3E"> </div>
<div class="card-body">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<p><a href="#">Learn More</a></p>
</div>
</div>
</li>
<li class="slide">
<div class="card">
<div class="card-img-scalable">
<img data-lazyimage="../../img/unsplash/danist-LZ7-lLCRS3s-unsplash.jpg" alt="alt tag for the image" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' /%3E"> </div>
<div class="card-body">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<p><a href="#">Learn More</a></p>
</div>
</div>
</li>
<li class="slide">
<div class="card">
<div class="card-img-scalable">
<img data-lazyimage="../../img/unsplash/nori-webb-fORnjg5ahmM-unsplash.jpg" alt="alt tag for the image" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' /%3E"> </div>
<div class="card-body">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<p><a href="#">Learn More</a></p>
</div>
</div>
</li>
<li class="slide">
<div class="card">
<div class="card-img-scalable">
<img data-lazyimage="../../img/unsplash/oleksii-drozdov-aFHxJx-aKMk-unsplash.jpg" alt="alt tag for the image" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' /%3E"> </div>
<div class="card-body">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<p><a href="#">Learn More</a></p>
</div>
</div>
</li>
<li class="slide">
<div class="card">
<div class="card-img-scalable">
<img data-lazyimage="../../img/unsplash/thomas-drouault-Y1UtWeiRmhE-unsplash.jpg" alt="alt tag for the image" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' /%3E"> </div>
<div class="card-body">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<p><a href="#">Learn More</a></p>
</div>
</div>
</li>
</ul>
</div>
</div>
<div class="slider{{slider_type}} position-relative">
<div class="slider-port" role="region" aria-label="slider" tabindex="0">
<ul class="slides sibling-margins-skip">
{{#each slides}}
<li class="slide">
{{> @image-card }}
</li>
{{/each}}
</ul>
</div>
</div>
{
"slider_type": "",
"slides": [
{
"lazyimage_src": "../../img/unsplash/alex-knight-4NJoYCuWDok-unsplash.jpg",
"lazyimage_alt": "alt tag for the image",
"image-card_body": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p><p><a href=\"#\">Learn More</a></p>"
},
{
"lazyimage_src": "../../img/unsplash/beasty--HxIhfS_dUk-unsplash.jpg",
"lazyimage_alt": "alt tag for the image",
"image-card_body": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p><p><a href=\"#\">Learn More</a></p>"
},
{
"lazyimage_src": "../../img/unsplash/danist-LZ7-lLCRS3s-unsplash.jpg",
"lazyimage_alt": "alt tag for the image",
"image-card_body": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p><p><a href=\"#\">Learn More</a></p>"
},
{
"lazyimage_src": "../../img/unsplash/nori-webb-fORnjg5ahmM-unsplash.jpg",
"lazyimage_alt": "alt tag for the image",
"image-card_body": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p><p><a href=\"#\">Learn More</a></p>"
},
{
"lazyimage_src": "../../img/unsplash/oleksii-drozdov-aFHxJx-aKMk-unsplash.jpg",
"lazyimage_alt": "alt tag for the image",
"image-card_body": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p><p><a href=\"#\">Learn More</a></p>"
},
{
"lazyimage_src": "../../img/unsplash/thomas-drouault-Y1UtWeiRmhE-unsplash.jpg",
"lazyimage_alt": "alt tag for the image",
"image-card_body": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p><p><a href=\"#\">Learn More</a></p>"
}
]
}
document.addEventListener("DOMContentLoaded", function() {
if ("IntersectionObserver" in window) {
// find all the sliders
var sliders = [].slice.call(document.querySelectorAll(".slider"));
// function to scroll to a certain slide
var scrollIt = function scrollIt(slider,slideToShow) {
var slides = slider.querySelectorAll('.slide');
var sliderPort = slider.querySelector('.slider-port');
var scrollPos = slideToShow.offsetLeft;
sliderPort.scrollLeft = scrollPos;
setTimeout(function(){ sliderPort.scrollLeft = scrollPos; },75); // i am doing this because there is something strage about setting scroll left position
};
// function to show a slide
var showSlide = function showSlide(slider,direction) {
var slides = slider.querySelectorAll('.slide');
var sliderPort = slider.querySelector('.slider-port');
var visible = slider.querySelectorAll('.visible');
var max_visible = Math.round(sliderPort.offsetWidth / slides[0].offsetWidth);
var i = direction === 'previous' ? 0 : 1;
// if more than one is visible, scroll to the last visible
if (visible.length > max_visible) {
// if next
if (direction) {
scrollIt(slider,visible[1]);
}
// if previous
else {
scrollIt(slider,visible[0])
}
}
else {
var newSlide = i === 0 ? visible[0].previousElementSibling : visible[0].nextElementSibling;
if (newSlide) {
scrollIt(slider,newSlide);
}
}
};
// do for each slider
sliders.forEach(function(slider) {
// find all the slides
var slides = slider.querySelectorAll('.slide');
// the slider port is the container for the slides
var sliderPort = slider.querySelector('.slider-port');
// set the default tab index such that the slide links cannot be focused
Array.prototype.forEach.call(slides, function (slide) {
if (slide.querySelector('a')) {
slide.querySelector('a').setAttribute('tabindex', '-1');
}
});
// settings for intersection observer
var observerSettings = {
root: sliderPort
};
// observe for when slide is visible
var slide_observer = new IntersectionObserver(function callback(slides, observer) {
Array.prototype.forEach.call(slides, function (entry) {
var link = entry.target.querySelector('a');
if (entry.isIntersecting) {
// if the slide is in the port, make it visible and focusable
entry.target.classList.add('visible');
if (link) { link.removeAttribute('tabindex', '-1'); }
}
else {
// otherwise, make it not visible and not focusable
entry.target.classList.remove('visible');
if (link) { link.setAttribute('tabindex', '-1'); }
}
});
}, observerSettings);
Array.prototype.forEach.call(slides, function(slide) {
// observe all the slides
return slide_observer.observe(slide);
});
// observe for the start of the slides
var start_observer = new IntersectionObserver(function callback(slides, observer) {
Array.prototype.forEach.call(slides, function (entry) {
if (entry.isIntersecting) {
entry.target.parentNode.parentNode.parentNode.querySelector('.slider-control-prev').setAttribute('disabled','disabled');
}
else {
entry.target.parentNode.parentNode.parentNode.querySelector('.slider-control-prev').removeAttribute('disabled');
}
});
}, observerSettings);
start_observer.observe(slides[0]);
// observe for the end of the slides
var end_observer = new IntersectionObserver(function callback(slides, observer) {
Array.prototype.forEach.call(slides, function (entry) {
if (entry.isIntersecting) {
entry.target.parentNode.parentNode.parentNode.querySelector('.slider-control-next').setAttribute('disabled','disabled');
}
else {
entry.target.parentNode.parentNode.parentNode.querySelector('.slider-control-next').removeAttribute('disabled');
}
});
}, observerSettings);
end_observer.observe(slides[slides.length - 1]);
// add controls
var controls = document.createElement('ul');
controls.setAttribute('aria-label', 'slider controls');
controls.className = "slider-controls list-unstyled p-0 m-0";
controls.innerHTML = '\n <li class="d-block p-0 m-0"><button class="slider-control slider-control-prev arrow prev" aria-label="previous">Previous</button></li>\n <li class="d-block p-0 m-0"><button class="slider-control slider-control-next arrow next" aria-label="next">Next</button></li>';
slider.append(controls);
// add event handlers to controls
var prev = controls.querySelector('.slider-control-prev');
var next = controls.querySelector('.slider-control-next');
prev.addEventListener('click', function(e) {
e.preventDefault();
showSlide(slider,'previous');
});
next.addEventListener('click', function(e) {
e.preventDefault();
showSlide(slider,'next');
});
});
}
});
$break-slider-control:640px;
$break-slider-2:600px;
$break-slider-3:900px;
.slider {
.slider-port {
margin-left:auto;
margin-right:auto;
overflow-x:scroll;
position:relative;
scroll-snap-type:x mandatory;
scroll-behavior:smooth;
-webkit-overflow-scrolling:touch;
width:100%;
@media (min-width:$break-slider-control) {
width:calc(100% - 5rem);
}
}
.slides {
display:flex;
padding:0;
padding-bottom:1rem;
.slide {
display:block;
flex-shrink:0;
margin:0;
padding:0;
scroll-snap-align:start;
width:100%;
}
.slide + .slide {
margin-left:16px;
}
}
.slider-controls {
@media (max-width:$break-slider-control - 1px) {
display:flex;
justify-content:space-between;
}
}
.slider-control {
background:transparent;
border:0;
padding:0;
@media (min-width:$break-slider-control) {
position:absolute;
top:50%;
&.slider-control-prev {
left:0;
}
&.slider-control-next {
right:0;
}
}
}
&.slider-2 {
.slides {
.slide {
@media (min-width:$break-slider-2) {
width:calc(50% - (16px / 2));
}
}
}
}
&.slider-3 {
.slides {
.slide {
@media (min-width:$break-slider-2) {
width:calc(50% - (16px / 2));
}
@media (min-width:$break-slider-3) {
width:calc(33.333% - (32px / 3));
}
}
}
}
}
This slider pattern is based on work done by Heydon Pickering for his Inclusive Components project. The goal is to create a version of the slider pattern (sometimes called a carousel) that is efficient (in terms of code) and accessible.
Pickering’s example is 3 years old at the time of this writing and some of the code no longer works. It was necessary to modify or rebuild some of the code to work with the current web standards. I also added some functions and removed others.
This pattern uses Bootstrap cards as the example carousel content. However, it should work for all kinds of elements.
Slick is a feature-packed carousel option that unfortunately has some accessibility issues. This slider may have fewer options than Slick, but it also requires less code and is more accessible.
Bootstrap has a carousel component, but it is intended for only one item at a time. The slider presented here is more flexible.