Product / Card Automatic Swipeable Carousel / Slider for Most of Shopify Theme

{% if youLike === true %} Hit Subscribe {% endif %}

I was able to write an autoplay slider / carousel using scroll. This works with most of Shopify themes (work best in Dawn and Debut theme). 

In this tutorial, we are creating a slider or carousel for a card or product card. I did provide an option to choose between the two. I also included an autoplay option, of course. You can choose from 3 to 7 products per slide, or 3 - 5 cards per slide. I lessen the number of slides in cards since the text will not be visible with more than 5.

I also added an options for dots and number counter. Yes, the counters work.

NOTE: You do not need to download a link nor incorporate a min.js. I was able to create a slick carousel before, see this link but it will not allow me to have multiple slider since both use the same min.js. 

 

 

1. Go to Admin Shopify store > Themes > Actions > Edit Code
2. In Section folder, create a new section, and name it "universal-carousel", then paste the code below.

 {{ 'universal-carousel.css' | asset_url | stylesheet_tag }}

{%- liquid assign products_to_display = section.settings.collection.all_products_count
if section.settings.collection.all_products_count > section.settings.products_count
assign products_to_display = section.settings.products_count
endif
-%}

<div data-carousel-id="carousel-{{section.id}}" class="carouselContainer">
<div class="topWrapper">
{% if section.settings.title != blank %} <h2>{{section.settings.title}}</h2>{% endif %}
{% if section.settings.view_all == true and section.settings.type == 'product'%}
<a href="{{ section.settings.collection.url }}" id="viewAllBtn">
<span>View All</span>
</a>
{% endif %} </div>
<div class="carouselWrapper
{% case section.settings.slide_number %}
{% when 2 %} two
{% when 3 %} three
{% when 4 %} four
{% when 5 %} five
{% when 6 %} six
{% when 7 %} seven
{% endcase %}">
<ul class="carouselBox" id="carouselBox"
data-slide="{{section.settings.slide_number}}"
data-type="{{section.settings.type}}"
data-autoplay="{{section.settings.autoplay}}"
data-interval="{{section.settings.interval | times: 1000 }}"
data-infinite-scroll="{{section.settings.infinite}}"
data-counter-type="{{section.settings.counter_type}}">
{% case section.settings.type %}
{% when "product" %}
{%- for product in section.settings.collection.products limit: section.settings.products_count-%}
<li class="carouselSlider"

id="slide-{% increment %}">
{% assign product_image = product.selected_or_first_available_variant %}
<a class="productBox" href="{{ product.url | within: collection }}" style="text-decoration: none;">
<img class="carouselImage" src="{{ product.featured_image | img_url: 'master'}}"
alt="{{ product.images.alt | escape }}"/>
<div class="detailsText">
<span class = "productTitle"> {{ product.title }}</span>
<span class = "productPrice"> {{ product.price | money }}</span>
</div>
</a>
</li>
{% endfor %}
{% when "card" %}
{% if section.settings.slide_number > 5 %}
{% assign section.settings.slide_number = 4 %}
{% else %}
{% assign slide_number = section.settings.slide_number %}
{% endif %}
{% for block in section.blocks %}
<li class="carouselSlider
{% case section.settings.slide_number %}
{% when 3 %} three
{% when 4 %} four
{% when 5 %} five
{% when 6 %} four
{% when 7 %} four
{% endcase %}"
id="slide-{% increment %}">
<div class="cardWrapper">
{% assign img_url = block.settings.card_image %}
<div class="cardImageWrapper">
<img class="cardImage"
src="{{img_url | img_url: master}}"
srcset="{% case img_url.width %}
{% when >= 375 %}{{ img_url | img_url: '375x' }}375w,
{% when >= 750 %}{{ img_url | img_url: '750x' }}750w,
{% when >= 1100 %}{{ img_url | img_url: '1100x' }}1100w,
{% when >= 1500 %}{{ img_url | img_url: '1500x' }}1500w,
{% when >= 1780 %}{{ img_url | img_url: '1780x' }}1780w,
{% when >= 2000 %}{{ img_url | img_url: '2000x' }}2000w,
{% when >= 3000 %}{{ img_url | img_url: '3000x' }} 3000w,
{% when >= 3840 %}{{ img_url | img_url: '3840x' }}3840w,
{% else %} {{ img_url | img_url: 'master' }}w,
{% endcase %}"
loading="lazy"
alt="{{ img_url.alt | escape }}">
</div>
<h2 class="cardTitle">{{block.settings.title}}</h2>
<div class="cardDescription">{{block.settings.card_description}}</div>
</div>
</li>
{% endfor %}
{% endcase %}
</ul>
</div>
<div class="controlContainer">
<div class="btnContainer">
<button type="button" class="controlWrapper" id="prevSlide">
<span class="controlBtn prevBtn" ></span>
</button>
<ul class="slideDotContainer">
{% case section.settings.counter_type %}
{% when 'dots' %}

