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.

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

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.

We’re Going to Make This 👇

  • Custom WordPress admin menu items tutorial

    WordPress Admin Menu Items with Custom URLs

    Add custom menu items to the main WordPress admin menu, with whatever URLs you want. Super-handy for linking to external support URLs and custom reports.

    WordPress Admin Menu Items with Custom URLsRead More
  • WordPress glossary terms tutorial

    Create a Glossary with Rich HTML Tooltips

    In this tutorial we’re going to create a glossary for a WordPress site, with rich HTML tooltips controlled by Popper JS, We’ll do this by creating a custom plugin, step-by-step.

    Create a Glossary with Rich HTML TooltipsRead More
  • Random post button tutorial for WordPress

    Button to show a Random Post

    Add a button anywhere in your WordPress site that lets users jump to a random post. Works with any post type and with page caching plugins.

    Button to show a Random PostRead More
  • EAN13 Product id tutorial for WooCommerce

    Add GTIN to WooCommerce Product Schema

    Fix the “No global identifier provided (e.g. GTIN)” warning from Google Search Console by adjusting the WooCommerce product schema. Works with GTIN, EAN and MPN.

    Add GTIN to WooCommerce Product SchemaRead More
  • Product Attribute Icons Tutorial

    Product Attribute Icons for WooCommerce

    Create a strip of product attribute icons and HTML snippets for the WooCommerce single product page. It’s a straightforward copy-and-paste tutorial that’s fully customisable with snippet files and filters, so you can use the same code in multiple projects.

    Product Attribute Icons for WooCommerceRead More
  • WordPress popup emailer tutorial

    Popup Email Form Without a Plugin

    Create a popup email contact form on your WordPress site without installing a plugin. We’ll include a GDPR/privacy consent checkbox, and we’ll rinse messages through Spam Shield to block junk messages.

    Popup Email Form Without a PluginRead More
  • WordPress Swiper Gallery Tutorial

    Multiple WordPress Gallery Effects with Swiper

    This tutorial extends the standard WordPress Gallery Block by using Swiper and GLightbox. You can apply different transition effects to your WordPress galleries by adding CSS Classes to the gallery blocks.

    Multiple WordPress Gallery Effects with SwiperRead More
  • Floating button tutorial

    Floating Button Tutorial for WordPress

    Add an always-on-top floating button to your WordPress site that displays a fully customisable popup panel when clicked. The whole thing will be mobile-first responsive.

    Floating Button Tutorial for WordPressRead More
  • Product variation custom fields tutorial

    Custom WooCommerce Product Variation Fields

    This tutorials covers how to add custom fields to your product variations in WooCommerce, sanitise the inputs and display the fields in your customers’ orders. We’ll do this with a few snippets of code, without installing another plugin. It’s easy to extend, and you don’t need to be a developer to follow it. I promise.

    Custom WooCommerce Product Variation FieldsRead More
  • Before and After Image Tutorial

    Before and After Image Slider

    Learn how to use the Cocoen JavaScript module to create before-and-after image sliders in WordPress. We’ll use the built-in WordPress Gallery Block to make it easy to layout the images.

    Before and After Image SliderRead More

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.

infoThis 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)
 *
 * https://wp-tutorials.tech/add-functionality/responsive-post-product-scrolling-carousel/
 */

// Block direct access.
defined('WPINC') || die();


/**
 * 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 dirname(__FILE__) . '/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.

WP Tutorials carousel shortcode
Post carousel shortcode

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“.

WP Tutorials Carousel directory structure
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.
defined('WPINC') || die();

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.

infoDon’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.
defined('WPINC') || die();

?><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>

infoYou 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;
}

infoNotice 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 😎 👇 👇 👇

Like This Tutorial?

loading

Let us know

Leave a Comment

Your email address will not be published.