Responsive Post & Product Scrolling Carousel

Learn how to create a scrolling post carousel for your WordPress site. SEO friendly and it can handle custom post types (any post type), including WooCommerce products. We’ll use PHP to create the HTML, an off-the shelf JavaScript library to do the scrolling stuff for us (which will work nicely on mobile devices) and we’ll tie it together with a bit of frontend JavaScript and CSS.

To keep things simple, we’ll create a shortcode. Shortcodes are a bit old-fashioned these days (we should be creating blocks now), but it means we can concentrate on the PHP and integrating the Glide.js library.

Glide.js logo
JavaScript carousel library by Jędrzej Chałubek

We’re Going to Make This 👇

The Glide.js library does all the hard work for us. We just need to create an unordered list in HTML, reference the JavaScript and CSS files, then pass our options to Glide.js.

tipThis is responsive. Resize your browser down to a mobile device size and see how the number of visible slides changes for a better-fit.

Getting Started – The Plan

The main PHP code breaks down into two functions:

  1. Enqueue the assets (JavaScript and CSS files) for Glide.js and our own bits-and-bobs.
  2. Process the shortcode and return a HTML string based on our options.

Because we might want multiple carousels on any page, we need each carousel to be able to have its own options… Post Type, the number of slides to show, etc. So we’ll be using custom data attributes to pass the options to the browser in <div> tags wrapped around each Glide.js carousel.

Before starting, make sure your site is using a custom child theme, because we want to keep our functions.php, and our theme directory, clean.

Let’s Write Some Code

We’re going to add a new PHP file to the custom child theme’s main folder, and we’re going to create a directory in there to hold Glide.js and our CSS/JS files.. In your custom child theme’s main folder, create a new file called wpt-carousel.php and paste the following into it – it’s just some scaffolding code to help us get things started:

<?php

/**
 * WP Tutorials Carousel (WPTC)
 */

// Block direct access.
if (!defined('WPINC')) {
	exit('Do NOT access this file directly.');
}

/**
 * Register CSS and scripts for Glide.js and our own visuals. You can safely
 * call this function multiple times - it will only register the assets
 * the first time it's called.
 */
function wptc_enqueue_frontend_assets_if_not_already_enqueued() {
	global $wpt_carousel_have_assets_been_queued;

	if (is_null($wpt_carousel_have_assets_been_queued)) {
		// Code will go in here...
		// ...

		$wpt_carousel_have_assets_been_queued = true;
	}
}

/**
 * The function that does all the actual work of returning a HTML string to
 * define the carousel.
 */
function do_shortcode_wpt_carousel($atts = null) {
	// Code will go in here...
	// ...

	return '<p>Hello World</p>';
}
add_shortcode('wpt_carousel', 'do_shortcode_wpt_carousel');

Now open your custom child theme’s functions.php file and add the following snippet to it:

/**
 * WP Tutorials Carousel (WPTC)
 */
require_once 'wpt-carousel.php';

Save those two files and then create a piece of content (a page or a post) so you can test the new shortcode. Add a shortcode block, save the content and you should see the words “Hello World” rendered in the output.

That’s our scaffolding in-place, so now we can look at sorting out Glide.js and our assets.

Directory Structure

In your custom child theme’s folder, create a subfolder called wpt-carousel.

You’ll need to grab the source code for Glide.js (choose “.zip” if you’re unsure which format to use), extract it and go to the “dist” (distribution) folder. The documentation says we only really need to use the “core” CSS file and the minified JavaScript file, so copy “glide.core.min.css” and “glide.min.js” into the “wpt-carousel” folder.

Now create empty text files called “default-slide.php”, “wptc-frontend.css” and “wptc-frontend.js“.

Carousel folder structure
FileWhat it Does
default-slide.phpTemplate PHP/HTML file for rendering each of the slides. You can create different templates for different carousels, like a product slider or one for “latest news”.
glide.core.min.cssThe core stylesheet for the Glide.js elements.
glide.min.jsThe JavaScript that makes Glide.js work.
wptc-frontend.cssOur frontend styles.
wptc-frontend.jsOur JavaScript code to connect our HTML to Glide.js. It doesn’t do much, but it’s the link that makes it work in the browser.
Files in our WordPress post carousel

This is the core of our shortcode. Completely replace the contents of wpt-carousel.php with the following – it’s chunky, but show no fear and dive in:

<?php

/**
 * WP Tutorials Carousel (WPTC)
 *
 * A scrollable post (any post type) responsive carousel with good SEO.
 *
 * https://wp-tutorials.tech/add-functionality/responsive-post-product-scrolling-carousel/
 *
 */

