How to Easily Create a Responsive Slider in WordPress

Do you want to create a responsive slider for your WordPress website?

Sliders are commonly used on the front page of websites to showcase your most important products, services, testimonials or other content in an interactive way.

In this article, we will show you how to add a responsive slider to your WordPress website.

How to easily create a responsive slider in WordPress

Why Add a Responsive WordPress Slider to your Website?

A slider or slideshow is a great way to highlight your most popular WooCommerce products, latest YouTube videos, customer reviews, and more.

Yelp reviews slider

Some sliders autoplay, so they move between slides automatically. This allows you to display lots of information in a small space, which leaves plenty of room to show other content.

For this reason, many websites use sliders on their most important pages, such as a landing page, home page, or sales pages.

Even if a slider is set to autoplay, visitors can typically move between slides manually by clicking on navigation buttons. In this way, a slider encourages visitors to interact with your site, which makes it more engaging.

You can see in the GIF below how dynamic a slider can make a page.

An example of a WordPress slider, created using SeedProd

With that said, let’s see how you can easily create a responsive slider in WordPress. Simply use the quick links below to jump straight to the method you want to use.

Method 1. Create a Responsive WordPress Slider Using a Plugin (Quick and Easy)

If you want to create a simple slider that looks good on desktop and mobile, then we recommend using the Soliloquy plugin.

This WordPress slider plugin lets you create slides using images from your media library and can also show videos hosted on third-party platforms like YouTube.

An example of a responsive slider, created using Soliloquy

You can also add alt text and captions to each slide, which is great for WordPress SEO.

First, you’ll need to sign up for a Soliloquy account. After that, you can install and activate the Soliloquy plugin on your website. For more details, you can follow our guide on how to install a WordPress plugin.

Upon activation, go to Soliloquy » Settings in the WordPress dashboard and enter your license key.

Activating the Soliloquy WordPress plugin

You can find the key by logging into your account on the Soliloquy website. After entering this information, click on the ‘Verify Key’ button.

With that done, you can add a new slider to your WordPress website by going to Soliloquy » Add New.

Creating a new responsive slider using a WordPress plugin

You can start by typing in a title. This is just for your reference so you can use anything that helps you identify the slider in your WordPress dashboard.

Next, it’s time to add images and videos to the slider. You can either drag and drop files onto the editor, or click ‘Select Files From Other Sources.’

Adding files to a responsive WordPress slider

If you want to use images, then you can add those files in exactly the same way you add images in the WordPress block editor.

If you want to include videos, then we recommend uploading them to a video hosting site like YouTube or Vimeo. Uploading videos directly to your website is not a good idea, as it can slow down your website and take up a lot of storage.

Meanwhile, platforms like YouTube are optimized for video, so you can show high-resolution videos in your sliders without negatively affecting your website.

After choosing a video hosting platform, you’ll need to upload all the videos you want to include in the WordPress slider. If you’re not sure how to upload videos, then we recommend reading the official documentation or user manual for your chosen video platform.

With that done, simply click on the ‘Select Files from Other Sources’ button and then select ‘Insert Video Slide.’

Adding videos to a responsive WordPress slider

You can now go ahead and paste the video’s URL into the ‘Video URL’ field.

Soliloquy shows all the supported link formats along the right-hand side, so make sure your link uses one of these formats.

Adding YouTube videos to a mobile-friendly slider

With that done, you can go ahead and type a title into the ‘Title’ field. This is just for your reference so you can use anything you want. You can also add alt text, which will help the search engines understand what this slide is all about.

After that, you can type an optional caption into the ‘Caption’ field. This will be shown in a grey bar along the bottom of the video before the visitor presses ‘Play.’

Adding videos to a responsive slider

If you don’t want to show this bar, then simply leave the ‘Caption’ field empty.

When you’re happy with the information you’ve entered, you can create more slides by clicking on ‘Add Another Video Slide.’

To configure this new slide, simply follow the same process described above.

Adding videos to a WordPress slider

Once you’ve created all your slides, go ahead and click on ‘Insert into Slider.’

You’ll now see all your slides in the ‘Currently in Your Slider’ section.

Creating a responsive slider in WordPress using Soliloquy

Next, you can click on the ‘Configuration’ tab and change the slider display settings. For example, there are options to choose a new slider theme, change the image size, and change the position of the slider and captions.

You can also customize the slider arrows and the pause/play button, adjust the transition speed, change the delay time, and more.

Configuring a WordPress image and video slider

Next, you’ll want to make sure your slider looks just as good on mobile devices, as it does on computers and laptops.

That said, click on the ‘Mobile’ tab and check the box next to ‘Create Mobile Slider Images.’

Add mobile dimensions to a responsive WordPress slider

You can then type in the sizes you want to use for the images and videos on mobile devices. When making these decisions, it may help to view the mobile version of your WordPress site on desktop.

By default, Soliloquy will hide captions for mobile users. Often, this helps the slides fit more comfortably on the smaller screens typically used by smartphones and tablets.

For that reason, we recommend leaving this setting disabled, but you can check the box next to ‘Show Captions on Mobile’ if you prefer.

With that done, click on the ‘Misc’ tab. Here, you can add custom CSS to your slider, edit the slider title and slug, and export the slider.

Adding custom CSS to a mobile-friendly slider

When you’re happy with how the slider is set up, go ahead and click on the ‘Publish’ button.

Soliloquy will automatically create a shortcode that you can add to any page, post, or widget-ready area.

Adding a shortcode to your WordPress blog or website

For more information on how to place the shortcode, please see our guide on how to add a shortcode in WordPress.

You’ll also notice some code snippets that you can add to your WordPress theme.

Adding custom code to a WordPress theme

For step-by-step instructions, see our guide on how to easily add custom code in WordPress.

The easiest way to add the slider to your site is by using the Soliloquy block. Simply go to the page or post where you want to show the slider and then click on the ‘+’ button.

In the popup that appears, type in ‘Soliloquy.’

Adding a slider to a WordPress website using a block

When the right block appears, click to add it to your WordPress blog or website.

After that, open the Soliloquy block and select the slider you just created.

Adding a Soliloquy block to your WordPress website

You can now click on the ‘Update’ or ‘Publish’ button to make the slider live.

Method 2. Create a Responsive Slider Using a Page Builder (More Customizable)

If you want to create a simple slider, then a plugin such as Soliloquy is a good choice. However, if you want to add an advanced slider to landing pages, custom home pages, or even your WordPress theme, then we recommend using a page builder plugin instead.

SeedProd is the best drag-and-drop WordPress page builder. It allows you to design custom landing pages and even create a custom WordPress theme without having to write a single line of code.

It also has an Image Carousel block that you can use to create stunning sliders.

An example of a slider, created using SeedProd

You can simply drop this ready-made block anywhere on a page and then customize it using the powerful drag-and-drop editor.

First, you need to install and activate the SeedProd plugin. For more details, see our step-by-step guide on how to install a WordPress plugin.

Note: There is a free version of SeedProd that lets you create professional designs no matter what your budget. However, we’ll be using the premium SeedProd plugin since it comes with the Image Carousel block. If you’re creating a slider to promote your products or services, then the premium plugin also integrates with WooCommerce and all of the best email marketing services.

After activating the plugin, SeedProd will ask for your license key.

Adding the SeedProd license key to your WordPress website

You can find this license key under your account on the SeedProd website. After entering the information, click on the ‘Verify Key’ button.

With that done, go to SeedProd » Landing Pages in your WordPress dashboard.

Choosing a page template for a landing page

SeedProd comes with over 180 professionally designed templates that are grouped into categories. Along the top you’ll see categories that let you create beautiful coming soon pages, activate maintenance mode, create a custom login page for WordPress, and more.

All of SeedProd’s templates are easy to customize, so you can use any design you want. When you find a template you like, simply hover your mouse over it and click on the checkmark icon.

Choosing a template in SeedProd

You can now type a name for your landing page into the ‘Page Name’ field. SeedProd will automatically create a ‘Page URL’ using the page name.

It’s smart to include relevant keywords in this URL wherever possible, as this will help search engines understand what the page is about. This can often improve your WordPress SEO.

To change the page’s automatically-generated URL, simply type into the ‘Page URL’ field.

Naming your landing page or custom home page

When you’re happy with the information you’ve entered, click on ‘Save and Start Editing the Page.’ This will load the SeedProd page builder interface.

This simple drag-and-drop builder shows a live preview of your page design to the right. On the left is a menu with all the different blocks and sections you can add to the page.

The SeedProd drag-and-drop page builder

When you find a block you want to add, simply drag and drop it onto your template.

To customize a block, just click to select that block in the SeedProd editor. The left-hand menu will now update to show all the settings you can use to customize it.

Customizing SeedProd blocks

As you’re building the page, you can move blocks around your layout by dragging and dropping them. For more detailed instructions, please see our guide on how to create a landing page with WordPress.

To create a responsive slider using SeedProd, simply find the Image Carousel block in the left-hand menu and then drag it onto your layout.

Adding the Image Carousel block to a landing page design

Next, click to select the Image Carousel block in the page editor.

You can now go ahead and add all the images you want to show in the slider. To start, click on the ‘Image 1’ item that SeedProd creates by default.

Adding images to a slider using SeedProd

You can now either use a photo from SeedProd’s built-in library, or click on ‘Use Your Own Image’ and choose a file from the WordPress media library.

After choosing an image, you can add a caption that will appear below that image. SeedProd doesn’t display captions by default, but we’ll show you how to enable them later in this post.

Adding captions to an image slider in WordPress

When you’re happy with how the slide is configured, click on the ‘Add Images’ button to create another slide.

To add more slides, simply follow the same process described above.

Creating an image carousel slider using SeedProd

After adding all the images to your slider, it’s time to customize how that slider looks and acts.

In the left-hand menu, click to expand the ‘Carousel Settings’ section.

Customizing the carousel settings for your WordPress slider

To start, you can switch between light and dark modes for the slider’s navigation buttons.

To try these different modes, click on the buttons next to ‘Navigation Color Mode.’ The live preview will update automatically so you can see which mode you like the best.

Customizing the slider navigation settings

By default, the carousel shows a single slide, and visitors will need to use the navigation controls to see more content.

However, you may want to show multiple slides at once by opening the ‘Slide to Show’ dropdown and choosing a number from the list. This can be useful if the slider has lots of content and you’re worried visitors might not click through all the slides.

Showing multiple files in an image carousel

By default, visitors will need to click to see the next slide. With that in mind, you may want to enable autoplay, as this often increases how many slides a visitor sees.

To do this, click to enable the ‘Autoplay’ switch and then specify how long each slide should stay onscreen by typing a number into ‘Autoplay Speed.’

Adding an autoplaying slider to a WordPress website

If you added captions to your images, then make sure you click on the ‘Show Caption’ slider to turn it from ‘No’ to ‘Yes.’

You can also change the caption alignment.

Adding captions to a WordPress slider

Depending on the background color, you may need to change the caption color before they’re visible to visitors.

To do this, click on the ‘Advanced’ tab and then use the ‘Caption Color’ settings.

Changing the color of the image carousel captions

You can further style the text by clicking on the ‘Edit’ button next to ‘Caption Typography.’

This adds some settings where you can change the font size, line height, spacing, and more.

SeedProd's typography settings

When you’re happy with how the captions look, you can add different box shadows to the slides using the ‘Slider Image Styles’ dropdown.

These shadows can really make the slides stand out, so it’s worth trying different shadows to see whether they fit your page design.

Adding shadow styles to images using SeedProd

In the ‘Advanced’ tab you’ll also find settings that let you add a border, change the spacing, and even create an entrance animation.

Most of these settings are self-explanatory so it’s worth looking through them to see what different kind of effects you can create.

SeedProd's advanced settings

When you’re happy with how the slider looks, you can continue working on the rest of the page. Simply drag blocks onto your design and then customize them using the settings in the left-hand menu.

If you want to delete a block, then simply hover over it and then click on the trash can icon when it appears.

Deleting blocks from a SeedProd page template

When asked, go ahead and click on ‘Yes, delete it.’

When you’re happy with how the page looks, it’s time to make it live by clicking on the ‘Save’ button. Finally, select ‘Publish’ to make your slider live.

Publishing a custom page design using the SeedProd page builder

Method 3. Create a Slider Using Smash Balloon (Best For Social Posts and Reviews)

We’re more likely to try things that we see other people buying, using, or recommending. With that being said, it’s a good idea to show social proof on your website including customer reviews, testimonials, or even positive comments you’ve got on social media.

The easiest way to show social proof on your site is by using Smash Balloon. Smash Balloon has plugins that allow you to embed content from Instagram, Facebook, Twitter, and YouTube.

It also has a Reviews Feed plugin that you can use to display reviews from Facebook, Tripadvisor, Yelp, and Google.

An example of Google reviews in a slider

However, showing lots of positive comments and reviews on your online store or website can take up a lot of space.

That’s why Smash Balloon comes with built-in carousel layouts and templates that you can use to create responsive sliders. This allows you to create social media and review slideshows with just a few clicks.

Smash Balloon's responsive slider templates

Even better, Smash Balloon will fetch new content automatically so the slider will always show the latest posts.

Another option is showing the content from your own social media accounts.

A Twitter slider, created using Smash Balloon

If visitors like what they see, they might decide to follow you on social media.

To help you get more followers, Smash Balloon comes with ready-made call to action buttons that you can add to your sliders.

An example of an Instagram slider

For detailed step-by-step instructions, please see our guides on how to add social media feeds to WordPress, and how to show Google, Facebook, and Yelp reviews in WordPress.

We hope that this article helped you learn how to easily create a responsive WordPress slider. You may also want to check out our guide on how to choose the best design software, or see our expert pick of the best live chat software for small businesses.

If you liked this article, then please subscribe to our YouTube Channel for WordPress video tutorials. You can also find us on Twitter and Facebook.

The post How to Easily Create a Responsive Slider in WordPress first appeared on WPBeginner.

CSS Infinite 3D Sliders

In this series, we’ve been making image sliders with nothing but HTML and CSS. The idea is that we can use the same markup but different CSS to get wildly different results, no matter how many images we toss in. We started with a circular slider that rotates infinitely, sort of like a fidget spinner that holds images. Then we made one that flips through a stack of photos.

This time around, we’re diving into the third dimension. It’s going to look tough at first, but lots of the code we’re looking at is exactly what we used in the first two articles in this series, with some modifications. So, if you’re just now getting into the series, I’d suggest checking out the others for context on the concepts we’re using here.

CSS Sliders series

This is what we’re aiming for:

At first glance, it looks like we have a rotating cube with four images. But in reality, we’re dealing with six images in total. Here is the slider from a different angle:

Now that we have a good visual for how the images are arranged, let’s dissect the code to see how we get there.

The basic setup

Same HTML as the rest of the sliders we’ve used for the other sliders:

<div class="gallery">
  <img src="" alt="">
  <img src="" alt="">
  <img src="" alt="">
  <img src="" alt="">
  <img src="" alt="">
