On a recent project, I needed a simple image carousel on the homepage. And then, on the gallery page I needed a fully polished solution. Sometimes, using a framework is the right choice. Others, a fully built out toolkit can be overkill.
The Vanilla Option
First, here is the home-rolled version that I came up with. It was integrated into a custom WordPress template. I loop through a set of posts within my carousel wrapper, creating a slide div with that record’s featured image. I keep track of how many slides get built. Beneath the carousel wrapper I create a navigation div, and build a dot button for each slide. Each dot gets an index assigned to it, saved to its button’s data attribute.
HTML:
<div class="ap-carousel">
<?php $num_slides = 0; foreach($posts as $post){ $num_slides++; ?>
<div class="ap-slide">
<a href="<?php the_permalink($post->ID); ?>" title="<?php the_title(); ?>">
<img src="<?php echo esc_url(get_the_post_thumbnail_url($post->ID)); ?>" class="zoom">
</a>
</div>
<?php } ?>
<div class="nav-dots">
<?php $active = "active-dot"; for($x = 0; $x < $num_slides; $x++){ ?>
<div class="dot"><button data="<?php echo $x; ?>" type="button" class="dot-button <?php echo $active; $active = ''; ?>">b</button></div>
<?php } ?>
</div>
</div>
CSS:
I used CSS animation to create a fade effect between slides. I position the navigation dots using CSS flexbox layout.
.ap-carousel{
position: relative;
}
.ap-slide{
display: none;
margin: 0 auto;
}
.ap-slide img{
width: auto;
display: block;
margin: 0 auto;
max-height: 90vh;
-webkit-animation-name: fade;
-webkit-animation-duration: 1.5s;
animation-name: fade;
animation-duration: 1.5s;
}
@-webkit-keyframes fade {
from {opacity: .4}
to {opacity: 1}
}
@keyframes fade {
from {opacity: .4}
to {opacity: 1}
}
.nav-dots{
display: flex;
justify-content: center;
}
.dot button{
display: block;
border-radius: 100%;
width: 12px;
height: 12px;
margin-right: 10px;
padding: 0;
border: none;
text-indent: -9999px;
background: black;
cursor: pointer;
}
.dot button.active-dot{
background: red;
}
JavaScript:
Finally, I create a JS function to change the slide and active dot based on a timer. I attach an event listener to the dots that will change the active slide based on the saved index data.
var slideIndex = 0;
showSlides();
function showSlides() {
var i;
var slides = document.getElementsByClassName("ap-slide");
var dots = document.getElementsByClassName("dot-button");
for (i = 0; i < slides.length; i++) { slides[i].style.display = "none"; dots[i].classList.remove("active-dot"); } slideIndex++; if (slideIndex > slides.length) {slideIndex = 1}
slides[slideIndex-1].style.display = "block";
dots[slideIndex-1].classList.add("active-dot")
setTimeout(showSlides, 5000); // Change image every 5 seconds
}
document.addEventListener('click', function(event){
if(!event.target.matches('.dot-button')) return;
slideIndex = event.target.getAttribute("data");
showSlides();
}, false);
That’s a simple and lite solution. It worked fine for the homepage of this recent project, but the main gallery page needed something more complex. I choose Galleria, a JavaScript framework.
The Framework Option

I implemented this option onto the WordPress category archive page. For this project, each piece of artwork is its own post. In my category template file I loop through posts, and populate a JSON object with the data about each slide. Initially, I had built HTML elements for each slide, but that caused slow page load times. The JSON data option is significantly faster. Here’s what my code setup looked like:
<div id="galleria"></div>
<script type="text/javascript">
window.galleryData = [];
</script>
<?php if (have_posts()): while (have_posts()) : the_post();
$featured_img_url = get_the_post_thumbnail_url();
?>
<script>
window.galleryData.push({ image: "<?php echo esc_url($featured_img_url); ?>", artinfo: "<div class='galleria-img-info'><h3 class='title'><a href='<?php the_permalink(); ?>'><?php the_title(); ?></a></h3><?php $size=get_post_meta(get_the_ID(), 'size', true);$size=addslashes($size);$date=get_post_meta(get_the_ID(), 'date', true);$materials=get_post_meta(get_the_ID(), 'materials', true);if(! empty ( $size ) ){echo '<p><strong>Dimensions:</strong> ' . $size . '</p>';}if(! empty ( $date ) ){echo '<p><strong>Date:</strong> ' . $date . '</p>';}if(! empty ( $materials ) ){echo '<p><strong>Materials:</strong> ' . $materials . '</p>';} ?><p class='you-can-mouse'>You can click the image to enlarge it. </p></div></div>" })
</script>
<?php } ?>
<script src="/galleria/galleria-1.5.7.js"></script>
<script type="text/javascript">
// Load the classic theme
Galleria.loadTheme('/galleria/galleria.classic.min.js');
//https://docs.galleria.io/collection/25-options
Galleria.configure({
imageCrop: false,
transitionSpeed:1000,
maxScaleRatio:1,
swipe:true,
thumbnails: 'none',
transition: 'fade',
lightbox: true
});
// Initialize Galleria
Galleria.run('#galleria', {dataSource: window.galleryData, autoplay: 5000, extend: function() {
// var gallery = this; // "this" is the gallery instance
// gallery.play(); // call the play method
}
});
Galleria.ready(function() {
$(".loading").hide();
this.bind('image', function(e) {
});
});
</script>