{"id":236,"date":"2019-09-15T04:26:03","date_gmt":"2019-09-15T04:26:03","guid":{"rendered":"https:\/\/www.antpace.com\/blog\/?p=236"},"modified":"2025-08-25T13:58:20","modified_gmt":"2025-08-25T13:58:20","slug":"easy-image-carousel","status":"publish","type":"post","link":"https:\/\/www.antpace.com\/blog\/easy-image-carousel\/","title":{"rendered":"Easy image carousel"},"content":{"rendered":"<p>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.<\/p>\n<h2>The Vanilla Option<\/h2>\n<p>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&#8217;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&#8217;s data attribute.<\/p>\n<p><strong>HTML<\/strong>:<\/p>\n<pre>&lt;div class=\"ap-carousel\"&gt;\n\n&lt;?php $num_slides = 0; foreach($posts as $post){ $num_slides++; ?&gt;\n\n\t&lt;div class=\"ap-slide\"&gt;\n\t\t&lt;a href=\"&lt;?php the_permalink($post-&gt;ID); ?&gt;\" title=\"&lt;?php the_title(); ?&gt;\"&gt;\n\t\t\t&lt;img src=\"&lt;?php echo esc_url(get_the_post_thumbnail_url($post-&gt;ID)); ?&gt;\" class=\"zoom\"&gt;\n\t\t&lt;\/a&gt;\n\t&lt;\/div&gt;\n\n&lt;?php } ?&gt;\n&lt;div class=\"nav-dots\"&gt;\n\t&lt;?php $active = \"active-dot\"; for($x = 0; $x &lt; $num_slides; $x++){ ?&gt;\n\t\t&lt;div class=\"dot\"&gt;&lt;button data=\"&lt;?php echo $x; ?&gt;\" type=\"button\" class=\"dot-button &lt;?php echo $active; $active = ''; ?&gt;\"&gt;b&lt;\/button&gt;&lt;\/div&gt;\n\t&lt;?php } ?&gt;\n&lt;\/div&gt;\n\n\n&lt;\/div&gt;\n\n<\/pre>\n<p><strong>CSS:<\/strong><\/p>\n<p>I used CSS animation to create a fade effect between slides. I position the navigation dots using CSS flexbox layout.<\/p>\n<pre>.ap-carousel{\n\tposition: relative;\n}\n.ap-slide{\n\tdisplay: none;\n\tmargin: 0 auto;\n}\n.ap-slide img{\n\twidth: auto;\n\tdisplay: block;\n\tmargin: 0 auto;\n\tmax-height: 90vh;\n\t-webkit-animation-name: fade;\n\t-webkit-animation-duration: 1.5s;\n\tanimation-name: fade;\n\tanimation-duration: 1.5s;\n}\n@-webkit-keyframes fade {\n\tfrom {opacity: .4}\n\tto {opacity: 1}\n}\n@keyframes fade {\n\tfrom {opacity: .4}\n\tto {opacity: 1}\n}\n.nav-dots{\n\tdisplay: flex;\n\tjustify-content: center;\n}\n.dot button{\n\tdisplay: block;\n\tborder-radius: 100%;\n\twidth: 12px;\n\theight: 12px;\n\tmargin-right: 10px;\n\tpadding: 0;\n\tborder: none;\n\ttext-indent: -9999px;\n\tbackground: black;\n\tcursor: pointer;\n}\n.dot button.active-dot{\n\tbackground: red;\n}\n\n<\/pre>\n<p><strong>JavaScript:<\/strong><\/p>\n<p>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.<\/p>\n<pre>var slideIndex = 0;\nshowSlides();\n\nfunction showSlides() {\n\tvar i;\n\tvar slides = document.getElementsByClassName(\"ap-slide\");\n\tvar dots = document.getElementsByClassName(\"dot-button\");\n\tfor (i = 0; i &lt; slides.length; i++) { slides[i].style.display = \"none\"; dots[i].classList.remove(\"active-dot\"); } slideIndex++; if (slideIndex &gt; slides.length) {slideIndex = 1}\n\tslides[slideIndex-1].style.display = \"block\";\n\tdots[slideIndex-1].classList.add(\"active-dot\")\n\tsetTimeout(showSlides, 5000); \/\/ Change image every 5 seconds\n}\n\ndocument.addEventListener('click', function(event){\n\tif(!event.target.matches('.dot-button')) return;\n\n\tslideIndex = event.target.getAttribute(\"data\");\n\tshowSlides();\n}, false);\n\n<\/pre>\n<p>That&#8217;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.<\/p>\n<h2>The Framework Option<\/h2>\n<figure id=\"attachment_484\" aria-describedby=\"caption-attachment-484\" style=\"width: 987px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-484\" src=\"https:\/\/www.antpace.com\/blog\/wp-content\/uploads\/2019\/09\/art-carousel.png\" alt=\"Carousel showcasing artwork\" width=\"987\" height=\"532\" \/><figcaption id=\"caption-attachment-484\" class=\"wp-caption-text\">Carousel showcasing artwork<\/figcaption><\/figure>\n<p>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&#8217;s what my code setup looked like:<\/p>\n<pre>&lt;div id=\"galleria\"&gt;&lt;\/div&gt;\n&lt;script type=\"text\/javascript\"&gt;\n\twindow.galleryData = [];\n&lt;\/script&gt;\n&lt;?php if (have_posts()): while (have_posts()) : the_post();\n\n$featured_img_url = get_the_post_thumbnail_url();\n\n?&gt;\n\n&lt;script&gt;\nwindow.galleryData.push({ image: \"&lt;?php echo esc_url($featured_img_url); ?&gt;\", artinfo: \"&lt;div class='galleria-img-info'&gt;&lt;h3 class='title'&gt;&lt;a href='&lt;?php the_permalink(); ?&gt;'&gt;&lt;?php the_title(); ?&gt;&lt;\/a&gt;&lt;\/h3&gt;&lt;?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 '&lt;p&gt;&lt;strong&gt;Dimensions:&lt;\/strong&gt; ' . $size . '&lt;\/p&gt;';}if(! empty ( $date ) ){echo '&lt;p&gt;&lt;strong&gt;Date:&lt;\/strong&gt; ' . $date . '&lt;\/p&gt;';}if(! empty ( $materials ) ){echo '&lt;p&gt;&lt;strong&gt;Materials:&lt;\/strong&gt; ' . $materials . '&lt;\/p&gt;';} ?&gt;&lt;p class='you-can-mouse'&gt;You can click the image to enlarge it. &lt;\/p&gt;&lt;\/div&gt;&lt;\/div&gt;\" })\n&lt;\/script&gt;\n\n&lt;?php } ?&gt;\n\n&lt;script src=\"\/galleria\/galleria-1.5.7.js\"&gt;&lt;\/script&gt;\n&lt;script type=\"text\/javascript\"&gt;\n\/\/ Load the classic theme\nGalleria.loadTheme('\/galleria\/galleria.classic.min.js');\n\/\/https:\/\/docs.galleria.io\/collection\/25-options\nGalleria.configure({\n    imageCrop: false,\n    transitionSpeed:1000,\n    maxScaleRatio:1,\n    swipe:true,\n    thumbnails: 'none',\n    transition: 'fade',\n    lightbox: true\n});\n\/\/ Initialize Galleria\nGalleria.run('#galleria', {dataSource: window.galleryData, autoplay: 5000, extend: function() {\n            \/\/ var gallery = this; \/\/ \"this\" is the gallery instance\n            \/\/ gallery.play(); \/\/ call the play method\n        }\n});\n\nGalleria.ready(function() {\n\n\t$(\".loading\").hide();\n\t\tthis.bind('image', function(e) {\n\t});\n\n});\n &lt;\/script&gt;\n\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>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 &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/www.antpace.com\/blog\/easy-image-carousel\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Easy image carousel&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":3153,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[],"class_list":["post-236","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-web-development"],"_links":{"self":[{"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/posts\/236","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/comments?post=236"}],"version-history":[{"count":1,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/posts\/236\/revisions"}],"predecessor-version":[{"id":3154,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/posts\/236\/revisions\/3154"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/media\/3153"}],"wp:attachment":[{"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/media?parent=236"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/categories?post=236"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/tags?post=236"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}