// Block direct access.
if (!defined('WPINC')) {
	exit('Do NOT access this file directly.');
}

const WPTC_GLIDEJS_VERSION = '3.4.1';
const WPTC_DEFAULT_TEMPLATE_NAME = 'default-slide';

/**
 * Register the shortcode.
 */
add_shortcode('wpt_carousel', 'do_shortcode_wpt_carousel');

/**
 * Register CSS and scripts for glide.js and our own visuals. You can safely
 * call this function multiple times - it will only register the assets
 * the first time it's called.
 */
function wptc_enqueue_frontend_assets_if_not_already_enqueued() {
	global $wpt_carousel_have_assets_been_queued;

	if (is_null($wpt_carousel_have_assets_been_queued)) {
		$base_url = get_stylesheet_directory_uri() . '/' . pathinfo(__FILE__, PATHINFO_FILENAME) . '/';
		$version = wp_get_theme()->get('Version');

		// The 'true' at the end lets glidejs be included in the footer.
		wp_enqueue_script('glide', $base_url . 'glide.min.js', array(), WPTC_GLIDEJS_VERSION, true);
		wp_enqueue_script('wptc-frontend', $base_url . 'wptc-frontend.js', array('jquery', 'glide'), $version, true);

		wp_enqueue_style('glide-core', $base_url . 'glide.core.min.css', array(), WPTC_GLIDEJS_VERSION);
		wp_enqueue_style('wptc-frontend', $base_url . 'wptc-frontend.css', array('glide-core'), $version);

		$wpt_carousel_have_assets_been_queued = true;
	}
}

/**
 * The function that does all the actual work of returning a HTML string to
 * drive the carousel.
 */
function do_shortcode_wpt_carousel($atts = null) {
	$html = '';

	if (is_admin()) {
		// Don't do anything.
	} elseif (wp_doing_ajax()) {
		// Don't do anything.
	} else {

		// Check what options have been passed to the shortcode.
		$args = shortcode_atts(
			array(
				'post_type' => 'post',
				'count' => 10,
				'perview' => 3,
				'type' => 'carousel',
				'controls' => 'off',
				'bullets' => 'off',
				'autoplay' => 3000,
				'hoverpause' => 'on',
				'template' => WPTC_DEFAULT_TEMPLATE_NAME,
			),
			$atts
		);

		// Sanity check the shortcode parameters.
		$base_dir_name = trailingslashit(dirname(__FILE__));
		$template_file_name = 'wpt-carousel/' . $args['template'] . '.php';

		$classes = array('wptc-container', 'wptc-' . $args['post_type']);
		$classes = apply_filters('wpt_carousel_classes', $classes);

		if (($per_view = intval($args['perview'])) <= 0) {
			$per_view = 3;
		}

		if (($autoplay = intval($args['autoplay'])) < 0) {
			$autoplay = false;
		}

		// All options are here: https://glidejs.com/docs/options/
		$glide_options = array(
			'type' => $args['type'],
			'perView' => $per_view,
			'autoplay' => $autoplay,
			'hoverpause' => filter_var($args['hoverpause'], FILTER_VALIDATE_BOOLEAN),
			'breakpoints' => array(
				'576' => array(
					'perView' => 1,
				),
				'768' => array(
					'perView' => 2,
				),
			),
		);

		wptc_enqueue_frontend_assets_if_not_already_enqueued();

		$html .= sprintf(
			'<div class="%s" data-glide-options="%s" style="opacity: 0.0;">',
			implode(' ', $classes),
			esc_attr(json_encode($glide_options))
		);

		// Create our WP_Query object so we can run a WordPress loop.
		$query_args = array(
			'post_type' => sanitize_title($args['post_type']),
			'posts_per_page' => intval($args['count']),
			'post_status' => 'publish',
		);
		$query = new WP_Query($query_args);

		if ($query->have_posts()) {
			$html .= '<div class="glide">';

			// Start the Glide track = which contains the slides.
			$html .= '<div class="glide__track" data-glide-el="track">';
			$html .= '<ul class="glide__slides">';
			while ($query->have_posts()) {
				$query->the_post();

				$html .= '<li class="glide__slide">';
				$html .= '<div class="wptc-item">';

				if (!is_readable($base_dir_name . $template_file_name)) {
					$html .= sprintf(
						'<span class="error">Template File Error<br />%s</span>',
						$template_file_name
					);
				} else {
					ob_start();
					include $template_file_name;
					$html .= ob_get_clean();
				}

				$html .= '</div>'; // .wptc-item
				$html .= '</li>'; // .glide__slide
			}
			$html .= '</ul>'; // .glide__slides
			$html .= '</div>'; // .glide__track

			// See the Glide.js documentation for how to create controls and
			// bullets. It's pretty easy.
			if (filter_var($args['controls'], FILTER_VALIDATE_BOOLEAN)) {
				// Render the controls HTML in here...
			}

			if (filter_var($args['bullets'], FILTER_VALIDATE_BOOLEAN)) {
				// Render the bullets HTML in here...
			}

			$html .= '</div>'; // .glide

			// It's very important to call wp_reset_query() after running a
			// WordPress loop.
			wp_reset_query();
		}

		$html .= '</div>'; // .wptc-container
	}

	return $html;
}