{% if section.settings.type == 'card' %}
{% for block in section.blocks %}
<li class="slideDots {% if forloop.first %}activeDot{% endif %}" data-dot-id="{{forloop.index0}}" onclick="showDot(this.dataset.dotId)">
</li>
{% endfor %}
{% endif %}
{% if section.settings.type == 'product' %}
{% for product in section.settings.collection.products limit: section.settings.products_count %}
<li class="slideDots {% if forloop.first %}activeDot{% endif %}" data-dot-id="{{forloop.index0}}" onclick="showDot(this.dataset.dotId)">
</li>
{% endfor %}
{% endif %}

{% when 'number' %}
<span class="activeNumber">{{forloop.index}}</span> / <span>{% if section.settings.type == 'product' %} {{products_to_display}}{% endif %}
{% if section.settings.type == 'card' %}{{section.blocks.size}}{% endif %}</span>
{% endcase %}

</ul>
<button type="button" class="controlWrapper" id="nextSlide" >
<span class="controlBtn nextBtn"></span>
</button>
</div>
{% if section.settings.autoplay == true %}
<button type="button" class="pauseContainer" id="autoplaySlide" onclick = "pauseFx()">
<span class="pauseBtn"></span>
<span class="playBtn"> </span>
</button>
{% endif %}
</div>
</div>

{% schema %}
{
"name": "Universal Carousel",
"settings": [
{
"type": "text",
"id": "title",
"label": "carousel name"
},
{
"type": "select",
"id": "type",
"default": "product",
"label": "type of item",
"options": [
{
"value": "product",
"label": "product"
},
{
"value": "card",
"label": "card"
}
]
},
{
"type": "collection",
"id": "collection",
"label": "Collection"
},
{
"type": "checkbox",
"id": "autoplay",
"default": false,
"label": "Autoplay"
},
{
"type": "checkbox",
"id": "view_all",
"default": false,
"label": "Show view all button (only available in product type)"
},
{
"type": "checkbox",
"id": "infinite",
"default": false,
"label": "Infinite Scroll"
},
{
"type": "range",
"id": "interval",
"label": "interval",
"max": 15,
"min": 1,
"step": 1,
"unit": "s",
"default": 3
},
{
"type": "range",
"id": "slide_number",
"min": 3,
"max": 7,
"label": "Number of slides to display",
"default": 4
},
{
"type": "range",
"id": "products_count",
"min": 4,
"max": 100,
"label": "Number of products to display",
"default": 4
},
{
"type": "select",
"id": "counter_type",
"default": "number",
"label": "type of counter",
"options": [
{
"value": "dots",
"label": "dots"
},
{
"value": "number",
"label": "number"
}
]
}
],
"blocks": [
{
"type": "card",
"name": "card",
"settings": [
{
"type": "image_picker",
"id": "card_image",
"label": "card image"
},
{
"type": "text",
"id": "title",
"label": "title"
},
{
"type": "richtext",
"id": "card_description",
"label": "description",
"default": "<p>Write your drescription here</p>"
}
]
}
],
"presets": [
{
"name": "Universal Carousel"
}
]
}
{% endschema %}

3. Next, we need to add a file for our CSS. Go to Asset folder, and click "add a new asset", then click "create a blank file". Make sure you have .css file dropdown on the left. We will name this CSS file "universal-carousel".