</div>

And once again, we’re using CSS Grid to place the images in a stack, one on top of another:

.gallery {
  display: grid;
}
.gallery > img {
  grid-area: 1 / 1;
  width: 160px;
  aspect-ratio: 1;
  object-fit: cover;
}

The animation

The logic for this slider is very similar to the circular slider from the first article. In fact, if you check the video above again, you can see that the images are placed in a way that creates a polygon. After a full rotation, it returns to the first image.

We relied on the CSS transform-origin and animation-delay properties for that first slider. The same animation is applied to all of the image elements, which rotate around the same point. Then, by using different delays, we correctly place all the images around a big circle.

The implementation will be a bit different for our 3D slider. Using transform-origin won’t work here because we’re working in 3D, so we will use transform instead to correctly place all the images, then rotate the container.

We’re reaching for Sass again so we can loop through the number of images and apply our transforms:

@for $i from 1 to ($n + 1) {
  .gallery > img:nth-child(#{$i}) {
     transform: 
       rotate(#{360*($i - 1) / $n}deg) /* 1 */
       translateY(50% / math.tan(180deg / $n)) /* 2 */ 
       rotateX(90deg); /* 3 */
  }
}

You might be wondering why we’re jumping straight into Sass. We started with a fixed number of images using vanilla CSS in the other articles before generalizing the code with Sass to account for any number (N) of images. Well, I think you get the idea now and we can cut out all that discovery work to get to the real implementation.

The transform property is taking three values, which I’ve illustrated here:

Showing the three phases of the image slider layout.

We first rotate all the images above each other. The angle of rotation depends on the number of images. For N images, we have an increment equal to 360deg/N. Then we translate all of the images by the same amount in a way that makes their center points meet on the sides.

Showing the stack of images arranged flat in a circle with a red line running through the center point of the images.

There’s some boring geometry that helps explain how all this works, but the distance is equal to 50%/tan(180deg/N). We dealt with a similar equation when making the circular slider ( transform-origin: 50% 50%/sin(180deg/N) ).

Finally, we rotate the images around the x-axis by 90deg to get the arrangement we want. Here is a video that illustrates what the last rotation is doing:

Now all we have to do is to rotate the whole container to create our infinite slider.

.gallery {
  transform-style: preserve-3d;
  --_t: perspective(280px) rotateX(-90deg);
  animation: r 12s cubic-bezier(.5, -0.2, .5, 1.2) infinite;
}
@keyframes r {
  0%, 3% {transform: var(--_t) rotate(0deg); }
  @for $i from 1 to $n {
    #{($i/$n)*100 - 2}%, 
    #{($i/$n)*100 + 3}% {
      transform: var(--_t) rotate(#{($i / $n) * -360}deg);
    }  
  }
  98%, 100% { transform: var(--_t) rotate(-360deg); }
}

That code might be hard to understand, so let’s actually step back a moment and revisit the animation we made for the circular slider. This is what we wrote in that first article:

.gallery {
  animation: m 12s cubic-bezier(.5, -0.2, .5, 1.2) infinite;
}
@keyframes m {
  0%, 3% { transform: rotate(0); }
  @for $i from 1 to $n {
    #{($i / $n) * 100 - 2}%,
    #{($i / $n) * 100 + 3}% { 
      transform: rotate(#{($i / $n) * -360}deg);
    }  
  }
  98%, 100% { transform: rotate(-360deg); }
}

The keyframes are almost identical. We have the same percentage values, the same loop, and the same rotation.

Why are both the same? Because their logic is the same. In both cases, the images are arranged around a circular shape and we need to rotate the whole thing to show each image. That’s how I was able to copy the keyframes from the circular slider and use that same code for our 3D slider. The only difference is that we need to rotate the container by -90deg along the x-axis to see the images since we have already rotated them by 90deg on the same axis. Then we add a touch of perspective to get the 3D effect.

That’s it! Our slider is done. Here is the full demo again. All you have to do is to add as many images as you want and update one variable to get it going.

Vertical 3D slider

Since we are playing in the 3D space, why not make a vertical version of the previous slider? The last one rotates along the z-axis, but we can also move along the x-axis if we want.

If you compare the code for both versions of this slider, you might not immediately spot the difference because it’s only one character! I replaced rotate() with rotateX() inside the keyframes and the image transform. That’s it!

It should be noted that rotate() is equivalent to rotateZ(), so by changing the axis from Z to X we transform the slider from the horizontal version into the vertical one.

Cube slider

We cannot talk about 3D in CSS without talking about cubes. And yes, that means we are going to make another version of the slider.

The idea behind this version of the slider is to create an actual cube shape with the images and rotate the full thing in around the different axis. Since it’s a cube, we’re dealing with six faces. We’ll use six images, one for each face of the cube. So, no Sass but back to vanilla CSS.

That animation is a little overwhelming, right? Where do you even start?

We have six faces, so we need to perform at least six rotations so that each image gets a turn. Well, actually, we need five rotations — the last one brings us back to the first image face. If you go grab a Rubik’s Cube — or some other cube-shaped object like dice — and rotate it with your hand, you’ll have a good idea of what we’re doing.

.gallery {
  --s: 250px; /* the size */

  transform-style: preserve-3d;
  --_p: perspective(calc(2.5*var(--s)));
  animation: r 9s infinite cubic-bezier(.5, -0.5, .5, 1.5);
}

@keyframes r {
  0%, 3%   { transform: var(--_p); }
  14%, 19% { transform: var(--_p) rotateX(90deg); }
  31%, 36% { transform: var(--_p) rotateX(90deg) rotateZ(90deg); }
  47%, 52% { transform: var(--_p) rotateX(90deg) rotateZ(90deg) rotateY(-90deg); }
  64%, 69% { transform: var(--_p) rotateX(90deg) rotateZ(90deg) rotateY(-90deg) rotateX(90deg); }
  81%, 86% { transform: var(--_p) rotateX(90deg) rotateZ(90deg) rotateY(-90deg) rotateX(90deg) rotateZ(90deg); }
  97%, 100%{ transform: var(--_p) rotateX(90deg) rotateZ(90deg) rotateY(-90deg) rotateX(90deg) rotateZ(90deg) rotateY(-90deg); }
}

The transform property starts with zero rotations and, on each state, we append a new rotation on a specific axis until we reach six rotations. Then we are back to the first image.

Let’s not forget the placement of our images. Each one is applied to a face of the cube using transform:

.gallery img {
  grid-area: 1 / 1;
  width: var(--s);
  aspect-ratio: 1;
  object-fit: cover;
  transform: var(--_t,) translateZ(calc(var(--s) / 2));
}
.gallery img:nth-child(2) { --_t: rotateX(-90deg); }
.gallery img:nth-child(3) { --_t: rotateY( 90deg) rotate(-90deg); }
.gallery img:nth-child(4) { --_t: rotateX(180deg) rotate( 90deg); }
.gallery img:nth-child(5) { --_t: rotateX( 90deg) rotate( 90deg); }
.gallery img:nth-child(6) { --_t: rotateY(-90deg); }

You are probably thinking there is weird complex logic behind the values I’m using there, right? Well, no. All I did was open DevTools and play with different rotation values for each image until I got it right. It may sound stupid but, hey, it works — especially since we have a fixed number of images and we are not looking for something that supports N images.

In fact, forget the values I’m using and try to do the placement on your own as an exercise. Start with all the images stacked on top of each other, open the DevTools, and go! You will probably end up with different code and that’s totally fine. There can be different ways to position the images.

What’s the trick with the comma inside the var()? Is it a typo?

It’s not a typo so don’t remove it! If you do remove it, you will notice that it affects the placement of the first image. You can see that in my code I defined --_t for all the images except the first one because I only need a translation for it. That comma makes the variable fall back to a null value. Without the comma, we won’t have a fallback and the whole value will be invalid.

From the specification:

Note: That is, var(--a,) is a valid function, specifying that if the --a custom property is invalid or missing, the var()` should be replaced with nothing.

Random cube slider

A little bit of randomness can be a nice enhancement for this sort of animation. So, rather than rotate the cube in sequential order, we can roll the dice so to speak, and let the cube roll however it will.

Cool right? I don’t know about you, but I like this version better! It’s more interesting and the transitions are satisfying to watch. And guess what? You can play with the values to create your own random cube slider!

The logic is actual not random at all — it just appears that way. You define a transform on each keyframe that allows you to show one face and… well, that’s really it! You can pick any order you want.

@keyframes r {
  0%, 3%   { transform: var(--_p) rotate3d( 0, 0, 0,  0deg); }
  14%,19%  { transform: var(--_p) rotate3d(-1, 1, 0,180deg); }
  31%,36%  { transform: var(--_p) rotate3d( 0,-1, 0, 90deg); }
  47%,52%  { transform: var(--_p) rotate3d( 1, 0, 0, 90deg); }
  64%,69%  { transform: var(--_p) rotate3d( 1, 0, 0,-90deg); }
  81%,86%  { transform: var(--_p) rotate3d( 0, 1, 0, 90deg); }
  97%,100% { transform: var(--_p) rotate3d( 0, 0, 0,  0deg); }
}

I am using rotate3d() this time but am still relying on DevTools to find the values that feel “right” to me. Don’t try to find a relationship between the keyframes because there simply isn’t one. I’m defining separate transforms and then watching the “random” result. Make sure the first image is the first and last frames, respectively, and show a different image on each of the other frames.

You are not obligated to use a rotate3d() transform as I did. You can also chain different rotations like we did in the previous example. Play around and see what you can come up with! I will be waiting for you to share your version with me in the comments section!

Wrapping up

I hope you enjoyed this little series. We built some fun (and funny) sliders while learning a lot about all kinds of CSS concepts along the way — from grid placement and stacking order, to animation delays and transforms. We even got to play with a dash of Sass to loop through an array of elements.

And we did it all with the exact same HTML for each and every slider we made. How cool is that? CSS is dang powerful and capable of accomplishing so much without the aid of JavaScript.


CSS Infinite 3D Sliders originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS Infinite Slider Flipping Through Polaroid Images

In the last article, we made a pretty cool little slider (or “carousel” if that’s what you prefer) that rotates in a circular direction. This time we are going to make one that flips through a stack of Polaroid images.

Cool right? Don’t look at the code quite yet because there’s a lot to unravel. Join me, will ya?

CSS Sliders series

The basic setup

Most of the HTML and CSS for this slider is similar to the circular one we made last time. In fact, we’re using the exact same markup:

<div class="gallery">
  <img src="" alt="">
  <img src="" alt="">
  <img src="" alt="">
  <img src="" alt="">
</div>

And this is the basic CSS that sets our parent .gallery container as a grid where all the images are stacked one on top of one another:

.gallery  {
  display: grid;
  width: 220px; /* controls the size */
}
.gallery > img {
  grid-area: 1 / 1;
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  border: 10px solid #f2f2f2;
  box-shadow: 0 0 4px #0007;
}

Nothing complex so far. Even for the Polaroid-like style for the images, all I’m using is some border and box-shadow. You might be able to do it better, so feel free to play around with those decorative styles! We’re going to put most of our focus on the animation, which is the trickiest part.

What’s the trick?

The logic of this slider relies on the stacking order of the images — so yes, we are going to play with z-index. All of the images start with the same z-index value (2) which will logically make the last image on the top of the stack.

We take that last image and slide it to the right until it reveals the next image in the stack. Then we decrease the image’s z-index value then we slide it back into the deck. And since its z-index value is lower than the rest of the images, it becomes the last image in the stack.

Here is a stripped back demo that shows the trick. Hover the image to activate the animation:

Now, imagine the same trick applied to all the images. Here’s the pattern if we’re using the :nth-child() pseudo-selector to differentiate the images:

  • We slide the last image (N). The next image is visible (N - 1).
  • We slide the next image (N - 1). The next image is visible (N - 2)
  • We slide the next image (N - 2). The next image is visible (N - 3)
  • (We continue the same process until we reach the first image)
  • We slide the first image (1). The last image (N) is visible again.

That’s our infinite slider!

Dissecting the animation

If you remember the previous article, I defined only one animation and played with delays to control each image. We will be doing the same thing here. Let’s first try to visualize the timeline of our animation. We will start with three images, then generalize it later for any number (N) of images.

Diagramming the three parts of the animation.

Our animation is divided into three parts: “slide to right”, “slide to left” and “don’t move”. We can easily identify the delay between each image. If we consider that the first image starts at 0s, and the duration is equal to 6s, then the second one will start at -2s and the third one at -4s.

.gallery > img:nth-child(2) { animation-delay: -2s; } /* -1 * 6s / 3 */
.gallery > img:nth-child(3) { animation-delay: -4s; } /* -2 * 6s / 3 */

We can also see that the “don’t move” part takes two-thirds of the whole animation (2*100%/3) while the “slide to right” and “slide to left” parts take one-third of it together — so, each one is equal to 100%/6 of the total animation.

We can write our animation keyframes like this:

@keyframes slide {
  0%     { transform: translateX(0%); }
  16.67% { transform: translateX(120%); }
  33.34% { transform: translateX(0%); }
  100%   { transform: translateX(0%); } 
}

That 120% is an arbitrary value. I needed something bigger than 100%. The images need to slide to the right away from the rest of the images. To do that, it needs to move by at least 100% of its size. That’s why I went 120% — to gain some extra space.

Now we need to consider the z-index. Don’t forget that we need to update the image’s z-index value after it slides to the right of the pile, and before we slide it back to the bottom of the pile.

@keyframes slide {
  0%     { transform: translateX(0%);   z-index: 2; }
  16.66% { transform: translateX(120%); z-index: 2; }
  16.67% { transform: translateX(120%); z-index: 1; } /* we update the z-order here */
  33.34% { transform: translateX(0%);   z-index: 1; }
  100%   { transform: translateX(0% );  z-index: 1; }  
}

Instead of defining one state at the 16.67% (100%/6) point in the timeline, we are defining two states at nearly identical points (16.66% and 16.67%) where the z-index value decreases before we slide back the image back to the deck.

Here’s what happens when we pull of all that together:

Hmmm, the sliding part seems to work fine, but the stacking order is all scrambled! The animation starts nicely since the top image is moving to the back… but the subsequent images don’t follow suit. If you notice, the second image in the sequence returns to the top of the stack before the next image blinks on top of it.

We need to closely follow the z-index changes. Initially, all the images have are z-index: 2. That means the stacking order should go…

Our eyes 👀 --> 3rd (2) | 2nd (2) | 1st (2)

We slide the third image and update its z-index to get this order:

Our eyes 👀 --> 2nd (2) | 1st (2) | 3rd (1)

We do the same with the second one:

Our eyes 👀 --> 1st (2) | 3rd (1) | 2nd (1)

…and the first one:

Our eyes 👀 --> 3rd (1) | 2nd (1) | 1st (1)

We do that and everything seems to be fine. But in reality, it’s not! When the first image is moved to the back, the third image will start another iteration, meaning it returns to z-index: 2:

Our eyes 👀 --> 3rd (2) | 2nd (1) | 1st (1)

So, in reality we never had all the images at z-index: 2 at all! When the images aren’t moving (i.e., the “don’t move” part of the animation) the z-index is 1. If we slide the third image and update its z-index value from 2 to 1, it will remain on the top! When all the images have the same z-index, the last one in the source order — our third image in this case — is on top of the stack. Sliding the third image results in the following:

Our eyes 👀 --> 3rd (1) | 2nd (1) | 1st (1)

The third image is still on the top and, right after it, we move the second image to the top when its animation restarts at z-index: 2:

Our eyes 👀 --> 2nd (2) | 3rd (1) | 1st (1)

Once we slide it, we get:

Our eyes 👀 --> 3rd (1) | 2nd (1) | 1st (1)

Then the first image will jump on the top:

Our eyes 👀 --> 1st(2) | 3rd (1) | 2nd (1)

OK, I am lost. All the logic is wrong then?

I know, it’s confusing. But our logic is not completely wrong. We only have to rectify the animation a little to make everything work the way we want. The trick is to correctly reset the z-index.

Let’s take the situation where the third image is on the top:

Our eyes 👀 -->  3rd (2) | 2nd (1) | 1st (1)

We saw that sliding the third image and changing its z-index keeps it on top. What we need to do is update the z-index of the second image. So, before we slide the third image away from the deck, we update the z-index of the second image to 2.

In other words, we reset the z-index of the second image before the animation ends.

Diagramming the parts of the animation with indicators for where z-index is increased or decreased.

The green plus symbol represents increasing z-index to 2, and the red minus symbol correlates to z-index: 1. The second image starts with z-index: 2, then we update it to 1 when it slides away from the deck. But before the first image slides away from the deck, we change the z-index of the second image back to 2. This will make sure both images have the same z-index, but still, the third one will remain on the top because it appears later in the DOM. But after the third image slides and its z-index is updated, it moves to the bottom.

This two-thirds through the animation, so let’s update our keyframes accordingly:

@keyframes slide {
  0%     { transform: translateX(0%);   z-index: 2; }
  16.66% { transform: translateX(120%); z-index: 2; }
  16.67% { transform: translateX(120%); z-index: 1; } /* we update the z-order here */
  33.34% { transform: translateX(0%);   z-index: 1; }
  66.33% { transform: translateX(0%);   z-index: 1; }
  66.34% { transform: translateX(0%);   z-index: 2; } /* and also here */
  100%   { transform: translateX(0%);   z-index: 2; }  
}

A little better, but still not quite there. There’s another issue…

Oh no, this will never end!

Don’t worry, we are not going to change the keyframes again because this issue only happens when the last image is involved. We can make a “special” keyframe animation specifically for the last image to fix things up.

When the first image is on the top, we have the following situation:

Our eyes 👀 -->  1st (2) | 3rd (1) | 2nd (1)

Considering the previous adjustment we made, the third image will jump on the top before the first image slides. It only happens in this situation because the next image that moves after the first image is the last image which has a higher order in the DOM. The rest of the images are fine because we have N, then N - 1, then we go from 3 to 2, and 2 to 1… but then we go from 1 to N.

To avoid that, we will use the following keyframes for the last image:

@keyframes slide-last {
  0%     { transform: translateX(0%);   z-index: 2;}
  16.66% { transform: translateX(120%); z-index: 2; }
  16.67% { transform: translateX(120%); z-index: 1; } /* we update the z-order here */
  33.34% { transform: translateX(0%);   z-index: 1; }
  83.33% { transform: translateX(0%);   z-index: 1; }
  83.34% { transform: translateX(0%);   z-index: 2; } /* and also here */
  100%   { transform: translateX(0%);   z-index: 2; }
}

We reset the z-index value 5/6 through the animation (instead of two-thirds) which is when the first image is out of the pile. So we don’t see any jumping!

TADA! Our infinite slider is now perfect! Here’s our final code in all its glory:

.gallery > img {
  animation: slide 6s infinite;
}
.gallery > img:last-child {
  animation-name: slide-last;
}
.gallery > img:nth-child(2) { animation-delay: -2s; } 
.gallery > img:nth-child(3) { animation-delay: -4s; }

@keyframes slide {
  0% { transform: translateX(0%); z-index: 2; }
  16.66% { transform: translateX(120%); z-index: 2; }
  16.67% { transform: translateX(120%); z-index: 1; } 
  33.34% { transform: translateX(0%); z-index: 1; }
  66.33% { transform: translateX(0%); z-index: 1; }
  66.34% { transform: translateX(0%); z-index: 2; } 
  100% { transform: translateX(0%); z-index: 2; }
}
@keyframes slide-last {
  0% { transform: translateX(0%); z-index: 2; }
  16.66% { transform: translateX(120%); z-index: 2; }
  16.67% { transform: translateX(120%); z-index: 1; }
  33.34% { transform: translateX(0%); z-index: 1; }
  83.33% { transform: translateX(0%); z-index: 1; }
  83.34% { transform: translateX(0%); z-index: 2; } 
  100%  { transform: translateX(0%); z-index: 2; }
}

Supporting any number of images

Now that our animation works for three images, let’s make it work for any number (N) of images. But first, we can optimize our work a little by splitting the animation up to avoid redundancy:

.gallery > img {
  z-index: 2;
  animation: 
    slide 6s infinite,
    z-order 6s infinite steps(1);
}
.gallery > img:last-child {
  animation-name: slide, z-order-last;
}
.gallery > img:nth-child(2) { animation-delay: -2s; } 
.gallery > img:nth-child(3) { animation-delay: -4s; }

@keyframes slide {
  16.67% { transform: translateX(120%); }
  33.33% { transform: translateX(0%); }
}
@keyframes z-order {
  16.67%,
  33.33% { z-index: 1; }
  66.33% { z-index: 2; }
}
@keyframes z-order-last {
  16.67%,
  33.33% { z-index: 1; }
  83.33% { z-index: 2; }
}

Way less code now! We make one animation for the sliding part and another one for the z-index updates. Note that we use steps(1) on the z-index animation. That’s because I want to abruptly change the z-index value, unlike the sliding animation where we want smooth movement.

Now that the code is easier to read and maintain, we have a better view for figuring out how to support any number of images. What we need to do is update the animation delays and the percentages of the keyframes. The delay are easy because we can use the exact same loop we made in the last article to support multiple images in the circular slider:

@for $i from 2 to ($n + 1) {
  .gallery > img:nth-child(#{$i}) {
    animation-delay: calc(#{(1 - $i)/$n}*6s);
  }
}

That means we’re moving from vanilla CSS to Sass. Next, we need to imagine how the timeline scale with N images. Let’s not forget that the animation happens in three phases:

Showing the three parts of the animation in a series of lines with arrows.

After “slide to right” and “slide to left”, the image should stay put until the rest of the images go through the sequence. So the “don’t move” part needs to take the same amount of time as (N - 1) as “slide to right” and “slide to left”. And within one iteration, N images will slide. So, “slide to right” and “slide to left” both take 100%/N of the total animation timeline. The image slides away from the pile at (100%/N)/2 and slides back at 100%/N .

We can change this:

@keyframes slide {
  16.67% { transform: translateX(120%); }
  33.33% { transform: translateX(0%); }
}

…to this:

@keyframes slide {
  #{50/$n}%  { transform: translateX(120%); }
  #{100/$n}% { transform: translateX(0%); }
}

If we replace N with 3, we get 16.67% and 33.33% when there are 3 images in the stack. It’s the same logic with the stacking order where we will have this:

@keyframes z-order {
  #{50/$n}%,
  #{100/$n}% { z-index: 1; }
  66.33% { z-index: 2; }
}

We still need to update the 66.33% point. That’s supposed to be where the image resets its z-index before the end of the animation. At that same time, the next image starts to slide. Since the sliding part takes 100%/N, the reset should happen at 100% - 100%/N:

@keyframes z-order {
  #{50/$n}%,
  #{100/$n}% { z-index: 1; }
  #{100 - 100/$n}% { z-index: 2; }
}

But for our z-order-last animation to work, it should happen a bit later in the sequence. Remember the fix we did for the last image? Resetting the z-index value needs to happen when the first image is out of the pile and not when it starts sliding. We can use the same reasoning here in our keyframes:

@keyframes z-order-last {
  #{50/$n}%,
  #{100/$n}% { z-index: 1; }
  #{100 - 50/$n}% { z-index: 2; }
}

We are done! Here’s what we get when using five images:

We can add a touch of rotation to make things a bit fancier:

All I did is append rotate(var(--r)) to the transform property. Inside the loop, --r is defined with a random angle:

@for $i from 1 to ($n + 1) {
  .gallery > img:nth-child(#{$i}) {
    --r: #{(-20 + random(40))*1deg}; /* a random angle between -20deg and 20deg */
  }
}

The rotation creates small glitches as we can sometimes see some of the images jumping to the back of the stack, but it’s not a big deal.

Wrapping up

All that z-index work was a big balancing act, right? If you were unsure how stacking order work before this exercise, then you probably have a much better idea now! If you found some of the explanations hard to follow, I highly recommend you to take another read of the article and map things out with pencil and paper. Try to illustrate each step of the animation using a different number of images to better understand the trick.

Last time, we used a few geometry tricks to create a circular slider that rotates back to the first image after a full sequence. This time, we accomplished a similar trick using z-index. In both cases, we didn’t duplicate any of the images to simulate a continuous animation, nor did we reach for JavaScript to help with the calculations.

Next time, we will make 3D sliders. Stay tuned!


CSS Infinite Slider Flipping Through Polaroid Images originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS Infinite and Circular Rotating Image Slider

Image sliders (also called carousels) are everywhere. There are a lot of CSS tricks to create the common slider where the images slide from left to right (or the opposite). It’s the same deal with the many JavaScript libraries out there that create fancy sliders with complex animations. We are not going to do any of that in this post.

Through a little series of articles, we are going to explore some fancy and uncommon CSS-only sliders. If you are of tired seeing the same ol’ classic sliders, then you are in the right place!

CSS Sliders series

For this first article, we will start with something I call the “circular rotating image slider”:

Cool right? let’s dissect the code!

The HTML markup

If you followed my series of fancy image decorations or CSS grid and custom shapes, then you know that my first rule is to work with the smallest HTML possible. I always try hard to find CSS solutions before cluttering my code with a lot <div>s and other stuff.

The same rule applies here — our code is nothing but a list of images in a container.

Let’s say we’re working with four images:

<div class="gallery">
  <img src="" alt="">
  <img src="" alt="">
  <img src="" alt="">
  <img src="" alt="">
</div>

That’s it! Now let’s move to the interesting part of the code. But first, we’re going to dive into this to understand the logic of how our slider works.

How does it work?

Here is a video where I remove overflow: hidden from the CSS so we can better understand how the images are moving:

It’s like our four images are placed on a large circle that rotates counter-clockwise.

All the images have the same size (denoted by S in the figure). Note the blue circle which is the circle that intersects with the center of all the images and has a radius (R). We will need this value later for our animation. R is equal to 0.707 * S. (I’m going to skip the geometry that gives us that equation.)

Let’s write some CSS!

We will be using CSS Grid to place all the images in the same area above each other:

.gallery  {
  --s: 280px; /* control the size */

  display: grid;
  width: var(--s);
  aspect-ratio: 1;
  padding: calc(var(--s) / 20); /* we will see the utility of this later */
  border-radius: 50%;
}
.gallery > img {
  grid-area: 1 / 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
}

Nothing too complex so far. The tricky part is the animation.

We talked about rotating a big circle, but in reality, we will rotate each image individually creating the illusion of a big rotating circle. So, let’s define an animation, m, and apply it to the image elements:

.gallery > img {
  /* same as before */
  animation: m 8s infinite linear;
  transform-origin: 50% 120.7%;
}

@keyframes m {
  100% { transform: rotate(-360deg); }
}

The main trick relies on that highlighted line. By default, the CSS transform-origin property is equal to center (or 50% 50%) which makes the image rotate around its center, but we don’t need it to do that. We need the image to rotate around the center of the big circle that contains our images hence the new value for transform-origin.

Since R is equal to 0.707 * S, we can say that R is equal to 70.7% of the image size. Here’s a figure to illustrate how we got the 120.7% value:

Let’s run the animation and see what happens:

I know, I know. The result is far from what we want, but in reality we are very close. It may looks like there’s just one image there, but don’t forget that we have stacked all the images on top of each other. All of them are rotating at the same time and only the top image is visible. What we need is to delay the animation of each image to avoid this overlap.

.gallery > img:nth-child(2) { animation-delay: -2s; } /* -1 * 8s / 4 */
.gallery > img:nth-child(3) { animation-delay: -4s; } /* -2 * 8s / 4 */
.gallery > img:nth-child(4) { animation-delay: -6s; } /* -3 * 8s / 4 */

Things are already getting better!

If we hide the overflow on the container we can already see a slider, but we will update the animation a little so that each image remains visible for a short period before it moves along.

We’re going to update our animation keyframes to do just that:

@keyframes m {
  0%, 3% { transform: rotate(0); }
  22%, 27% { transform: rotate(-90deg); }
  47%, 52% { transform: rotate(-180deg); }
  72%, 77% { transform: rotate(-270deg); }
  98%, 100% { transform: rotate(-360deg); }
}

For each 90deg (360deg/4, where 4 is the number of images) we will add a small pause. Each image will remain visible for 5% of the overall duration before we slide to the next one (27%-22%, 52%-47%, etc.). I’m going to update the animation-timing-function using a cubic-bezier() function to make the animation a bit fancier:

Now our slider is perfect! Well, almost perfect because we are still missing the final touch: the colorful circular border that rotates around our images. We can use a pseudo-element on the .gallery wrapper to make it:

.gallery {
  padding: calc(var(--s) / 20); /* the padding is needed here */
  position: relative;
}
.gallery::after {
  content: "";
  position: absolute;
  inset: 0;
  padding: inherit; /* Inherits the same padding */
  border-radius: 50%;
  background: repeating-conic-gradient(#789048 0 30deg, #DFBA69 0 60deg);
  mask: 
    linear-gradient(#fff 0 0) content-box, 
    linear-gradient(#fff 0 0);
  mask-composite: exclude;
}
.gallery::after,
.gallery >img {
  animation: m 8s infinite cubic-bezier(.5, -0.2, .5, 1.2);
}

I have created a circle with a repeating conic gradient for the background while using a masking trick that only shows the padded area. Then I apply to it the same animation we defined for the images.

We are done! We have a cool circular slider:

Let’s add more images

Working with four images is good, but it would be better if we can scale it to any number of images. After all, this is the purpose of an image slider. We should be able to consider N images.

For this, we are going to make the code more generic by introducing Sass. First, we define a variable for the number of images ($n) and we will update every part where we hard-coded the number of images (4).

Let’s start with the delays:

.gallery > img:nth-child(2) { animation-delay: -2s; } /* -1 * 8s / 4 */
.gallery > img:nth-child(3) { animation-delay: -4s; } /* -2 * 8s / 4 */
.gallery > img:nth-child(4) { animation-delay: -6s; } /* -3 * 8s / 4 */

The formula for the delay is (1 - $i)*duration/$n, which gives us the following Sass loop:

@for $i from 2 to ($n + 1) {
  .gallery > img:nth-child(#{$i}) {
    animation-delay: calc(#{(1 - $i) / $n} * 8s);
  }
}

We can make the duration a variable as well if we really want to. But let’s move on to the animation:

@keyframes m {
  0%, 3% { transform: rotate(0); }
  22%, 27% { transform: rotate(-90deg); }
  47%, 52% { transform: rotate(-180deg); }
  72%, 77% { transform: rotate(-270deg); }
  98%, 100% {transform: rotate(-360deg); }
}

Let’s simplify it to get a better view of the pattern:

@keyframes m {
  0% { transform: rotate(0); }
  25% { transform: rotate(-90deg); }
  50% { transform: rotate(-180deg); }
  75% { transform: rotate(-270deg); }
  100% { transform: rotate(-360deg); }
}

The step between each state is equal to 25% — which is 100%/4 — and we add a -90deg angle — which is -360deg/4. That means we can write our loop like this instead:

@keyframes m {
  0% { transform: rotate(0); }
  @for $i from 1 to $n {
    #{($i / $n) * 100}% { transform: rotate(#{($i / $n) * -360}deg); }  
  }
  100% { transform: rotate(-360deg); }
}

Since each image takes 5% of the animation, we change this:

#{($i / $n) * 100}%

…with this:

#{($i / $n) * 100 - 2}%, #{($i / $n) * 100 + 3}%

It should be noted that 5% is an arbitrary value I choose for this example. We can also make it a variable to control how much time each image should stay visible. I am going to skip that for the sake of simplicity, but for homework, you can try to do it and share your implementation in the comments!

@keyframes m {
  0%,3% { transform: rotate(0); }
  @for $i from 1 to $n {
    #{($i / $n) * 100 - 2}%, #{($i / $n) * 100 + 3}% { transform: rotate(#{($i / $n) * -360}deg); }  
  }
  98%,100% { transform: rotate(-360deg); }
}

The last bit is to update transform-origin. We will need some geometry tricks. Whatever the number of images, the configuration is always the same. We have our images (small circles) placed inside a big circle and we need to find the value of the radius, R.

You probably don’t want a boring geometry explanation so here’s how we find R:

R = S / (2 * sin(180deg / N))

If we express that as a percentage, that gives us:

R = 100% / (2 * sin(180deg / N)) = 50% / sin(180deg / N)

…which means the transform-origin value is equal to:

transform-origin: 50% (50% / math.sin(180deg / $n) + 50%);

We’re done! We have a slider that works with any number images!

Let’s toss nine images in there:

Add as many images as you want and update the $n variable with the total number of images.

Wrapping up

With a few tricks using CSS transforms and standard geometry, we created a nice circular slider that doesn’t require a lot of code. What is cool about this slider is that we don’t need to bother duplicating the images to keep the infinite animation since we have a circle. After a full rotation, we will get back to the first image!


CSS Infinite and Circular Rotating Image Slider originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Qi Theme

Qi Theme 21

Qi Theme is a free WordPress theme created by Qode Interactive – an award-winning studio. This theme perfectly combines top speed and performance with a beautiful design.

It comes with 100 demos, allowing you to easily set up any type of website, whether it’s an online store, an artist’s portfolio, or a simple blog. If you can think it up, this theme will help you build it – it will even grant you free access to premium stock photos.

Qi Theme is fully supported by video tutorials as well as an extensive knowledge base, so you’ll always have a place to look for help.

Spark SEO

spark seo

Spark SEO offers search engine Optimisation services to small and medium local businesses. We are a remote team of absolute subject matter experts, and we are ready to help your local business grow

The post Spark SEO appeared first on WeLoveWP.

Crafting a Scrollable and Draggable Parallax Slider

In this article, I’ll show you how to build a parallax slider with a fun reveal animation. I’ll be using GSAP, CSS Grid and Flexbox and I’ll assume that you have some basic knowledge on how to use these techniques. Besides that, I’ll be using a custom smooth scroll based on Virtual Scroll for a better experience.

The article is split up in 3 steps:

  1. The hover animation
  2. The open/expand animation
  3. The slider scroll/drag parallax effect

 For a better understanding I added motion videos for each step.

1. Hover animation

When we hover the button on the top right, 3 placeholder images will animate in the viewport from the right side.

Markup

<button class="button-slider-open js-slider-open" type="button">
  <svg>...</svg>
</button>

<div class="placeholders js-placeholders">
  <div class="placeholders__img-wrap js-img-wrap" style="--aspect-ratio: 0.8;">
    <img
      src="https://images.unsplash.com/photo-1479839672679-a46483c0e7c8?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=80&ar=0.8"
      class="placeholders__img"
    >
  </div>

  ...
</div>

Animation

We have a this.dom object where we store all the DOM elements such as the images. When initializing the app we call the setHoverAnimation which creates a GSAP timeline that is set default to paused. In the timeline we will animate the 3 images into the viewport so they’re partly visible. The user will then have the impression that they can be expanded on click.

this.dom = {};
this.dom.el = document.querySelector('.js-placeholders');
this.dom.images = this.dom.el.querySelectorAll('.js-img-wrap');
this.dom.buttonOpen = document.querySelector('.js-slider-open');
setHoverAnimation() {
  this.tl = gsap.timeline({ paused: true });

  this.tl
    .addLabel('start')

    .set(this.dom.el, { autoAlpha: 1 })
    .set(this.dom.images, { scale: 0.5, x: (window.innerWidth / 12) * 1.2, rotation: 0 })

    .to(this.dom.images, { duration: 1, stagger: 0.07, ease: 'power3.inOut', x: 0, y: 0 })
    .to(this.dom.images[0], { duration: 1, ease: 'power3.inOut', rotation: -4 }, 'start')
    .to(this.dom.images[1], { duration: 1, ease: 'power3.inOut', rotation: -2 }, 'start');
}

To trigger the animation when hovering the button, we created 2 events (handleMouseenter and handleMouseleave) which will play the GSAP timeline or simply reverse the animation.

this.dom.buttonOpen.addEventListener('mouseenter', this.handleMouseenter);
this.dom.buttonOpen.addEventListener('mouseleave', this.handleMouseleave);

handleMouseenter() {
  this.tl.play();
}

handleMouseleave() {
  this.tl.reverse();
}

2. Expand placeholder items

When we click the button on the top right, after hovering, the 3 placeholder images will animate to a 3 column grid. There will be more than 3 items in the slider, but only the first 3 will be visible in the viewport so it’s not necessary to animate the other items. Once the 3 placeholder items are in the correct position we can display the actual slider items underneath and remove the placeholder items.

Animation

First we have to calculate the position that the placeholder items will have to animate to. Subtracting the left position of the placeholder items with the left position of the slider items will get you the correct x position. To get the y position I’m doing the exact same thing, but use the top bounds instead.

const x1 = this.bounds.left - slider.items[0].bounds.left - 20;
const x2 = this.bounds.left - slider.items[1].bounds.left + 10;
const x3 = this.bounds.left - slider.items[2].bounds.left;

const y1 = this.bounds.top - slider.items[0].bounds.top + 10;
const y2 = this.bounds.top - slider.items[1].bounds.top - 30;
const y3 = this.bounds.top - slider.items[2].bounds.top + 30;

The placeholder items are smaller than the slider items so we have to scale them up. To calculate the scale value we will just divide the width of the placeholder items by the width of one of the slider items (they are all the same size).

const scale = slider.items[0].bounds.width / this.bounds.width;

The intersectX1 X2 X3 values are used to set the initial x position of the images inside its container. Each image in the slider will have its own x position stored. The x position will be used to animate the slider images inside its container, this will create the parallax effect.

const intersectX1 = slider.items[0].x;
const intersectX2 = slider.items[1].x;
const intersectX3 = slider.items[2].x;

A new GSAP timeline will be created for the expand animation. Once the timeline is completed setHoverAnimation will be fired so the placeholder items are reset and ready to animate again. Also we will start the reveal animation of the slider elements such as the text on the items and the close button, but we will not go into the text animations. You can find that in the final code.

this.tl = gsap.timeline({
  onComplete: () => {
    this.setHoverAnimation();
    slider.open();
  }
});

The 3 placeholder images will animate to the x and y position that we defined earlier. Let’s have a look at the GSAP timeline.

this.tl
  .addLabel('start')
  .to(this.dom.images[0], { duration: 1.67, ease: 'power3.inOut', x: -x1, y: -y1, scale, rotation: 0 }, 'start')
  .to(this.dom.images[1], { duration: 1.67, ease: 'power3.inOut', x: -x2, y: -y2, scale, rotation: 0 }, 'start')
  .to(this.dom.images[2], { duration: 1.67, ease: 'power3.inOut', x: -x3, y: -y3, scale, rotation: 0 }, 'start')
  .to(this.dom.images[0].querySelector('img'), { duration: 1.67, ease: 'power3.inOut', x: intersectX1 }, 'start')
  .to(this.dom.images[1].querySelector('img'), { duration: 1.67, ease: 'power3.inOut', x: intersectX2 }, 'start')
  .to(this.dom.images[2].querySelector('img'), { duration: 1.67, ease: 'power3.inOut', x: intersectX3 }, 'start',)
  .set(this.dom.el, { autoAlpha: 0 }, 'start+=1.67')

3. Parallax effect

The images are scaled up in its container that will have `overflow: hidden`. Once you move the slider, the images that are in the viewport will move with a slightly different speed.

Markup

<div class="slider js-slider">
  <div class="slider__container js-container" data-scroll>
    <div class="slider__item js-item" style="--aspect-ratio: 0.8;">
      <div class="slider__item-img-wrap js-img-wrap js-img" style="--aspect-ratio: 0.8;">
        <img
          src="https://images.unsplash.com/photo-1472835560847-37d024ebacdc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=80&ar=0.8"
          class="slider__item-img"
        >
      </div>

      <div class="slider__item-content">
        <div class="slider__item-heading-wrap">
          <h3 class="slider__item-heading">
            Indigo
          </h3>
        </div>

        <div class="slider__item-button-wrap">
          <button class="button slider__item-button" type="button">
            Read more
          </button>
        </div>
      </div>
    </div>

    ...
  </div>
</div>
<div class="slider__progress-wrap js-progress-wrap">
  <div class="slider__progress js-progress"></div>
</div>

Animation

We will be using a smooth scroll on top of Virtual Scroll, but we will not go into how to set this up (please check the documentation). With interpolation, we can achieve this smooth parallax scrolling effect.

Once again we have a this.dom object where we will store all the DOM elements that we need.

this.dom = {};
this.dom.el = document.querySelector('.js-slider');
this.dom.container = this.dom.el.querySelector('.js-container');
this.dom.items = this.dom.el.querySelectorAll('.js-item');
this.dom.images = this.dom.el.querySelectorAll('.js-img-wrap');
this.dom.progress = this.dom.el.querySelector('.js-progress');

To keep track of the scroll progress we created a simple progress bar which defines how much we moved the slider. We simply calculate a value between 0 and 1 that represents the x position of the slider container. This value will be updated in a requestAnimationFrame and used to animate the scaleX value of the progress bar.

const max = -this.dom.container.offsetWidth + window.innerWidth;
const progress = ((scroll.state.last - 0) * 100) / (max - 0) / 100;

this.dom.progress.style.transform = `scaleX(${progress})`;

Before we start the animation of the parallax effect on the images we first store some data of each slider item in the array this.items. This array wil contain the image element, the bounds and the x position of the image.

setCache() {
  this.items = [];
  [...this.dom.items].forEach((el) => {
    const bounds = el.getBoundingClientRect();

    this.items.push({
      img: el.querySelector('img'),
      bounds,
      x: 0,
    });
  });
}

Now we’re finally ready to create the parallax effect on the images. The code below will be executed in a render function in a requestAnimationFrame. In this function we will be using the interpolated value of our scroll (scroll.state.last).

For a better performance we will only animate the images that are in the viewport. To do so we will detect which items are visible in the viewport.

const { bounds } = item;
const inView = scrollLast + window.innerWidth >= bounds.left && scrollLast < bounds.right;

If the item is visible in the viewport, we will calculate a value between 0 and 100 (percentage) that indicates how much of the target element is actually visible within the viewport.

const min = bounds.left - window.innerWidth;
const max = bounds.right;
const percentage = ((scrollLast - min) * 100) / (max - min);

Once we have that value stored, we can calculate a new value based on percentage that we will then transform into a pixel value like this.

const newMin = -(window.innerWidth / 12) * 3;
const newMax = 0;
item.x = ((percentage - 0) / (100 - 0)) * (newMax - newMin) + newMin;

After calculating that final x value we can now simply animate the image inside its container like this.

item.img.style.transform = `translate3d(${item.x}px, 0, 0)`;

This is what the render function looks like.

render() {
  const scrollLast = scroll.state.last;

  this.items.forEach((item) => {
    const { bounds } = item;
    const inView = scrollLast + window.innerWidth >= bounds.left && scrollLast < bounds.right;

    if (inView) {
      const min = bounds.left - window.innerWidth;
      const max = bounds.right;
      const percentage = ((scrollLast - min) * 100) / (max - min);
      const newMin = -(window.innerWidth / 12) * 3;
      const newMax = 0;
      item.x = ((percentage - 0) / (100 - 0)) * (newMax - newMin) + newMin;

      item.img.style.transform = `translate3d(${item.x}px, 0, 0) scale(1.75)`;
    }
  });
}

I hope this has been not too difficult to follow and that you have gained some insight into creating this parallax slider.

If you would like to see this slider live in action, let’s have a look at the architectural website for Nieuw Bergen by Gewest13. It’s used to showcase their seven buildings.

Please let me know if you have any questions @rluijtenant.

The post Crafting a Scrollable and Draggable Parallax Slider appeared first on Codrops.

14 Best WordPress Testimonial Plugins (Compared)

Are you looking for the best testimonial plugins for your WordPress website?

Testimonials and reviews are a great way to add social proof to your website and help build trust among your users.

In this article, we have hand-picked the best testimonial plugins for WordPress that you can use on your website.

The best testimonial plugins for WordPress

Why Use a Testimonial Plugin for WordPress?

Using a testimonial plugin lets you easily add customer or client testimonials, reviews, or feedback to your WordPress website. This adds social proof to your site and helps you win new customers and boost sales.

You could add testimonials by manually copying them into your posts or pages. However, a testimonials plugin lets you display them attractively in a slider, carousel, grid, and other beautiful layouts.

Depending on the plugin you choose, you can show testimonials with photos, a short blurb, reviewer’s name, title, business, or star ratings.

That being said, let’s take a look at some of the best WordPress testimonial plugins that you can try.

1. Facebook Social Reviews Feed – Smash Balloon

Facebook Social Reviews Feed – Smash Balloon

Facebook Social Reviews Feed from Smash Balloon lets you display reviews from your Facebook Page on your site. This is a fantastic option if you already have some great reviews on Facebook. It means you don’t have to copy and paste any text or seek out fresh testimonials.

Facebook reviews boost user trust as they come from real Facebook profiles that can be looked up.

Custom Facebook Feed Pro itself is the most popular Facebook feed plugin out there and works with any WordPress theme. It makes it really easy to add a Facebook page feed to WordPress and boost engagement on your social profiles.

With the Reviews extension, you can display Facebook reviews as testimonials by entering the shortcode in any post or page, or even in a sidebar or footer widget. You can also display them anywhere on your landing pages or individual product pages.

Note: You will need to purchase the Custom Facebook Feed Pro plugin by Smash Balloon in order to use the Reviews extension.

2. Custom Twitter Review Feed – Smash Balloon

Custom Twitter Review Feed – Smash Balloon

Custom Twitter Review Feed from Smash Balloon is a great option for displaying Twitter testimonials on your site.

Like Facebook reviews, testimonials that are shared through tweets are a great way to show feedback from real individuals.

It’s easy to narrow your Twitter feed down to only show testimonials. You can do this by using hashtags or an advanced search to only display testimonials about you or your company.

The Custom Twitter Feeds Pro plugin has lots of other great features as well. For instance, you might create a curated list of Twitter accounts then display this as a feed on your website.

Plus, you can even automatically moderate tweets so that tweets containing specified words are hidden.

3. WPForms

The WPForms website

WPForms is the best WordPress form plugin in the market used by over 3 million users. It can be used to collect customer testimonials too. This helps you get all your testimonials in one place and one format.

WPForms’ special Form Templates Pack addon includes a Testimonial Form Template. This lets you get started quickly. You can modify the form and any form fields that you may need.

With WPForms, you could even add a file upload option to your testimonial form. This lets customers upload files, such as photos of themselves with your product or even a short video clip.

You can also use WPForms to run a survey with beautiful reports. You could use this to show data about how your customers use your products, what they like best, and how satisfied they are.

4. Site Reviews

Site Reviews

Site Reviews lets visitors submit reviews with a 1 to 5 star rating. With this plugin, you can pin your best reviews or testimonials so that they show up first.

It’s also possible to moderate new review submissions, approving them before they go live on your site. This helps you protect against unfairly bad reviews. Alternatively, you can require users to be logged into your site in order to leave a review.

Site Reviews lets you easily respond to reviews, display a summary, and more. The developers provide active support through the WordPress.org support forum for the plugin.

5. Easy Testimonials

Easy Testimonials

Easy Testimonials lets you enter testimonials from your WordPress admin area.

You can include a star rating, the person’s role, and the product or service they used, as well as the testimonial itself. If you enter their email address, Easy Testimonials will automatically use their Gravatar image if they have one.

The pro version of Easy Testimonials also allows you to add a customer feedback form on your website.

Easy Testimonials includes 5 blocks for the WordPress block editor (Gutenberg editor). You can also display testimonials using a simple shortcode.

For detailed instructions, see our guide on how to add a customer reviews page in WordPress.

6. Testimonials Widget

Testimonials widget

Testimonials Widget is another flexible WordPress testimonials plugin. Despite the name, it allows you to add testimonials anywhere on your website, not just in widgets.

It comes with a sidebar widget allowing you to easily display testimonials in sidebars. Testimonials are displayed in a carousel with beautiful slide-in and fade effects.

7. Strong Testimonials

Strong Testimonials

Strong Testimonials is one of the most customizable WordPress testimonial plugins. It comes with multiple display options. These include a testimonial slider, grid layout, masonry layout, single column layout, and more.

It also includes a form that allows your customers to easily add their reviews and testimonials. If you have been getting customer reviews on social media, then the pro version of this plugin can also embed reviews from Facebook, Google My Business, Yelp, and more.

8. Testimonial Basics

Testimonial Basics

Testimonial Basics is a complete testimonial management solution for your WordPress website. It includes multiple display styles, a sidebar widget, and a feedback form to collect user testimonials.

If a user submits a testimonial, this plugin will use Gravatar to fetch their photo. However, you can also manually upload photos directly from your WordPress admin area.

9. WP Testimonials with Rotator Widget

WP Testimonials with Rotator

WP Testimonials with Rotator Widget is a simple yet highly customizable testimonials plugin. It allows you to easily add testimonials and sort them into categories and tags.

After creating testimonials, you can display them using a shortcode. This shortcode comes with several parameters that let you customize the appearance of testimonials. The plugin also includes a widget with the same options as the shortcode.

10. BNE Testimonials

BNE Testimonials

BNE Testimonials is another easy to use option for adding testimonials to your WordPress site. It comes with a shortcode and a sidebar widget to easily display testimonials.

It has a slider and plain list layout for testimonials, which inherits your theme’s style for display.

11. Testimonial

Testimonial Builder

Testimonial Builder is a straightforward WordPress testimonials plugin. It has simple options and the ability to choose your own colors.

It has draggable box items and allows you to choose different fonts, font-sizes, text color, and featured photo. The plugin does not have a slider or rotator, so testimonials will be displayed in a grid or list style.

12. Testimonial Slider

Testimonial Slider

Testimonial Slider offers a modern WordPress testimonial slider with an improved user experience. It comes with a slider as well as the option for a plain list layout. You can also add a front-end contact form to collect customer testimonials.

You can add testimonials anywhere using the shortcode or sidebar widget. The testimonial slider is touch / mobile friendly and works well at all screen sizes.

Bonus Tools to Add Social Proof Beyond Testimonials

Testimonials show that users find your products/services good. However, you need to maximize social proof with other tools as well.

The following are a couple of tools that you can use to create the FOMO effect with social proof.

13. TrustPulse

The TrustPulse website

TrustPulse is a powerful social proof plugin for WordPress. It helps you boost conversions by showing real-time notifications of activity on your website.

For instance, TrustPulse can show notifications about sales, downloads, forms submitted, and more. It’s a powerful way to use the fear of FOMO to encourage potential customers to buy.

The TrustPulse notifications aren’t intrusive, but they are still highly effective. They can boost conversions up to 15% straight out of the box. You can customize the style, colors, text, image, and more for the notifications.

14. OptinMonster

OptinMonster displaying a testimonial popup

OptinMonster is the best conversion optimization tool on the market. It allows you to easily convert website visitors into paying customers.

It comes with beautiful popups, slide-in menus, countdown timers, floating headers, and other tools. All of them can be used to display social proof and customer testimonials.

It has powerful targeting features that allow you to show the right message at right time. For instance, you can show users a popup when they are about to leave.

It works with all top email marketing services, WordPress form plugins, and WooCommerce. This helps you boost your conversions several folds.

We hope this article helped you find the best testimonial plugin for your WordPress website. You may also want to see our complete list of must have WordPress plugins for business websites, and our proven tips to increase blog traffic.

If you liked this article, then please subscribe to our YouTube Channel for WordPress video tutorials. You can also find us on Twitter and Facebook.

The post 14 Best WordPress Testimonial Plugins (Compared) appeared first on WPBeginner.

Creating WebGL Effects with CurtainsJS

This article focuses adding WebGL effects to <image> and <video> elements of an already “completed” web page. While there are a few helpful resources out there on this subject (like these two), I hope to help simplify this subject by distilling the process into a few steps: 

  • Create a web page as you normally would.
  • Render pieces that you want to add WebGL effects to with WebGL.
  • Create (or find) the WebGL effects to use.
  • Add event listeners to connect your page with the WebGL effects.

Specifically, we’ll focus on the connection between regular web pages and WebGL. What are we going to make? How about a draggle image slider with an interactive mouse hover!

We won’t cover the core functionality of slider or go very far into the technical details of WebGL or GLSL shaders. However, there are plenty of comments in the demo code and links to outside resources if you’d like to learn more. 

We’re using the latest version of WebGL (WebGL2) and GLSL (GLSL 300) which currently do not work in Safari or in Internet Explorer. So, use Firefox or Chrome to view the demos. If you’re planning to use any of what we’re covering in production, you should load both the GLSL 100 and 300 versions of the shaders and use the GLSL 300 version only if curtains.renderer._isWebGL2 is true. I cover this in the demo above.

First, create a web page as you normally would

You know, HTML and CSS and whatnot. In this case, we’re making an image slider but that’s just for demonstration. We’re not going to go full-depth on how to make a slider (Robin has a nice post on that). But here’s what I put together:

  1. Each slide is equal to the full width of the page.
  2. After a slide has been dragged, the slider continues to slide in the direction of the drag and gradually slow down with momentum.
  3. The momentum snaps the slider to the nearest slide at the end point. 
  4. Each slide has an exit animation that’s fired when the drag starts and an enter animation that’s fired when the dragging stops.
  5. When hovering the slider, a hover effect is applied similar to this video.

I’m a huge fan of the GreenSock Animation Platform (GSAP). It’s especially useful for us here because it provides a plugin for dragging, one that enables momentum on drag, and one for splitting text by line . If you’re uncomfortable creating sliders with GSAP, I recommend spending some time getting familiar with the code in the demo above.

Again, this is just for demonstration, but I wanted to at least describe the component a bit. These are the DOM elements that we will keep our WebGL synced with. 

Next, use WebGL to render the pieces that will contain WebGL effects

Now we need to render our images in WebGL. To do that we need to:

  1. Load the image as a texture into a GLSL shader.
  2. Create a WebGL plane for the image and correctly apply the image texture to the plane.
  3. Position the plane where the DOM version of the image is and scale it correctly.

The third step is particularly non-trivial using pure WebGL because we need to track the position of the DOM elements we want to port into the WebGL world while keeping the DOM and WebGL parts in sync during scroll and user interactions.

There’s actually a library that helps us do all of this with ease: CurtainsJS! It’s the only library I’ve found that easily creates WebGL versions of DOM images and videos and syncs them without too many other features (but I’d love to be proven wrong on that point, so please leave a comment if you know of others that do this well).

With Curtains, this is all the JavaScript we need to add:

// Create a new curtains instance
const curtains = new Curtains({ container: "canvas", autoRender: false });
// Use a single rAF for both GSAP and Curtains
function renderScene() {
  curtains.render();
}
gsap.ticker.add(renderScene);
// Params passed to the curtains instance
const params = {
  vertexShaderID: "slider-planes-vs", // The vertex shader we want to use
  fragmentShaderID: "slider-planes-fs", // The fragment shader we want to use
  
 // Include any variables to update the WebGL state here
  uniforms: {
    // ...
  }
};
// Create a curtains plane for each slide
const planeElements = document.querySelectorAll(".slide");
planeElements.forEach((planeEl, i) => {
  const plane = curtains.addPlane(planeEl, params);
  // const plane = new Plane(curtains, planeEl, params); // v7 version
  // If our plane has been successfully created
  if(plane) {
    // onReady is called once our plane is ready and all its texture have been created
    plane.onReady(function() {
      // Add a "loaded" class to display the image container
      plane.htmlElement.closest(".slide").classList.add("loaded");
    });
  }
});

We also need to update our updateProgress function so that it updates our WebGL planes.

function updateProgress() {
  // Update the actual slider
  animation.progress(wrapVal(this.x) / wrapWidth);
  
  // Update the WebGL slider planes
  planes.forEach(plane => plane.updatePosition());
}

We also need to add a very basic vertex and fragment shader to display the texture that we’re loading. We can do that by loading them via <script> tags, like I do in the demo, or by using backticks as I show in the final demo.  

Again, this article will not go into a lot of detail on the technical aspects of these GLSL shaders. I recommend reading The Book of Shaders and the WebGL topic on Codrops as starting points.

If you don’t know much about shaders, it’s sufficient to say that the vertex shader positions the planes and the fragment shader processes the texture’s pixels. There are also three variable prefixes that I want to point out:

  • ins are passed in from a data buffer. In vertex shaders, they come from the CPU (our program). In fragment shaders, they come from the vertex shader.
  • uniforms are passed in from the CPU (our program).
  • outs are outputs from our shaders. In vertex shaders, they are passed into our fragment shader. In fragment shaders, they are passed to the frame buffer (what is drawn to the screen).

Once we’ve added all of that to our project, we have the same exact thing before but our slider is now being displayed via WebGL! Neat.

CurtainsJS easily converts images and videos to WebGL. As far as adding WebGL effects to text, there are several different methods but perhaps the most common is to draw the text to a <canvas> and then use it as a texture in the shader (e.g. 1, 2). It’s possible to do most other HTML using html2canvas (or similar) and use that canvas as a texture in the shader; however, this is not very performant.

Create (or find) the WebGL effects to use

Now we can add WebGL effects since we have our slider rendering with WebGL. Let’s break down the effects seen in our inspiration video:

  1. The image colors are inverted.
  2. There is a radius around the mouse position that shows the normal color and creates a fisheye effect.
  3. The radius around the mouse animates from 0 when the slider is hovered and animates back to 0 when it is no longer hovered.
  4. The radius doesn’t jump to the mouse’s position but animates there over time.
  5. The entire image translates based on the mouse’s position in reference to the center of the image.

When creating WebGL effects, it’s important to remember that shaders don’t have a memory state that exists between frames. It can do something based on where the mouse is at a given time, but it can’t do something based on where the mouse has been all by itself. That’s why for certain effects, like animating the radius once the mouse has entered the slider or animating the position of the radius over time, we should use a JavaScript variable and pass that value to each frame of the slider. We’ll talk more about that process in the next section.

Once we modify our shaders to invert the color outside of the radius and create the fisheye effect inside of the radius, we’ll get something like the demo below. Again, the point of this article is to focus on the connection between DOM elements and WebGL so I won’t go into detail about the shaders, but I did add comments to them.

But that’s not too exciting yet because the radius is not reacting to our mouse. That’s what we’ll cover in the next section.

I haven’t found a repository with a lot of pre-made WebGL shaders to use for regular websites. There’s ShaderToy and VertexShaderArt (which have some truly amazing shaders!), but neither is aimed at the type of effects that fit on most websites. I’d really like to see someone create a repository of WebGL shaders as a resource for people working on everyday sites. If you know of one, please let me know.

Add event listeners to connect your page with the WebGL effects

Now we can add interactivity to the WebGL portion! We need to pass in some variables (uniforms) to our shaders and affect those variables when the user interacts with our elements. This is the section where I’ll go into the most detail because it’s the core for how we connect JavaScript to our shaders.

First, we need to declare some uniforms in our shaders. We only need the mouse position in our vertex shader:

// The un-transformed mouse position
uniform vec2 uMouse;

We need to declare the radius and resolution in our fragment shader:

uniform float uRadius; // Radius of pixels to warp/invert
uniform vec2 uResolution; // Used in anti-aliasing

Then let’s add some values for these inside of the parameters we pass into our Curtains instance. We were already doing this for uResolution! We need to specify the name of the variable in the shader, it’s type, and then the starting value:

const params = {
  vertexShaderID: "slider-planes-vs", // The vertex shader we want to use
  fragmentShaderID: "slider-planes-fs", // The fragment shader we want to use
  
  // The variables that we're going to be animating to update our WebGL state
  uniforms: {
    // For the cursor effects
    mouse: { 
      name: "uMouse", // The shader variable name
      type: "2f",     // The type for the variable - https://webglfundamentals.org/webgl/lessons/webgl-shaders-and-glsl.html
      value: mouse    // The initial value to use
    },
    radius: { 
      name: "uRadius",
      type: "1f",
      value: radius.val
    },
    
    // For the antialiasing
    resolution: { 
      name: "uResolution",
      type: "2f", 
      value: [innerWidth, innerHeight] 
    }
  },
};

Now the shader uniforms are connected to our JavaScript! At this point, we need to create some event listeners and animations to affect the values that we’re passing into the shaders. First, let’s set up the animation for the radius and the function to update the value we pass into our shader:

const radius = { val: 0.1 };
const radiusAnim = gsap.from(radius, { 
  val: 0, 
  duration: 0.3, 
  paused: true,
  onUpdate: updateRadius
});
function updateRadius() {
  planes.forEach((plane, i) => {
    plane.uniforms.radius.value = radius.val;
  });
}

If we play the radius animation, then our shader will use the new value each tick.

We also need to update the mouse position when it’s over our slider for both mouse devices and touch screens. There’s a lot of code here, but you can walk through it pretty linearly. Take your time and process what’s happening.

const mouse = new Vec2(0, 0);
function addMouseListeners() {
  if ("ontouchstart" in window) {
    wrapper.addEventListener("touchstart", updateMouse, false);
    wrapper.addEventListener("touchmove", updateMouse, false);
    wrapper.addEventListener("blur", mouseOut, false);
  } else {
    wrapper.addEventListener("mousemove", updateMouse, false);
    wrapper.addEventListener("mouseleave", mouseOut, false);
  }
}


// Update the stored mouse position along with WebGL "mouse"
function updateMouse(e) {
  radiusAnim.play();
  
  if (e.changedTouches && e.changedTouches.length) {
    e.x = e.changedTouches[0].pageX;
    e.y = e.changedTouches[0].pageY;
  }
  if (e.x === undefined) {
    e.x = e.pageX;
    e.y = e.pageY;
  }
  
  mouse.x = e.x;
  mouse.y = e.y;
  
  updateWebGLMouse();
}


// Updates the mouse position for all planes
function updateWebGLMouse(dur) {
  // update the planes mouse position uniforms
  planes.forEach((plane, i) => {
    const webglMousePos = plane.mouseToPlaneCoords(mouse);
    updatePlaneMouse(plane, webglMousePos, dur);
  });
}


// Updates the mouse position for the given plane
function updatePlaneMouse(plane, endPos = new Vec2(0, 0), dur = 0.1) {
  gsap.to(plane.uniforms.mouse.value, {
    x: endPos.x,
    y: endPos.y,
    duration: dur,
    overwrite: true,
  });
}


// When the mouse leaves the slider, animate the WebGL "mouse" to the center of slider
function mouseOut(e) {
  planes.forEach((plane, i) => updatePlaneMouse(plane, new Vec2(0, 0), 1) );
  
  radiusAnim.reverse();
}

We should also modify our existing updateProgress function to keep our WebGL mouse synced.

// Update the slider along with the necessary WebGL variables
function updateProgress() {
  // Update the actual slider
  animation.progress(wrapVal(this.x) / wrapWidth);
  
  // Update the WebGL slider planes
  planes.forEach(plane => plane.updatePosition());
  
  // Update the WebGL "mouse"
  updateWebGLMouse(0);
}

Now we’re cooking with fire! Our slider now mets all of our requirements.

Two additional benefits of using GSAP for your animations is that it provides access to callbacks, like onComplete, and GSAP keeps everything perfectly synced no matter the refresh rate (e.g. this situation).

You take it from here!

This is, of course, just the tip of the iceberg when it comes to what we can do with the slider now that it is in WebGL. For example,  common effects like turbulence and displacement can be added to the images in WebGL. The core concept of a displacement effect is to move pixels around based on a gradient lightmap that we use as an input source. We can use this texture (that I pulled from this displacement demo by Jesper Landberg — you should give him a follow) as our source and then plug it into our shader. 

To learn more about creating textures like these, see this article, this tweet, and this tool. I am not aware of any existing repositories of images like these, but if you know of one please, let me know.

If we hook up the texture above and animate the displacement power and intensity so that they vary over time and based on our drag velocity, then it will create a nice semi-random, but natural-looking displacement effect:

It’s also worth noting that Curtains has its own React version if that’s how you like to roll.

That’s all I’ve got for now. If you create something using what you’ve learned from this article, I’d love to see it! Connect with me via Twitter.


The post Creating WebGL Effects with CurtainsJS appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Let’s Make a Multi-Thumb Slider That Calculates The Width Between Thumbs

HTML has an <input type="range">, which is, you could argue, the simplest type of proportion slider. Wherever the thumb of that slider ends up could represent a proportion of whatever is before and whatever is after it (using the value and max attributes). Getting fancier, it’s possible to build a multi-thumb slider. But we’ve got another thing in mind today… a proportion slider with multiple thumbs, and sections that cannot overlap.

Here’s what we’ll be building today:

This is a slider, but not just any slider. It’s a proportion slider. Its various sections must all add up to 100% and the user can adjust the different sections in it.

Why would you need such a thing ? 

Maybe you want to build a budget app where a slider like this would consist of your various planned expenses:

I needed something like this to make an anime movie suggestions platform. While researching UX patterns, I noticed that other anime movie suggestion websites have this sort of thing where you select tags to get movie recommendations. That’s cool and all, but how much cooler would it be to add weight to those tags so that recommendations for a tag with a larger weight are prioritized over other tags with lighter weights. I looked around for other ideas, but didn’t find much.

So I built this! And believe it or not, making it is not that complicated. Let me take you through the steps.

The static slider

We’ll be building this in React and TypeScript. That’s not required though. The concepts should port to vanilla JavaScript or any other framework.

Let’s make this slider using dynamic data for the start. We’ll keep the different sections in a variable array with the name and color of each section. 

const _tags = [
  {
    name: "Action",
    color: "red"
  },
  {
    name: "Romance",
    color: "purple"
  },
  {
    name: "Comedy",
    color: "orange"
  },
  {
    name: "Horror",
    color: "black"
  }
];

The width of each tag section is controlled by an array of percentages that add up to 100%. This is done by using the Array.Fill() method to initialize the state of each width:

const [widths, setWidths] = useState<number[]>(new Array(_tags.length).fill(100 / _tags.length))

Next, we’ll create a component to render a single tag section:

interface TagSectionProps {
  name: string
  color: string
  width: number
}


const TagSection = ({ name, color, width }: TagSectionProps) => {
  return <div
    className='tag'
    style={{ ...styles.tag, background: color, width: width + '%' }}
>
    <span style={styles.tagText}>{name}</span>
   <div
     style={styles.sliderButton}
     className='slider-button'>        
     <img src={"https://assets.codepen.io/576444/slider-arrows.svg"} height={'30%'} />
    </div>
  </div >
}

Then we’ll render all of the sections by mapping through the _tags array and return the TagSection component we created above:

const TagSlider = () => {
  const [widths, setWidths] = useState<number[]>((new Array(_tags.length).fill(100 / _tags.length)))
  return <div
    style={{
      width: '100%',
      display: 'flex'
    }}>
    {
    _tags.map((tag, index) => <TagSection
      width={widths[index]}
      key={index}
      name={tag.name}
      color={tag.color}
    />)
    }
  </div>
}

To make rounded borders and hide the last slider button, let’s the :first-of-type and :last-of-type pseudo-selectors in CSS:

.tag:first-of-type {
  border-radius: 50px 0px 0px 50px;
}
.tag:last-of-type {
  border-radius: 0px 50px 50px 0px;
}
.tag:last-of-type>.slider-button {
  display:none !important;
}

Here’s where we are so far. Note the slider handles don’t do anything yet! We’ll get to that next.

Adjustable slider sections

We want the slider sections to adjust move when the slider buttons are dragged with either a mouse cursor or touch, We’ll do that by making sure the section width changes correspond to how much the slider buttons have been dragged. That requires us to answer a few questions:

  1. How do we get the cursor position when the slider button is clicked?
  2. How do we get the cursor position while the slider button is being dragged?
  3. How can we make the width of the tag section correspond to how much the tag section button has been dragged ?

One by one…

How do we get the cursor position when the slider button is clicked?

Let’s add an onSliderSelect event handler to the TagSectionProps interface:

interface TagSectionProps {
  name: string;
  color: string;
  width: number;
  onSliderSelect: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
}

The onSliderSelect event handler is attached to the onPointerDown event of the TagSection component:

const TagSection = ({
  name,
  color,
  width,
  onSliderSelect // Highlight
}: TagSectionProps) => {
  return (
    <div
      className="tag"
      style={{
        ...styles.tag,
        background: color,
        width: width + "%"
        }}
    >
      <span style={styles.tagText}>{name}</span>
      <span style={{ ...styles.tagText, fontSize: 12 }}>{width + "%"}</span>
  
      <div
        style={styles.sliderButton}
        onPointerDown={onSliderSelect}
        className="slider-button"
      >
      <img src={"https://animesonar.com/slider-arrows.svg"} height={"30%"} />
      </div>
    </div>
  );
};

We use onPointerDown instead of onMouseDown to catch both mouse and touch screen events. Here’s more information about that. 

We’re using e.pageX in the onSliderSelect prop function to get the cursor’s position when the slider’s button has been clicked:

<TagSection
  width={widths[index]}
  key={index}
  name={tag.name}
  onSliderSelect={(e) => {
    const startDragX = e.pageX;
  }}
/>

One down!

How do we get the cursor position while the slider button is being dragged?

Now we need to add an event listener to listen for the drag events, pointermove and touchmove. We’ll use these events to cover mouse cursor and touch movements. The section widths need to stop updating once the user’s finger raises from the screen (thus ending the drag): 

window.addEventListener("pointermove", resize);
window.addEventListener("touchmove", resize);


const removeEventListener = () => {
  window.removeEventListener("pointermove", resize);
  window.removeEventListener("touchmove", resize);
}


const handleEventUp = (e: Event) => {
  e.preventDefault();
  document.body.style.cursor = "initial";
  removeEventListener();
}


window.addEventListener("touchend", handleEventUp);
window.addEventListener("pointerup", handleEventUp);

The resize function gives the X coordinate of the cursor while the slider button is being dragged:

const resize = (e: MouseEvent & TouchEvent) => {
  e.preventDefault();
  const endDragX = e.touches ? e.touches[0].pageX : e.pageX
}

When the resize function is triggered by a touch event, e.touches is an array value — otherwise, it’s null, in which case endDragX takes the value of e.pageX

How can we make the width of the tag section correspond to how much the tag section button has been dragged?

To change the width percentages of the various tag sections, let’s get the distance the cursor moves in relation to the entire width of the slider as a percentage. From there, we’ll assign that value to the tag section.

First, we need to get the refof TagSlider using React’s useRef hook:

const TagSlider = () => {
const TagSliderRef = useRef<HTMLDivElement>(null);
  // TagSlider
  return (
    <div
      ref={TagSliderRef}
// ...

Now let’s figure out the width of the slider by using its reference to get the offsetWidth property, which returns the layout width of an element as an integer:

onSliderSelect={(e) => {
  e.preventDefault();
  document.body.style.cursor = 'ew-resize';


  const startDragX = e.pageX;
  const sliderWidth = TagSliderRef.current.offsetWidth;
}};

Then we calculate the percentage distance the cursor moved relative to the entire slider:

const getPercentage = (containerWidth: number, distanceMoved: number) => {
  return (distanceMoved / containerWidth) * 100;
};


const resize = (e: MouseEvent & TouchEvent) => {
  e.preventDefault();
  const endDragX = e.touches ? e.touches[0].pageX : e.pageX;
  const distanceMoved = endDragX - startDragX;
  const percentageMoved = getPercentage(sliderWidth, distanceMoved);
}

Finally, we can assign the newly calculated section width to its index on the _widths state variable:

const percentageMoved = getPercentage(sliderWidth, distanceMoved);
const _widths = widths.slice();
const prevPercentage = _widths[index];
const newPercentage = prevPercentage + percentageMoved


_widths[index] = newPercentage;
setWidths(_widths);

But this isn’t the end! The other sections aren’t changing widths and the percentages can wind up being negative or adding up to more than 100%. Not to mention, the sum of all section widths isn’t always equal to 100% because we haven’t applied a restriction that prevents the overall percentage from changing.

Fixing up the other sections

Let’s make sure the width of one section changes when the section next to it changes.

const nextSectionNewPercentage = percentageMoved < 0 
  ? _widths[nextSectionIndex] + Math.abs(percentageMoved)
  : _widths[nextSectionIndex] - Math.abs(percentageMoved)

This has the effect of reducing the width of the neighboring section if the section increases and vice-versa. We can even shorten it:

const nextSectionNewPercentage = _widths[nextSectionIndex] - percentageMoved

Adjusting a section percentage should only affect its neighbor to the right. This means that the maximum value of a given section maximum percentage should be its width plus the width of its neighbor width when it’s allowed to take up the entire neighbor’s space.

We can make that happen by calculating a maximum percentage value:

const maxPercent = widths[index] + widths[index+1]

To prevent negative width values, let’s restrict the widths to values greater than zero but less than the max percentage: 

const limitNumberWithinRange = (value: number, min: number, max: number):number => {
  return Math.min(Math.max(value,min),max)
}

The limitNumberWithinRange function both prevents negative values and instances where the sum of sections results ina value higher than the maximum percentage. (Hat tip to this StavkOverflow thread.)

We can use this function for the width of the current section and its neighbor:

const currentSectionWidth = limitNumberWithinRange(newPercentage, 0, maxPercent)
_widths[index] = currentSectionWidth


const nextSectionWidth = limitNumberWithinRange(nextSectionNewPercentage, 0, maxPercent);
_widths[nextSectionIndex] = nextSectionWidth;

Extra touches

Right now, the slider calculates the width of each section as a percentage of the entire container to some crazy decimal. That’s super precise, but not exactly useful for this sort of UI. If we want to work with whole numbers instead of decimals, we can do something like this:

const nearestN = (N: number, number: number) => Math.ceil(number / N) * N;
const percentageMoved = nearestN(1, getPercentage(sliderWidth, distanceMoved))

This function approximates the second parameter’s value to the nearest N (specified by the first parameter). Setting  N to 1 like the this example has the effect of making the percentage change in whole numbers instead of tiny incremental decimals.

Another nice to touch is consider additional handling for sections with a zero percentage value. Those should probably be removed from the slider altogether since they no longer take up any proportion of the overall width. We can stop listening for events on those sections:

if (tags.length > 2) {
  if (_widths[index] === 0) {
     _widths[nextSectionIndex] = maxPercent;
    _widths.splice(index, 1);
    setTags(tags.filter((t, i) => i !== index));
    removeEventListener();
  }
  if (_widths[nextSectionIndex] === 0) {
    _widths[index] = maxPercent;
    _widths.splice(nextSectionIndex, 1);
    setTags(tags.filter((t, i) => i !== nextSectionIndex));
    removeEventListener();
  }
}

Voila!

Here’s the final slider:

The post Let’s Make a Multi-Thumb Slider That Calculates The Width Between Thumbs appeared first on CSS-Tricks.

CSS-Only Carousel

It's kind of amazing how far HTML and CSS will take you when building a carousel/slideshow.

  1. Setting some boxes in a horizontal row with flexbox is easy.
  2. Showing only one box at a time with overflow and making it swipable with -webkit-overflow-scrolling is easy.
  3. You can make the "slides" line up nicely with scroll-snap-type.
  4. A couple of #jump-links is all you need to make navigation for it, which you can make all nice and smooth with scroll-behavior.

See the Pen
Real Simple Slider
by Chris Coyier (@chriscoyier)
on CodePen.

Christian Schaefer has taken it a little further with next and previous buttons, plus an auto-play feature that stops playing once interaction starts.

See the Pen
A CSS-only Carousel Slider
by Christian Schaefer (@Schepp)
on CodePen.

About that auto-play thing — it's a bonafide CSS trick:

  1. First I slowly offset the scroll snap points to the right, making the scroll area follow along due to being snapped to them.
  2. After having scrolled the width of a whole slide, I deactivate the snapping. The scroll area is now untied from the scroll snap points.
  3. Now I let the scroll snap points jump back to their initial positions without them "snap-dragging" the scroll area back with them
  4. Then I re-engage the snapping which now lets the scroll area snap to a different snap point 🤯

Cool.

JavaScript-powered slideshows (e.g. with Flickty) can be real nice, too. There is just something neat about getting it done with so little code.

See the Pen
Flickity - wrapAround
by Dave DeSandro (@desandro)
on CodePen.

The post CSS-Only Carousel appeared first on CSS-Tricks.

Multi-Thumb Sliders: Particular Two-Thumb Case

This is a concept I first came across a few years back when Lea Verou wrote an article on it. Multi-range sliders have sadly been removed from the spec since, but something else that has happened in the meanwhile is that CSS got better — and so have I, so I recently decided to make my own 2019 version.

In this two-part article, we'll go through the how, step-by-step, first building an example with two thumbs, then identify the issues with it. We'll solve those issues, first for the two-thumb case then, in part two, come up with a better solution for the multi-thumb case.

Note how the thumbs can pass each other and we can have any possible order, with the fills in between the thumbs adapting accordingly. Surprisingly, the entire thing is going to require extremely little JavaScript.

Article Series:

  1. Multi-Thumb Sliders: Particular Two-Thumb Case (This Post)
  2. Multi-Thumb Sliders: General Case (Coming Tomorrow!)

Basic structure

We need two range inputs inside a wrapper. They both have the same minimum and maximum value (this is very important because nothing is going to work properly otherwise), which we set as custom properties on the wrapper (--min and --max). We also set their values as custom properties (--a and --b).

- let min = -50, max = 50
- let a = -30, b = 20;

.wrap(style=`--a: ${a}; --b: ${b}; --min: ${min}; --max: ${max}`)
  input#a(type='range' min=min value=a max=max)
  input#b(type='range' min=min value=b max=max)

This generates the following markup:

<div class='wrap' style='--a: -30; --b: 20; --min: -50; --max: 50'>
  <input id='a' type='range' min='-50' value='-30' max='50'/>
  <input id='b' type='range' min='-50' value='20' max='50'/>
</div>

Accessibility considerations

We have two range inputs and they should probably each have a <label>, but we want our multi-thumb slider to have a single label. How do we solve this issue? We can make the wrapper a <fieldset>, use its <legend> to describe the entire multi-thumb slider, and have a <label> that's only visible to screen readers for each of our range inputs. (Thanks to Zoltan for this great suggestion.)

But what if we want to have a flex or grid layout on our wrapper? That's something we probably want, as the only other option is absolute positioning and that comes with its own set of issues. Then we run into a Chromium issue where <fieldset> cannot be a flex or grid container.

To go around this, we use the following ARIA equivalent (which I picked up from this post by Steve Faulkner):

- let min = -50, max = 50
- let a = -30, b = 20;

.wrap(role='group' aria-labelledby='multi-lbl' style=`--a: ${a}; --b: ${b}; --min: ${min}; --max: ${max}`)
  #multi-lbl Multi thumb slider:
  label.sr-only(for='a') Value A:
  input#a(type='range' min=min value=a max=max)
  label.sr-only(for='b') Value B:
  input#b(type='range' min=min value=b max=max)

The generated markup is now:

<div class='wrap' role='group' aria-labelledby='multi-lbl' style='--a: -30; --b: 20; --min: -50; --max: 50'>
  <div id='multi-lbl'>Multi thumb slider:</div>
  <label class='sr-only' for='a'>Value A:</label>
  <input id='a' type='range' min='-50' value='-30' max='50'/>
  <label class='sr-only' for='b'>Value B:</label>
  <input id='b' type='range' min='-50' value='20' max='50'/>
</div>

If we set an aria-label or an aria-labelledby attribute on an element, we also need to give it a role.

Basic styling

We make the wrapper a middle-aligned grid with two rows and one column. The bottom grid cell gets the dimensions we want for the slider, while the top one gets the same width as the slider, but can adjust its height according to the group label's content.

$w: 20em;
$h: 1em;

.wrap {
  display: grid;
  grid-template-rows: max-content $h;
  margin: 1em auto;
  width: $w;
}

To visually hide the <label> elements, we absolutely position them and clip them to nothing:

.wrap {
  // same as before
  overflow: hidden; // in case <label> elements overflow
  position: relative;
}

.sr-only {
  position: absolute;
  clip-path: inset(50%);
}

Some people might shriek about clip-path support, like how using it cuts out pre-Chromium Edge and Internet Explorer, but it doesn't matter in this particular case! We're getting to the why behind that in a short bit.

We place the sliders, one on top of the other, in the bottom grid cell:

input[type='range'] {
  grid-column: 1;
  grid-row: 2;
}

See the Pen by thebabydino (@thebabydino) on CodePen.

We can already notice a problem however: not only does the top slider track show up above the thumb of the bottom one, but the top slider makes it impossible for us to even click and interact with the bottom one using a mouse or touch.

In order to fix this, we remove any track backgrounds and borders and highlight the track area by setting a background on the wrapper instead. We also set pointer-events: none on the actual <input> elements and then revert to auto on their thumbs.

@mixin track() {
  background: none; /* get rid of Firefox track background */
  height: 100%;
  width: 100%;
}

@mixin thumb() {
  background: currentcolor;
  border: none; /* get rid of Firefox thumb border */
  border-radius: 0; /* get rid of Firefox corner rounding */
  pointer-events: auto; /* catch clicks */
  width: $h; height: $h;
}

.wrap {
  /* same as before */
  background: /* emulate track with wrapper background */ 
    linear-gradient(0deg, #ccc $h, transparent 0);
}

input[type='range'] {
  &::-webkit-slider-runnable-track, 
  &::-webkit-slider-thumb, & { -webkit-appearance: none; }
  
  /* same as before */
  background: none; /* get rid of white Chrome background */
  color: #000;
  font: inherit; /* fix too small font-size in both Chrome & Firefox */
  margin: 0;
  pointer-events: none; /* let clicks pass through */
  
  &::-webkit-slider-runnable-track { @include track; }
  &::-moz-range-track { @include track; }
  
  &::-webkit-slider-thumb { @include thumb; }
  &::-moz-range-thumb { @include thumb; }
}

Note that we've set a few more styles on the input itself as well as on the track and thumb in order to make the look consistent across the browsers that support letting clicks pass through the actual input elements and their tracks, while allowing them on the thumbs. This excludes pre-Chromium Edge and IE, which is why we haven't included the -ms- prefix — there's no point styling something that wouldn't be functional in these browsers anyway. This is also why we can use clip-path to hide the <label> elements.

If you'd like to know more about default browser styles in order to understand what's necessary to override here, you can check out this article where I take an in-depth look at range inputs (and where I also detail the reasoning behind using mixins here).

See the Pen by thebabydino (@thebabydino) on CodePen.

Alright, we now have something that looks functional. But in order to really make it functional, we need to move on to the JavaScript!

Functionality

The JavaScript is pretty straightforward. We need to update the custom properties we've set on the wrapper. (For an actual use case, they'd be set higher up in the DOM so that they're also inherited by the elements whose styles that depend on them.)

addEventListener('input', e => {
  let _t = e.target;
  _t.parentNode.style.setProperty(`--${_t.id}`, +_t.value)
}, false);

See the Pen by thebabydino (@thebabydino) on CodePen.

However, unless we bring up DevTools to see that the values of those two custom properties really change in the style attribute of the wrapper .wrap, it's not really obvious that this does anything. So let's do something about that!

Showing values

Something we can do to make it obvious that dragging the thumbs actually changes something is to display the current values. In order to do this, we use an output element for each input:

- let min = -50, max = 50
- let a = -30, b = 20;

.wrap(role='group' aria-labelledby='multi-lbl' style=`--a: ${a}; --b: ${b}; --min: ${min}; --max: ${max}`)
  #multi-lbl Multi thumb slider:
  label.sr-only(for='a') Value A:
  input#a(type='range' min=min value=a max=max)
  output(for='a' style='--c: var(--a)')
  label.sr-only(for='b') Value B:
  input#b(type='range' min=min value=b max=max)
  output(for='b' style='--c: var(--b)')

The resulting HTML looks as follows:

<div class='wrap' role='group' aria-labelledby='multi-lbl' style='--a: -30; --b: 20; --min: -50; --max: 50'>
  <div id='multi-lbl'>Multi thumb slider:</div>
  <label class='sr-only' for='a'>Value A:</label>
  <input id='a' type='range' min='-50' value='-30' max='50'/>
  <output for='a' style='--c: var(--a)'></output>
  <label class='sr-only' for='b'>Value B:</label>
  <input id='b' type='range' min='-50' value='20' max='50'/>
  <output for='b' style='--c: var(--b)'></output>
</div>

We display the values in an ::after pseudo-element using a little counter trick:

output {
  &::after {
    counter-reset: c var(--c);
    content: counter(c);
  }
}

See the Pen by thebabydino (@thebabydino) on CodePen.

It's now obvious these values change as we drag the sliders, but the result is ugly and it has messed up the wrapper background alignment, so let's add a few tweaks! We could absolutely position the <output> elements, but for now, we simply squeeze them in a row between the group label and the sliders:

.wrap {
  // same as before
  grid-template: repeat(2, max-content) #{$h}/ 1fr 1fr;
}

[id='multi-lbl'] { grid-column: 1/ span 2 }

input[type='range'] {
  // same as before
  grid-column: 1/ span 2;
  grid-row: 3;
}

output {
  grid-row: 2;
  
  &:last-child { text-align: right; }
  
  &::after {
    content: '--' attr(for) ': ' counter(c) ';'
    counter-reset: c var(--c);
  }
}

Much better!

See the Pen by thebabydino (@thebabydino) on CodePen.

Setting separate :focus styles even gives us something that doesn't look half bad, plus allows us to see which value we're currently modifying.

input[type='range'] {
  /* same as before */
  z-index: 1;

  &:focus {
    z-index: 2;
    outline: dotted 1px currentcolor;
    
    &, & + output { color: darkorange }
  }
}

See the Pen by thebabydino (@thebabydino) on CodePen.

All we need now is to create the fill between the thumbs.

The tricky part

We can recreate the fill with an ::after pseudo-element on the wrapper, which we place on the bottom grid row where we've also placed the range inputs. This pseudo-element comes, as the name suggests, after the inputs, but it will still show up underneath them because we've set positive z-index values on them. Note that setting the z-index works on the inputs (without explicitly setting their position to something different from static) because they're grid children.

The width of this pseudo-element should be proportional to the difference between the higher input value and the lower input value. The big problem here is that they pass each other and we have no way of knowing which has the higher value.

First approach

My first idea on how to solve this was by using width and min-width together. In order to better understand how this works, consider that we have two percentage values, --a and --b, and we want to make an element's width be the absolute value of the difference between them.

Either one of the two values can be the bigger one, so we pick an example where --b is bigger and an example where --a is bigger:

<div style='--a: 30%; --b: 50%'><!-- first example, --b is bigger --></div>
<div style='--a: 60%; --b: 10%'><!-- second example, --a is bigger --></div>

We set width to the second value (--b) minus the first (--a) and min-width to the first value (--a) minus the second one (--b).

div {
  background: #f90;
  height: 4em;
  min-width: calc(var(--a) - var(--b));
  width: calc(var(--b) - var(--a));
}

If the second value (--b) is bigger, then the width is positive (which makes it valid) and the min-width negative (which makes it invalid). That means the computed value is the one set via the width property. This is the case in the first example, where --b is 70% and --a is 50%. That means the width computes to 70% - 50% = 20%, while the min-width computes to 50% - 70% = -20%.

If the first value is bigger, then the width is negative (which makes it invalid) and the min-width is positive (which makes it valid), meaning the computed value is that set via the min-width property. This is the case in the second example, where --a is 80% and --b is 30%, meaning the width computes to 30% - 80% = -50%, while the min-width computes to 80% - 30% = 50%.

See the Pen by thebabydino (@thebabydino) on CodePen.

Applying this solution for our two thumb slider, we have:

.wrap {
  /* same as before */
  --dif: calc(var(--max) - var(--min));
  
  &::after {
    content: '';
    background: #95a;
    grid-column: 1/ span 2;
    grid-row: 3;
    min-width: calc((var(--a) - var(--b))/var(--dif)*100%);
    width: calc((var(--b) - var(--a))/var(--dif)*100%);
  }
}

In order to represent the width and min-width values as percentages, we need to divide the difference between our two values by the difference (--dif) between the maximum and the minimum of the range inputs and then multiply the result we get by 100%.

See the Pen by thebabydino (@thebabydino) on CodePen.

So far, so good... so what?

The ::after always has the right computed width, but we also need to offset it from the track minimum by the smaller value and we can't use the same trick for its margin-left property.

My first instinct here was to use left, but actual offsets don't work on their own. We'd have to also explicitly set position: relative on our ::after pseudo-element in order to make it work. I felt kind of meh about doing that, so I opted for margin-left instead.

The question is what approach can we take for this second property. The one we've used for the width doesn't work because there is no such thing as min-margin-left.

A min() function is now in the CSS spec, but at the time when I coded these multi-thumb sliders, it was only implemented by Safari (it has since landed in Chrome as well). Safari-only support was not going to cut it for me since I don't own any Apple device or know anyone in real life who does... so I couldn't play with this function! And not being able to come up with a solution I could actually test meant having to change the approach.

Second approach

This involves using both of our wrapper's (.wrap) pseudo-elements: one pseudo-element's margin-left and width being set as if the second value is bigger, and the other's set as if the first value is bigger.

With this technique, if the second value is bigger, the width we're setting on ::before is positive and the one we're setting on ::after is negative (which means it's invalid and the default of 0 is applied, hiding this pseudo-element). Meanwhile, if the first value is bigger, then the width we're setting on ::before is negative (so it's this pseudo-element that has a computed width of 0 and is not being shown in this situation) and the one we're setting on ::after is positive.

Similarly, we use the first value (--a) to set the margin-left property on the ::before since we assume the second value --b is bigger for this pseudo-element. That means --a is the value of the left end and --b the value of the right end.

For ::after, we use the second value (--b) to set the margin-left property, since we assume the first value --a is bigger this pseudo-element. That means --b is the value of the left end and --a the value of the right end.

Let's see how we put it into code for the same two examples we previously had, where one has --b bigger and another where --a is bigger:

<div style='--a: 30%; --b: 50%'></div>
<div style='--a: 60%; --b: 10%'></div>
div {
  &::before, &::after {
    content: '';
    height: 5em;
  }
  
  &::before {
    margin-left: var(--a);
    width: calc(var(--b) - var(--a));
  }

  &::after {
    margin-left: var(--b);
    width: calc(var(--a) - var(--b));
  }
}

See the Pen by thebabydino (@thebabydino) on CodePen.

Applying this technique for our two thumb slider, we have:

.wrap {
  /* same as before */
  --dif: calc(var(--max) - var(--min));
  
  &::before, &::after {
    grid-column: 1/ span 2;
    grid-row: 3;
    height: 100%;
    background: #95a;
    content: ''
  }
  
  &::before {
    margin-left: calc((var(--a) - var(--min))/var(--dif)*100%);
    width: calc((var(--b) - var(--a))/var(--dif)*100%)
  }
  
  &::after {
    margin-left: calc((var(--b) - var(--min))/var(--dif)*100%);
    width: calc((var(--a) - var(--b))/var(--dif)*100%)
  }
}

See the Pen by thebabydino (@thebabydino) on CodePen.

We now have a nice functional slider with two thumbs. But this solution is far from perfect.

Issues

The first issue is that we didn't get those margin-left and width values quite right. It's just not noticeable in this demo due to the thumb styling (such as its shape, dimensions relative to the track, and being full opaque).

But let's say our thumb is round and maybe even smaller than the track height:

See the Pen by thebabydino (@thebabydino) on CodePen.

We can now see what the problem is: the endlines of the fill don't coincide with the vertical midlines of the thumbs.

This is because of the way moving the thumb end-to-end works. In Chrome, the thumb's border-box moves within the limits of the track's content-box, while in Firefox, it moves within the limits of the slider's content-box. This can be seen in the recordings below, where the padding is transparent, while the content-box and the border are semi-transparent. We've used orange for the actual slider, red for the track and purple for the thumb.

Animated gif. Chrome only moves the thumb within the left and right limits of the track's content-box.
Recording of the thumb motion in Chrome from one end of the slider to the other.

Note that the track's width in Chrome is always determined by that of the parent slider - any width value we may set on the track itself gets ignored. This is not the case in Firefox, where the track can also be wider or narrower than its parent <input>. As we can see below, this makes it even more clear that the thumb's range of motion depends solely on the slider width in this browser.

Animated gif. Firefox moves the thumb within the left and right limits of the actual range input's content-box.
Recording of the thumb motion in Firefox from one end of the slider to the other. The three cases are displayed from top to bottom. The border-box of the track perfectly fits the content-box of the slider horizontally. It's longer and it's shorter).

In our particular case (and, to be fair, in a lot of other cases), we can get away with not having any margin, border or padding on the track. That would mean its content-box coincides to that of the actual range input so there are no inconsistencies between browsers.

But what we need to keep in mind is that the vertical midlines of the thumbs (which we need to coincide with the fill endpoints) move between half a thumb width (or a thumb radius if we have a circular thumb) away from the start of the track and half a thumb width away from the end of the track. That's an interval equal to the track width minus the thumb width (or the thumb diameter in the case of a circular thumb).

This can be seen in the interactive demo below where the thumb can be dragged to better see the interval its vertical midline (which we need to coincide with the fill's endline) moves within.

See the Pen by thebabydino (@thebabydino) on CodePen.

The demo is best viewed in Chrome and Firefox.

The fill width and margin-left values are not relative to 100% (or the track width), but to the track width minus the thumb width (which is also the diameter in the particular case of a circular thumb). Also, the margin-left values don't start from 0, but from half a thumb width (which is a thumb radius in our particular case).

$d: .5*$h; // thumb diameter
$r: .5*$d; // thumb radius
$uw: $w - $d; // useful width

.wrap {
  /* same as before */
  --dif: calc(var(--max) - var(--min));
	
  &::before {
    margin-left: calc(#{$r} + (var(--a) - var(--min))/var(--dif)*#{$uw});
    width: calc((var(--b) - var(--a))/var(--dif)*#{$uw});
  }
  
  &::after {
    margin-left: calc(#{$r} + (var(--b) - var(--min))/var(--dif)*#{$uw});
    width: calc((var(--a) - var(--b))/var(--dif)*#{$uw});
  }
}

Now the fill starts and ends exactly where it should, along the midlines of the two thumbs:

See the Pen by thebabydino (@thebabydino) on CodePen.

This one issue has been taken care of, but we still have a way bigger one. Let's say we want to have more thumbs, say four:

Animated gif. Shows a slider with four thumbs which can pass each other and be in any order, while the fills are always between the two thumbs with the two smallest values and between the two thumbs with the two biggest values, regardless of their order in the DOM.
An example with four thumbs.

We now have four thumbs that can all pass each other and they can be in any order that we have no way of knowing. Moreover, we only have two pseudo-elements, so we cannot apply the same techniques. Can we still find a CSS-only solution?

Well, the answer is yes! But it means scrapping this solution and going for something different and way more clever — in part two of this article!

Article Series:

  1. Multi-Thumb Sliders: Particular Two-Thumb Case (This Post)
  2. Multi-Thumb Sliders: General Case (Coming Tomorrow!)

The post Multi-Thumb Sliders: Particular Two-Thumb Case appeared first on CSS-Tricks.

Kioken Blocks Partners with Gutenslider Plugin

Kioken Blocks creator Onur Oztaskiran is teaming up with Niklas Jurij Plessing, a Berlin-based developer and author of the Gutenslider plugin, to improve both products under the same roof. Oztaskiran said the partnership is not an acquisition but rather a unification of efforts that may eventually result in combining under the same name.

“Our short term plan is to work on each other’s plugins to improve them according to our individual areas of expertise (me in design, marketing and user happiness, him in development and more technical stuff where I fall short), and then fully collaborate on plugins and themes,” Oztaskiran said.

Gutenslider will remain a standalone plugin and will not be merged into Kioken Blocks. Both products will share similar resources in terms of functionality and support. The team plans to work on porting their products to be ready for WordPress.org’s upcoming Block Directory. Pro users of Kioken Blocks will be able to use the pro functionalities of Gutenslider and the team plans to make Gutenslider work like an extension to Kioken Blocks.

“Gutenslider is pretty extensive at it is, and we thought it deserves to keep going as a standalone block and plugin, since it will be also available in the upcoming Block Directory for Gutenberg,” Oztaskiran said. “We will handle it as another product even though it is under the same roof as Kioken Blocks. We will continue adding new features to that block and improve the experience and Kioken Blocks will gain new blocks as well, but not as extensive as Gutenslider. There’s a possibility we could rename the block but that’s not the case at the moment.”

Oztaskiran said he sees a lot of possibilities in Gutenslider, because it is not just an image and video slider but capable of adding different types of block content on top of the slides, such as paragraphs, headings, images, galleries, products, and more.

“Since the future of Gutenberg, as we see it, is going to be shaped around the Block Directory in the editor, our plan is focusing more blocks on that directory, with the Kioken Blocks as a builder on top of them as a plugin,” Oztaskiran said. “The final goal is building an ecosystem for WordPress users who have adopted the new editor – products, plugins and themes with a streamlined interface and experience. Dev partnerships are the first step of it.”

Oztaskiran could not confirm if the product catalog will be combining under one company name. The final decision has not yet been made but he said it is likely that they will combine under the Kioken branding sometime in the future for marketing their WordPress products.

React Slider with Parallax Hover Effects

Recently I experimented with building a content slider (or carousel, if that’s your fancy) using React. I wanted to create some unique position-based cursor effects when the user hovers over the active slide. This eventually led to the parallax effect you’ll see in the final demo.

This post will dive into the details of the slider’s components, the dynamic CSS variables used for the parallax hover effect, and some of the other properties that brought this project to life.

See the Pen React Slider w/ Hover Effect by Ryan Mulligan (@hexagoncircle) on CodePen.light

Component Setup

This React slider consists of three components: SliderSlide, and SliderControl. The SliderControl houses the button template used for the previous and next arrow controls. The Slider is the parent component that contains the methods for transitioning slides. Inside the Slider render template, an array of slide objects is iterated over and each slide’s data set is returned within a Slide child component using the map() method:

{slides.map(slide => {
  return (
    <Slide
      key={slide.index}
      slide={slide}
      current={current}
      handleSlideClick={this.handleSlideClick}
    />
  )
})}

Each of these rendered slides has the following properties:

  • A unique key (learn more about keys in React here). This key grabs index from the slide’s data.
  • A slide property equal to the slide object so the component can access that set of data.
  • The current property grabs the Slider’s current state value and controls the previous, current, and next classes being set on each slide.
  • handleSlideClick points to the Slider method of the same name to update the current value to the clicked slide’s index. This will animate the clicked slide into view.

Updating slide classes

The Slide element has additional classes set based on the current slide.

if (current === index) classNames += ' slide--current'
else if (current - 1 === index) classNames += ' slide--previous'
else if (current + 1 === index) classNames += ' slide--next'

In the code above, when current equals a slide’s index, that slide becomes active and is given a current class name. Adjacent sibling slides get previous and next class names. By adding these classes to their respective slides, unique hover styles can be applied.

Animation of previous and next slides with cursor changing as elements are hovered

On hover, the cursor changes based on the direction of the slide and that hovered element is pulled towards the current slide along the x-axis. As a result, the user receives some additional visual cues when they are interacting with those neighboring slides.

Slide Parallax Hover Effect

Now for the fun part! The Slide component contains methods that cast parallax magic. The onMouseMove event attribute is using the handleMouseMove method to update the x and y values as the user hovers over the slide. When the cursor is moved off of the slide, onMouseLeave calls handleMouseLeave to reset the x and y values and transition the slide elements back into place.

The x and y coordinates are calculated by finding the user’s cursor in the viewport and where it’s hovering in relation to the center of the slide element. Those coordinate values are assigned to CSS variables (--x and --y) that are then used in transforms to move the child elements around in the slide. In the following pen, click on the “display coordinates” checkbox and hover over the slide to see how the x and y values update to reflect your cursor’s position and movement.

See the Pen Cursor Movement Hover Effect by Ryan Mulligan (@hexagoncircle) on CodePen.light

The Parallax CSS

Let’s take a look at the CSS (Sass) being applied to some of these slide elements:

.slide:hover .slide__image-wrapper {
  --x: 0;
  --y: 0;
  --d: 50
		
  transform: 
    scale(1.025)
    translate(
      calc(var(--x) / var(--d) * 1px),
      calc(var(--y) / var(--d) * 1px)
  );
}

The slide__image-wrapper has overflow: hidden set so that the image can move beyond its wrapper container and hide some of itself beyond the wrapper boundaries. The wrapper container also has a faster transition-duration than the image. Now these elements animate at different speeds. I combined this with some fancy transform calculations and it developed some fluid, independent transitions.

Calculate those transforms

The translate(x, y) values are computed using the CSS calc() function. On the slide__image-wrapper, the --d property (the divisor) is set to 50, which yields a lower coordinate value and less of a push from the slide’s center. Now check out the slide__image transform:

.slide__image.slide--current {
  --d: 20;
	
  transform:
    translate(
      calc(var(--x) / var(--d) * 1px),
      calc(var(--y) / var(--d) * 1px)
    ); 
}

The divisor is changed to 20 on the slide__image so that the x and y values in the transform are higher and will push the image further away from the center of slide. Finally, the formula is multiplied by one pixel so that a unit gets applied to the value. Parallax achieved!

Try playing around with the --d values in the CSS and watch how the transitions change! Edit on Codepen.

Does it seem like the slide headline and button seem to move ever so slightly in the opposite direction of the image? Indeed they do! To achieve this, I multiplied the translate(x, y) calculations by negative pixel values instead:

.slide__content {
  --d: 60;
	
  transform: 
    translate(
      calc(var(--x) / var(--d) * -1px),
      calc(var(--y) / var(--d) * -1px)
    );
}

Moving the slides

Check out the Slider component render code in the final demo:

See the Pen React Slider w/ Hover Effect by Ryan Mulligan (@hexagoncircle) on CodePen.light

You’ll notice the slider__wrapper element surrounding the slides. This wrapper transitions back and forth along the x-axis as the user interacts with the slider. The values for this transform are set after the current slide’s index is multiplied by the amount of slides divided into 100.  I’ve added this in a variable on line 163 to keep the template a little cleaner:

'transform': `translateX(-${current * (100 / slides.length)}%)

In this example, there are 4 slides. Click the next arrow button or on the second slide (which has an index of 1) and it will pull the wrapper 25% to the left. Click on the third slide (index of 2), do the math (2 x 25), and watch it move the wrapper 50% to the left.

Some other tidbits

These are a few other features I’d like to quickly call out:

  • If a slide isn’t active, the pointer-events property is set to none. I chose to do this to avoid keyboard tab focusing on buttons inside inactive slides.
  • The parallax effect is only being applied to the current slide by declaring transforms when the slide--current class is present. Inactive slides have their own animations and shouldn’t have all that fun hover magic that the active slide has.
  • Images fade in when they are loaded using the imageLoaded method in the Slide component. This helps the initial load of a slide feel smoother instead of its image just popping in. A future iteration of this project will apply lazy loading as well (which is starting to roll out as a native browser feature; very exciting!)

How would you extend or refactor this idea? I’d love to read your thoughts and comments. Leave them below or reach out to me on Twitter.

React Slider with Parallax Hover Effects was written by Ryan Mulligan and published on Codrops.