Learning to build a better slider part four
The buttons now work to some degree, but I’m trying to sort out an odd issue with the previous button not working properly. The controls now show up only if JavaScript is enabled, which is pretty neat. I still have a bunch to do to finish this up. I may take a break from it for a couple of days and come back to it later.
scroll or use your arrow keys to see more kittens
scroll for more cute kittens
use your left and right arrow keys to see more kittens
swipe to see more kittens
View the code:
HTML and JS
<div class="code-block">
<div class="gallery-container">
<div class="gallery no-js" role="group" aria-label="gallery" tabindex="0" aria-describedby="instructions">
<ul>
<li>
<figure>
<img class="dots" src='data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 6 2" stroke="currentColor" stroke-dasharray="1,0.5"><path d="M1,1 5,1" /></svg>' data-src="https://placekitten.com/500/500" alt="placeholder image of a cute kitteh">
<noscript>
<img src="https://placekitten.com/500/500" alt="placeholder image of a cute kitteh">
</noscript>
<figcaption>Gene Hackman</figcaption>
</figure>
</li>
<li>
<figure>
<img class="dots" src='data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 6 2" stroke="currentColor" stroke-dasharray="1,0.5"><path d="M1,1 5,1" /></svg>' data-src="https://placekitten.com/505/505" alt="placeholder image of a cute kitteh">
<noscript>
<img src="https://placekitten.com/505/505" alt="placeholder image of a cute kitteh">
</noscript>
<figcaption>Frederick Von Lickshimself</figcaption>
</figure>
</li>
<li>
<figure>
<img class="dots" src='data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 6 2" stroke="currentColor" stroke-dasharray="1,0.5"><path d="M1,1 5,1" /></svg>' data-src="https://placekitten.com/510/510" alt="placeholder image of a cute kitteh">
<noscript>
<img src="https://placekitten.com/510/510" alt="placeholder image of a cute kitteh">
</noscript>
<figcaption>Purrecious</figcaption>
</figure>
</li>
<li>
<figure>
<img class="dots" src='data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 6 2" stroke="currentColor" stroke-dasharray="1,0.5"><path d="M1,1 5,1" /></svg>' data-src="https://placekitten.com/515/515" alt="placeholder image of a cute kitteh">
<noscript>
<img src="https://placekitten.com/515/515" alt="placeholder image of a cute kitteh">
</noscript>
<figcaption>Fuzzball</figcaption>
</figure>
</li>
</ul>
<!-- list of gallery pictures -->
</div>
<div id="instructions" class="instructions">
<p id="hover-and-focus">scroll or use your arrow keys to see more kittens</p>
<p id="hover">scroll for more cute kittens</p>
<p id="focus">use your left and right arrow keys to see more kittens</p>
<p id="touch">swipe to see more kittens</p>
</div>
</div>
</div>
<script>
/* Variables for lazy loading of images - Another helpful bit of JS from Heydon Pickering's book, Inclusive Components - https://inclusive-components.design/a-content-slider/ */
const gallery = document.querySelector('.gallery')
gallery.classList.remove('no-js');
const slides = gallery.querySelectorAll('.gallery li')
const observerSettings = {
root: document.querySelector('.gallery'),
rootMargin: '-10px'
}
// Listen for a touch and add a touch class to the body element. Script and overall concepts by Heydon Pickering, https://inclusive-components.design/a-content-slider/. Gosh he's good at this stuff.
window.addEventListener('touchstart', function
touched() {
document.body.classList.add('touch');
window.removeEventListener('touchstart', touched, false)
}, false)
// IntersectionObserver script to help with lazy loading of slider images
if ('IntersectionObserver' in window) {
Array.prototype.forEach.call(slides, function(slide) {
let img = slide.querySelector('figure > img');
});
const callback = (slides, observer) => {
Array.prototype.forEach.call(slides, function (entry) {
entry.target.classList.remove('visible');
if (!entry.isIntersecting) {
return;
}
let img = entry.target.querySelector('img');
img.onload = () => img.classList.remove('dots');
img.setAttribute('src', img.dataset.src);
entry.target.classList.add('visible');
})
}
const observer = new IntersectionObserver(callback, observerSettings);
Array.prototype.forEach.call(slides, t => observer.observe(t));
} else {
Array.prototype.forEach.call(slides, function (s) {
let img = s.querySelector('img');
img.setAttribute('src', img.getAttribute('data-src'));
img.classList.remove('dots');
})
}
/* Build out controls buttons and prepare them to be displayed. */
const controls = document.createElement('ul')
controls.classList.add('gallery-controls');
controls.setAttribute('aria-label', 'gallery controls');
controls.innerHTML = `
<li>
<button class="previous" aria-label="previous picture"><</button>
</li>
<li>
<button class="next" aria-label="next picture">></button>
</li>
`;
const instructions = document.getElementById('instructions');
gallery.parentNode.insertBefore(controls, gallery);
gallery.parentNode.style.padding = '0 3rem';
/* Scrolling of slides */
function scrollIt(slideToShow) {
let scrollPos = Array.prototype.indexOf.call(slides, slideToShow) * (gallery.scrollWidth / slides.length);
console.log(scrollPos);
gallery.scrollLeft = scrollPos;
}
function showSlide(dir, slides) {
let visible = document.querySelectorAll('.gallery .visible');
let i = dir === 'previous' ? 0 : 1;
if (visible.length > 1) {
scrollIt(visible[1]);
} else {
let newSlide = i === 0 ? visible[0].previousElementSibling : visible[0].nextElementSibling;
if (newSlide) {
scrollIt(newSlide);
}
}
}
controls.addEventListener('click', function (e) {
console.log('button clicked');
showSlide(e.target.closest('button').id, slides)
})
</script>
CSS
.code-block {
max-width: 60em;
margin: 1rem auto;
}
.gallery-container {
position: relative;
}
[aria-label="gallery"] {
border: 2px solid;
padding: 1rem;
overflow-x: scroll;
scroll-behavior: smooth;
-webkit-overflow-scrolling: touch;
-webkit-scroll-snap-type: mandatory;
-ms-scroll-snap-type: mandatory;
scroll-snap-type: mandatory;
-webkit-scroll-snap-points-x: repeat(100%);
-ms-scroll-snap-points-x: repeat(100%);
scroll-snap-points-x: repeat(100%);
}
[aria-label="gallery"]:focus, [aria-label="galler controls"]:focus {
outline: 4px solid dodgerblue;
outline-offset: -6px;
}
[aria-label="gallery controls"] button:focus {
outline-offset: -4px;
}
[aria-label="gallery"] ul {
display: flex;
padding-left: 0;
margin: 0;
}
[aria-label="gallery"] li {
list-style-type: none;
flex: 0 0 100%;
padding: 2rem;
text-align: center;
}
[aria-label="gallery"] figure {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 50vh;
}
[aria-label="gallery"] figcaption {
padding: 0.5rem;
font-style: italic;
text-align: center;
}
[aria-label="gallery"] img {
max-width: 100%;
max-height: calc(100%-2rem);
margin-top: 2rem;
}
.instructions p {
background-color: #030303;
color: #fff;
text-align: center;
padding: 1rem;
margin-top: 0;
}
#focus, #hover, #hover-and-focus, #touch {
display: none;
}
[aria-label="gallery"]:focus + .instructions #focus,
[aria-label="gallery"]:hover + .instructions #hover {
display: block;
}
[aria-label="gallery"]:hover + .instructions #hover + #focus {
display: none;
}
[aria-label="gallery"]:hover:focus + .instructions #hover-and-focus {
display: block;
}
[aria-label="gallery"]:hover:focus + .instructions #hover-and-focus ~ * {
display: none;
}
.touch .instructions p {
display: none !important;
}
.touch .instructions #touch {
display: block !important;
}
.gallery {
position: relative;
}
.gallery img {
display: block;
margin: 2rem auto 0;
max-width: 100%;
max-height: calc(100% - 2rem);
min-width: 1px;
min-height: 1px;
}
.gallery figure noscript {
display: flex;
flex-direction: column;
justify-content: center;
width: 100%;
padding: 1rem;
height: 50vh;
}
noscript {
padding: 0;
}
[aria-label="gallery controls"] li {
list-style: none;
}
[aria-label="gallery controls"] button {
position: absolute;
top: 0;
background: #010101;
color: #fff;
border: 2px solid #010101;
border-radius: 0;
width: 3rem;
height: calc(60vh + 4px);
letter-spacing: 1px;
font-weight: bold;
cursor: pointer;
}
.previous {
left: 0;
}
.next {
right: 0;
}
@keyframes flash {
to {
opacity: 0;
}
}
img.dots {
max-width: 5rem;
max-height: 5rem;
padding: 0;
animation: flash 0.5s linear infinite;
}
.no-js .dots {
display: none;
}