Although it’s a big lump of code, there’s really not much to it… The function wptc_enqueue_frontend_assets_if_not_already_enqueued() does exactly what it says, and should be easy enough to read.

tipDon’t be shy about using long names for your functions and variables. If it makes the code easy to read and reduces ambiguity, it’s a good thing.

The Core Logic

The main function for the carousel is do_shortcode_wpt_carousel() and it breaks down like this:

  • If we’re in the admin area or running an AJAX query (i.e. if we’re not rendering frontend HTML) then don’t do anything.
  • Enqueue the stylesheets and JavaScript.
  • Parse the shortcode options, which come through in the $atts array.
    • Use shortcode_atts() to provide sensible default values for unspecified parameters.
  • Create an array called $glide_options, which we pass through to the frontend in a custom data attribute.
    • Our JavaScript code will turn this into an object that we pass directly into the Glide() constructor.
  • Open our containing <div> tag and set the data-glide-options custom data attribute with our $glide_options array, serialised as a JSON string.
    • Taking a PHP array, turning it into a JSON string and then escaping the string into an attribute is a common way of getting PHP data into JavaScript.
    • Inline version: data-my-thingy="<?php echo esc_attr(json_encode($my_php_array_data)); ?>"
  • Create $query_args which we’ll pass into a WP_Query object to drive our WordPress loop.
    • There are lots of options you can put in here, and it’s the heart of querying WordPress content.
  • If the WP_Query object has any posts, then enter the loop now and render an unordered list, where each list items is a carousel slide.
    • We include a template file to render the actual slides. If $template_file_name can’t be read, then add an error message to the HTML to make the problem easy to fix.
  • importantAfter the loop, call wp_reset_query() so that WordPress knows we’ve finished doing stuff with the global $post object.

The Slide/Item Template

Because we’re including a PHP file for the individual slides, you’ll need a PHP file for that, too. Open wpt-carousel/default-slide.php and paste the following into it:

<?php

// Block direct access.
if (!defined('WPINC')) {
	exit('Do NOT access this file directly.');
}

?><div class="thumbnail">
	<?php the_post_thumbnail('medium');?>
	<h2 class="post-title"><?php the_title();?></h2>
</div>
<div class="post-excerpt"><?php
the_excerpt();

printf(
	'<a href="%s" title="%s" class="button"><span class="screen-reader-text">%s</span>%s</a>',
	esc_url(get_the_permalink()),
	esc_attr(get_the_title()),
	esc_attr(get_the_title()),
	esc_html__('Read More', 'wp-tutorials')
);
?></div>

tipYou can do loads of customisation and SEO optimisation in here. Using link text like “Read More” and “Click Here” is considered bad practise, because website crawlers can’t derive any context for the target URL. Here, we’re setting the title property of our <a> element to give the link specific context. We’re also adding a <span> with a screen-reader hint. You could also use an aria-label and do all sorts of screen-reader accessibility stuff.

The CSS is pretty straightforward and you can adjust it however you want. Paste this into wpt-carousel/wptc-frontend.css.

/**
 * WP Tutorials Carousel - Frontend
 */

/** Required to make the carousel work. */

.wptc-container {
    padding:  1em;
    transition: 0.3s;
}

.wptc-container .glide__slides {
    margin-left:  0;
}

.wptc-container .glide__slide {
    display:  block;
    height:  inherit;
}

.wptc-container li {
    margin-left:  0;
}

.wptc-item {
    display:  flex;
    flex-direction: column;
    align-items: stretch;
    border-radius:  1em;
    overflow:  hidden;
    justify-content: stretch;
    height:  100%;
    border:  2px solid #888;
    background-color:  white;
}

.wptc-item .error {
    padding:  1em;
    font-weight: bold;
    color:  red;
}

/** Change these to adjust the styles for your slides. */

.wptc-item .thumbnail {
    display:  block;
    flex:  1;
    position:  relative;
}