4. Open the newly create CSS file and paste the code below

 :root {
--text-color: #232b2b;
--bg-color: #dee2e6;
--card-bg-color: rgba(25, 181, 254, .3);
--box-bg-color: skyblue;
--button-color: rgba(0, 0, 0);
--button-bg-color: rgba(0,0,0,.3);
--button--dark-bg-color: rgba(0,0,0,.6);
}
.carouselContainer {
height: 45vw;
position: relative;
display: flex;
flex-direction: column;
justify-items: center;
align-items: center;
margin: 50px;
}
.topWrapper {
display: flex;
justify-content: space-between;
width: 100%;
align-items: center;
}
.carouselWrapper {
width: 100%;
height: 100%;
position: relative;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: stretch;
}
.carouselBox{
width: 100%;
height: 100%;
padding: 3.5% 0;
display: flex;
align-items: center;
margin: 0;
flex-wrap: inherit;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
-webkit-overflow-scrolling: touch;
position: absolute;
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
.carouselBox::-webkit-scrollbar {
display: none;
}
.carouselSlider {
height: 100%;
width: 25%;
object-fit: contain;
aspect-ratio: 1;
display: flex;
flex-direction: column;
justify-content: center;
padding-right: 2%;
}
/*===== PRODUCT ==== */
.productBox {
background: var(--bg-color);
}
.productBox:hover {
background: var(--box-bg-color);
transform: scale(1.2);
}
.carouselImage {
width: 100%;
aspect-ratio: 1;
}
.detailsText {
display: flex;
flex-direction: column;
align-items: flex-start;
line-height: 1.2;
font-size: clamp(10px, 1.5vw ,18px);
color: var(--text-color);
white-space: nowrap;
overflow: hidden;
padding: 0 5% 3%;
}
.productPrice {
padding-top: 1%;
font-size: clamp(10px, 2vw ,18px);
font-weight: 600;
}

/* === NUMBER OF SLIDES === */
.three, .four {height: 100%}
.five {height: 60%}
.six, .seven {height: 50%}
.three .carouselSlider {width: 33.5%}
.four .carouselSlider { width: 25%}
.five .carouselSlider { width: 20%}
.six .carouselSlider { width: 16.7%}
.seven .carouselSlider { width: 14.3%}
:is(.six, .seven) .detailsText {font-size: clamp(8px, 1vw ,12px);}

@media screen and (max-width: 800px) {
.carouselContainer {
height: 50vw;
}
.carouselWrapper {
height: 100% !important;
}
.carouselSlider {
height: 100%;
width: 33.5% !important;
}
}
@media screen and (max-width: 400px) {
.carouselContainer {
height: 75vw;
margin: 4%;
}
.carouselBox {
padding: 1.5% 0;
}
.carouselSlider {
padding-left: 4%;
width: 50% !important;
}
.detailsText {
font-size: 2.5vw;
}
.controlContainer:hover {
transform: scale(1.1);
}
#viewAllBtn {
line-height: 1.2;
}
}

/*===== CARD ==== */
.cardWrapper {
width: 100%;
height: 100%;
position: relative;
padding: 5%;
background-color: var(--card-bg-color);
overflow: hidden;
}
.cardWrapper:hover .cardImage {
transform: scale(1.2);
}
.cardWrapper:hover .cardImageWrapper {
overflow: visible;
transform: scale(1.2);
}
.cardImageWrapper {
position: relative;
padding-bottom: 65%;
width: 100%;
overflow: hidden;
grid-row: 1 / 2;
}
.cardImage {
width: 100%;
position: absolute;
}
.cardTitle {
width: 100%;
margin: 5% 0;
text-align: center;
font-weight: 600;
font-size: clamp(9px, 1.5vw, 19px);
line-height: 1.2;
grid-row: 2 / 3;
}
.cardDescription {
width: 100%;
line-height: 1.2;
grid-row: 3 / 4;
}
.cardDescription p {
font-size: clamp(8px, 1.2vw, 18px);
margin: 0;
text-overflow: ellipsis;
}

/* ====== CONTROLS ===== */
.controlContainer {
display: flex;
justify-content: center;
}
.controlContainer:hover {
transform: scale(1.2);
}
.btnContainer {
height: 30px;
display: flex;
justify-content: space-between;
align-items: center;
color: var(--button-color);
padding: .5%;
}
.controlWrapper {
width: 50px;
height: inherit;
position: relative;
display: flex;
align-items: center;
cursor: pointer;
justify-content: space-evenly;
border: none;
}
.controlBtn {
display: inline-block;
width: 25px;
height: 15px;
background-color: var(--button--dark-bg-color);
border: none;
}
.controlWrapper:hover .controlBtn{
transform: scale(1.2);
background-color: var( --button-color);
}
#nextSlide:disabled .controlBtn {
background-color: var(--button-bg-color);
}
.nextBtn {
-webkit-clip-path: polygon(15% 0, 65% 50%, 15% 100%, 0 85%, 35% 50%, 0 15%);
clip-path: polygon(15% 0, 65% 50%, 15% 100%, 0 85%, 35% 50%, 0 15%);
margin-right: -10px;
}
#prevSlide:disabled .controlBtn {
background-color: var(--button-bg-color);
}
.prevBtn {
-webkit-clip-path: polygon(100% 15%, 65% 50%, 100% 85%, 85% 100%, 35% 50%, 85% 0);
clip-path: polygon(100% 15%, 65% 50%, 100% 85%, 85% 100%, 35% 50%, 85% 0);
margin-left: -10px;
}
.pauseContainer {
position: relative;
width: 30px;
height: 30px;
margin-left: 1rem;
display: flex;
justify-content: center;
align-items: center;
border: none;
}
.pauseBtn {
width: 50%;
height: 50%;
position: absolute;
background-color: var(--button--dark-bg-color);
-webkit-clip-path: polygon(0% 0%, 0% 100%, 40% 100%, 40% 0, 60% 0, 60% 99%, 100% 100%, 100% 0%);
clip-path: polygon(0% 0%, 0% 100%, 40% 100%, 40% 0, 60% 0, 60% 99%, 100% 100%, 100% 0%);
}
.playBtn {
width: 50%;
height: 60%;
display: none;
position: absolute;
background-color: var(--button--dark-bg-color);
-webkit-clip-path: polygon(0 0, 0 100%, 100% 49%);
clip-path: polygon(0 0, 0 100%, 100% 49%);
}
.playBtn:hover {
transform: scale(1.2);
background-color: var( --button-color);
}
.pauseBtn:hover {
transform: scale(1.2);
background-color: var( --button-color);
}

/*=== DOT CONTROL ====*/
.slideDotContainer {
position: relative;
width: 100%;
height: inherit;
margin: 0 .5rem;
display: flex;
padding: 0;
justify-content: space-around;
align-items: center;
flex-wrap: inherit;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
-webkit-overflow-scrolling: touch;
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none;
}
.slideDots:hover {
transform: scale(1.2);
}
.slideDots {
position: relative;
background-color: transparent;
border: 2px solid var(--button--dark-bg-color );
width: 10px;
height: 10px;
border-radius: 50%;
margin: 0 1px;
list-style-type: none;
}
.activeDot {
background-color: var(--button-bg-color);
}

/*=== VIEW ALL CONTROL ====*/
#viewAllBtn {
display: inline-block;
border-radius: 5px;
padding: 5px 10px;
color: white;
font-weight: 600;
box-shadow: 2px 2px 2px black;
background-color: var(--button--dark-bg-color);
text-decoration: none;
text-align: center;
}
#viewAllBtn:hover::after {
content: "Products";
}

#viewAllBtn:active{
box-shadow: none;
transform: translateY(-2%);
}

 

5. Sorry, we are not done yet. In the same Asset folder, create a js file. Add a new asset, click the "create a blank file", from the drop down, click the ".js". Name the file "universal-carousel"

6. Open the "universal-carousel.js" file and paste the code below.

const carousel = document.querySelector('.carouselContainer');
const slides = carousel.querySelectorAll('.carouselSlider');
const slideBox = carousel.querySelector('ul#carouselBox');