.wptc-item .post-title {
    position:  absolute;
    left:  0;
    bottom:  0;
    width:  100%;
    background-color:  rgba( 255, 255, 255, 0.80 );
    margin:  0;
    padding:  1.0rem;
    font-size:  18pt;
}

.wptc-item .thumbnail .wp-post-image {
    width:  100%;
    height:  100%;
    height:  17em;
    object-fit: cover;
}

.wptc-item .post-excerpt {
    background-image:  linear-gradient( to bottom right, transparent, #eee);
    padding:  1.05rem;
    line-height: 1.25em;
    flex:  1;
    display:  flex;
    flex-direction: column;
    justify-content: space-between;
    align-items: flex-start;
}

.wptc-item .post-excerpt .button {
    display:  inline-block;
}

tipNotice how we set the transition of “.wptc-container” to 0.3s – this is so that we can load the carousel container(s) with zero opacity (totally transparent) and then fade them in by just setting the opacity to 1.0.

Now we’ve got the HTML rendered in the browser, and the assets are enqueued too, we need a bit of custom JavaScript to glue it all together. The first thing we should really do is check that our JavaScript file has been loaded by the browser.

  1. Reload your test content page/post. There should be a blank space where the carousel is, because it is still transparent.
  2. View the page source.
  3. Search for “wptc-frontend-js” in a <script> element. If the script is being loaded correctly, you should see something like this in the page source:
<script
   src='https://example.org/wp-content/themes/mytheme/wpt-carousel/wptc-frontend.js?ver=1.0.1'
   id='wptc-frontend-js'
>
</script>

When you’ve confirmed this is in the page source, close the page source window/tab so you’ve got your main WP content in front of you again, and open your browser’s DevTools – usually with the [F12] key. The JavaScript console lets you error messages that might pop-up. We can also send ourselves useful diagnostic messages in the JavaScript console.

Edit the wpt-carousel/wptc-frontend.js file in your custom child theme and paste the following into it:

/**
 * WP Tutorials Carousel - Frontend
 * 
 * https://wp-tutorials.tech/add-functionality/responsive-post-product-scrolling-carousel/
 */
(function($) {
    'use strict';

    $(window).on('load', function() {
        console.log('WP Tutorials Carousel : load');

        $('.wptc-container').each(function(index, el) {
            var options = $(this).data( 'glide-options');
            // console.log( options );
            new Glide( $(this).find( '.glide')[0], options ).mount();
        });

        $('.wptc-container').css( 'opacity', '1.0' );
    });
})(jQuery);

This simple script works like this this:

  • When the (browser) window raises the “load” event…
    • Send the string “WP Tutorials Carousel : load” to the JavaScript console so we know our script is running.
    • For each DOM element that has the “wptc-container” CSS class…
      • Extract the data-glide-options custom data attribute.
      • If you want to see the contents of this object, uncomment the line that calls console.log( options )
      • Find the first child DOM element that has the “glide” CSS class and use it to create a new Glide() object.
    • For each DOM element that has the “wptc-container” CSS class, set the opacity to 1.0 (100%).
      • Because we set a transition of 0.3s in the “wptc-container” CSS class definition, the carousel container will take a third of a second to fade in.

Test It

That should be it! In your browser, do a force reload (bypass the cache) to make sure the browser loads the latest version of all your assets – usually [Ctrl][Shift][R], but it depends on your browser.

Check it out on mobile devices – phones and tablets. Make sure this doesn’t work only on your laptop… but that it works well for everybody.

Troubleshooting

If something’s not working, the first thing to check is the JavaScript console. This will show if there was a problem loading JS/CSS assets. It will also show if there were any errors calling the Glide.js library.

Use the WordPress plugin Query Monitor to help you pick up on any PHP errors. You can uninstall it when you’ve finished with it.

If things are sort-of working, but the page elements are being rendered strangely, check your HTML is valid – you might be missing a closing element or a closing quote for an attribute. the standard W3C Validator will help you with this. There’s no excuse for having errors in your website’s HTML and just hoping the browser will clean up your mess.

Going Further

This code should be easy to extend in several ways.

  • Pass the template attribute into the shortcode and experiment with different item templates.
  • If you’re running a WooCommerce shop, set the post type to “product” and add a new template file. Make your WooCommerce shop look different to everybody else’s.
  • Look through the Glide.js options and add things like navigation buttons and bullets to the carousel. I’ve put some placeholders into wpt-carousel.php to get you started.
  • Move it out of your theme and into a plugin, so you can use it on your other projects really easily.

Have you found some cool uses for this code in your websites? Let us know in the comments below 😎 👇 👇 👇

Leave a Comment

Your email address will not be published. Required fields are marked *