const interval = slideBox.dataset.interval;
const counter = slideBox.dataset.counterType;
const mediaMedium = window.matchMedia( '( max-width: 800px )' );
const mediaSmall = window.matchMedia( '( max-width: 400px )' );
const slideLot = carousel.querySelector('.activeNumber');

const prevBtn = carousel.querySelector("#prevSlide");
const nextBtn = carousel.querySelector("#nextSlide");
const autoplayBtn = carousel.querySelector('#autoplaySlide');
const slideDots = carousel.querySelectorAll('.slideDots');

const pauseBtn = carousel.querySelector("#autoplaySlide > span.pauseBtn");
const playBtn = carousel.querySelector("#autoplaySlide > span.playBtn");
const dotBtn = carousel.querySelector('.slideDots');

let numSlide = slideBox.dataset.slide;
let infinite = slideBox.dataset.infiniteScroll;
let autoplay = slideBox.dataset.autoplay;
let timeout;
let curSlide = 0;

if (counter == 'number') {
slideLot.innerHTML = 1;
}

if (mediaSmall.matches) {
numSlide = 2;
}else if (mediaMedium.matches) {
numSlide = 3;
} else {
numSlide = slideBox.dataset.slide;
}

const slideWidth = Math.round(slideBox.clientWidth / numSlide);

prevBtn.addEventListener("click", prevFx);
nextBtn.addEventListener("click", nextFx);
goToSlide(0);

if(autoplay == 'true') {
autoPlay();
}else {
clearTimeout(timeout);
}

function autoPlay(numSlide){
nextFx();
timeout = setTimeout(autoPlay, interval);
}

function pauseFx() {
if (autoplay === 'true'){
clearTimeout(timeout);
autoplay = 'false';
playBtn.style.display = "block";
pauseBtn.style.display = "none";
}else{
if(curSlide == slides.length -1) {
curSlide = -1;
}
autoPlay();
autoplay = 'true';
playBtn.style.display = "none";
pauseBtn.style.display = "block";
}
}

function prevFx () {
curSlide--;
if(curSlide < 0){
if(infinite == 'true') {
curSlide = slides.length - 1;
} else {
curSlide = 0;
}
}
goToSlide(curSlide);
activeSlide(curSlide);
}

function nextFx () {
curSlide++
if(curSlide == slides.length) {
if(infinite == 'true') {
curSlide = 0;
}
if(autoplay == 'true') {
curSlide = 0;
}
}
goToSlide(curSlide);
activeSlide(curSlide);
}

function showDot(dot) {
const clickedDot = carousel.querySelector(`[data-dot-id="${dot}"]`);
slideDots.forEach(dot => dot.classList.remove('activeDot'));
clickedDot.classList.add('activeDot');
if(infinite == 'false') {
if(dot == slides.length - 1) {
nextBtn.setAttribute('disabled', true);
} else {
nextBtn.removeAttribute('disabled');
}
if(dot == 0) {
prevBtn.setAttribute('disabled', true);
} else {
prevBtn.removeAttribute('disabled');
}
}
curSlide = dot;
goToSlide(curSlide);
}

function goToSlide (slide) {
let leftScroll = slide * slideWidth;
slideBox.scrollTo({left: leftScroll});
}

function activeSlide(curSlide) {
if(infinite == 'false') {
if(curSlide == slides.length - 1) {
nextBtn.setAttribute('disabled', true);
} else {
nextBtn.removeAttribute('disabled');
}
if(curSlide == 0) {
prevBtn.setAttribute('disabled', true);
} else {
prevBtn.removeAttribute('disabled');
}
}
if(counter == 'number') {
slideLot.innerHTML = curSlide + 1;
}
if(counter == 'dots') {
showDot(curSlide);
}
}

 

7. Lastly, we have to link our .js file to the theme. Open the Layout folder and click the "theme.liquid". Before the </head>, paste the code below.

  <script src="{{ 'universal-carousel.js' | asset_url }}" defer="defer"></script>

 

You can change the color corresponds to the variables, see code in blue.

That is it. Let me know if you have questions (",)

Leave us a comment