Welcome to our newly updated website!! We're also having our biggest sale yet! I'm still working on a few things so I apologize for the empty spaces or weirdness

Animated and Filterable Banners in Shopify

by Andreas Walters on November 14, 2024

Hey all!
I've been working in the Shopify ecosystem for a number of years now, and I've gotten to the point where I've learned enough about how to do and tweak things and of the things I've built, I would love to share with you, in case you want to use them too. This also serves as a little repository for myself when I have to go back and see the things I've changed that won't carry over when changing themes. 

So, for this first one, I was really inspired by one of banner selections on the Wizards of the Coast website, which had a sort of popup image above some brand text. So I wanted to do my own implementation. 
My vision was that I would have some sort of eye-catching art, and by rolling over you'd get a popup and more visibility on the logo. 

This led to the development of this prototype that was the basis of navigation through our site. 

(This example is an early HTML code I made, but serves as a good interactive example

Now, previsouly to make this happen we would just use the HTML-Box Section with the websites' customize editor.

This works, but its a pain in the butt to manage your code because you need to upload all three of your images first, and then link them to your new segment. Furthermore, if you wanted to reorganize your tiles, you had to drag those sections of code around. TLDR, it worked, but not really good for maintenance or quick changes/updates. 

Implementation as a Segment

So this was a relatively new thing for me, first you'll need to go to "Edit Code", you will find it by clicking the ellipsis next to the "customize" option.

then you'll need to create a new Section -> Liquid

File name is pretty important but for this example we'll just use everyhgin i;ve implementd, so name it MWG_dynamic.banners.liquid

then replace all the code with this

and thants all there really is to it, go ahead and save and then enter your customize editor. Make sure you're on the home page and click "+add section" and search for MWG. (we'll talk about making sections appear in pages next).

There you'll find ive exposed a lot of the settings so that you can adapt them as you see fit, using my own settings as defaults. 

Just to go over a few things:

This whole thing is enabled by tags. There is no way to code a dictionary of tags for you to look up, so you'll have to manually keep track of which tags you are using, and ensure that you link them to your buttons. 

Things to know

  • Structure:
    • BannerBox: container for all the boxes
    • Box: the individual box that elements are contained within
      • Background: image that covers the box, has roll-over effect
      • Logo: element within the box, has roll-over effect
      • Award element within the box, has roll-over effect
    • Fade-out Duration: time for boxes to dissapear when clicking a filter
    • Fade-out Delay: time to wait until new filter starts revealing new filter
    • Fade-in Delay between boxes: how long until the next box starts fading in.
    • Fade-in Duration: how long it takes for the box to fade in
  • CSS - this is the secret sauce of formatting with html, if you're unsure of anything, you can always 
    • vh/vw-, Viewport (height/width) - this essentially is a more elegant way of doing height / width %. You will see these everywhere, from determining gaps, to  
    • Box Dimensions (this is determined by Box Styling, you'll have to mess with it and see what works for you, 
    • z-index - this is how elements are layered, lowest is on the bottom, and the higher number is on the top
  • Buttons 
    • These are broken up by Label, Tag, and Row 1/2 (this was done to accomedate my old build of having two rows of filters (high level and specific, like systems supported).
    • Buttons can only support one tag, but gallery items can support multiple tags (which is honestly to me where a lot of the beauty of this kicks in).
    • CSS is fairly consistent, justify content: center, allows them to be centered, and gap 
  • Gallery Items (width x height in pixels): In order to have consistency in all of the elements, I created phtoshop templates using the following dimensions, as logo, popup, and award all rely on transparent elements, I used webp as my preferred output format (and being a little more optimzed for web-usage). I would also export as a whole, so they would consume the entire canvas with the alpha intact. 
    Note: You can obvioulsy use different dimensions, but this is at least a starting point with how you want elements to render. 
    • Background 542 x 800
    • Logo 1084 x 610
    • Popup - 542 x 644 
    • Award - 293 x 312
THE CODE

{%- assign item = section.settings -%}
<!-- Style Block -->
<style>
.bannerbox {
{{ section.settings.css_bannerbox }};
display: flex;
flex-wrap: wrap;
}

.box {
{{ section.settings.css_box }};
display: flex;
position: relative;
overflow: hidden;
opacity: 0; /* Initially hidden */
transition: opacity {{ section.settings.fade_in_duration }}ms ease,
transform {{ section.settings.fade_in_duration }}ms ease;
}

.box.visible {
opacity: 1;
transform: translateY(0);
}

.background {
{{ section.settings.css_background }};
object-fit: cover !important;
object-position: center !important;
width: 100% !important;
height: 100% !important;
-webkit-mask-image: radial-gradient(
circle at top left,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0.3) 10%,
rgba(0, 0, 0, 1) 30%
),
radial-gradient(
circle at top right,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0.3) 10%,
rgba(0, 0, 0, 1) 30%
),
radial-gradient(
circle at bottom left,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0.3) 10%,
rgba(0, 0, 0, 1) 30%
),
radial-gradient(
circle at bottom right,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0.3) 10%,
rgba(0, 0, 0, 1) 30%
);
mask-image: radial-gradient(
circle at top left,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0.3) 10%,
rgba(0, 0, 0, 1) 30%
),
radial-gradient(
circle at top right,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0.3) 10%,
rgba(0, 0, 0, 1) 30%
),
radial-gradient(
circle at bottom left,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0.3) 10%,
rgba(0, 0, 0, 1) 30%
),
radial-gradient(
circle at bottom right,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0.3) 10%,
rgba(0, 0, 0, 1) 30%
);
}
opacity: 0;
transition: mask-position {{ section.settings.fade_in_duration }}ms ease,
opacity {{ section.settings.fade_out_duration }}ms ease;
}

.box.visible .background {
opacity: 1;
-webkit-mask-position: 0%;
mask-position: 0%;
}

.box:hover .background {
{{ section.settings.css_background_hover }};
}

.logo {
{{ section.settings.css_logo }};
}

.box:hover .logo {
{{ section.settings.css_logo_hover }};
}

.popup {
{{ section.settings.css_popup }};
}

.box:hover .popup {
{{ section.settings.css_popup_hover }};
}

.award {
{{ section.settings.css_award }};
}

.box:hover .award {
{{ section.settings.css_award_hover }};
}
.filter-buttons {
{{ section.settings.css_filter_buttons }};
}

.filter-button.active {
{{ section.settings.css_filter_button_active }};
}
</style>


<!-- Filter Buttons - Row 1 -->
<div class="filter-buttons row-1" style="{{ section.settings.css_filter_buttons }}">
{% for block in section.blocks %}
{% if block.type == 'button' and block.settings.row == 'row1' %}
<button class="filter-button btn btn--secondary" data-tag="{{ block.settings.tag | downcase }}">
{{ block.settings.label }}
</button>
{% endif %}
{% endfor %}
</div>

<!-- Filter Buttons - Row 2 -->
<div class="filter-buttons row-2" style="{{ section.settings.css_filter_buttons }}">
{% for block in section.blocks %}
{% if block.type == 'button' and block.settings.row == 'row2' %}
<button class="filter-button btn btn--secondary" data-tag="{{ block.settings.tag | downcase }}">
{{ block.settings.label }}
</button>
{% endif %}
{% endfor %}
</div>


<!-- Gallery Container -->
<div class="bannerbox">
{% for block in section.blocks %}
{% if block.type == 'gallery_item' %}
<div class="box" data-tags="{{ block.settings.tags | downcase }}">
<a href="{{ block.settings.link }}">
<img class="background" src="{{ block.settings.background | img_url: 'master' }}" alt="Background">
<img class="logo" src="{{ block.settings.logo | img_url: 'medium' }}" alt="Logo">
<img class="popup" src="{{ block.settings.popup | img_url: 'medium' }}" alt="Popup">
<img class="award" src="{{ block.settings.award | img_url: 'small' }}" alt="Award">
</a>
</div>
{% endif %}
{% endfor %}
</div>

<script>
document.addEventListener('DOMContentLoaded', function() {
const buttons = document.querySelectorAll('.filter-button');
const boxes = document.querySelectorAll('.box');

const fadeOutDuration = {{ section.settings.fade_out_duration }};
const fadeOutDelay = {{ section.settings.fade_out_delay }};
const fadeInDelay = {{ section.settings.fade_in_delay }};
const fadeInDuration = {{ section.settings.fade_in_duration }};

function filterBoxes(tag) {
// Step 1: Fade out all boxes
boxes.forEach(box => {
box.style.transition = `opacity ${fadeOutDuration}ms ease, transform ${fadeOutDuration}ms ease`;
box.classList.remove('visible');
});

setTimeout(() => {
// Apply the filter
boxes.forEach(box => {
if (tag === 'all' || box.dataset.tags.includes(tag)) {
box.style.display = 'flex';
} else {
box.style.display = 'none';
}
});

// Step 2: Fade in the filtered boxes with a delay
const visibleBoxes = Array.from(boxes).filter(box => box.style.display === 'flex');
visibleBoxes.forEach((box, index) => {
setTimeout(() => {
const background = box.querySelector('.background');
box.classList.add('visible');
background.style.transition = `mask-position ${fadeInDuration}ms ease, opacity ${fadeInDuration}ms ease`;
background.style.maskPosition = '0%';
background.style.opacity = '1';
}, fadeInDelay * index);
});

}, fadeOutDuration + fadeOutDelay);
}

// Set up button click event listeners
buttons.forEach(button => {
button.addEventListener('click', function() {
const tag = this.getAttribute('data-tag');
buttons.forEach(btn => btn.classList.remove('active'));
this.classList.add('active');
filterBoxes(tag);
});
});

// Trigger the first button on load
if (buttons.length > 0) {
buttons[0].click();
}
});
</script>




{% schema %}
{
"name": "MWG Dynamic Banners",
"settings": [
{
"type": "textarea",
"id": "css_filter_buttons",
"label": "CSS for Filter Buttons Row 1",
"default": "display: flex; justify-content: center; gap: 1vh; margin-bottom: 1vh;"
},
{
"type": "textarea",
"id": "css_filter_button_active",
"label": "CSS for Active Filter Button",
"default": "background-color: #D89900; color: #fff; border: 1px solid #D89900;"
},
{
"type": "textarea",
"id": "css_bannerbox",
"label": "Bannerbox Styling",
"default": "justify-content: center; gap: 2vh; transition: 0.3s ease;"
},
{
"type": "textarea",
"id": "css_box",
"label": "Box Styling",
"default": "margin: 1vh; width: 30vh; height: 45vh; transition: opacity 0.4s ease; transition: transform 0.4s ease;"
},
{
"type": "textarea",
"id": "css_background",
"label": "Background Image Styling",
"default": "z-index: 1; filter: brightness(80%); filter: blur(0px); transition: 0.4s ease;"
},
{
"type": "textarea",
"id": "css_background_hover",
"label": "Background Image Hover Styling",
"default": "filter: brightness(60%); filter: blur(6px);"
},
{
"type": "textarea",
"id": "css_logo",
"label": "Logo Image Styling",
"default": "z-index: 3; object-position: center; position: absolute; bottom: -15%; left: 50%; transform: translate(-50%, -50%); opacity: 100%; transition: 0.3s ease; width: 85%; filter: brightness(90%);"
},
{
"type": "textarea",
"id": "css_logo_hover",
"label": "Logo Image Hover Styling",
"default": "opacity: 100%; width: 110%; filter: brightness(110%);"
},
{
"type": "textarea",
"id": "css_popup",
"label": "Popup Image Styling",
"default": "z-index: 2; object-position: center; position: absolute; bottom: -30%; left: 50%; transform: translate(-50%, -50%); opacity: 0%; transition: 0.4s ease; width: 85%; filter: brightness(90%);"
},
{
"type": "textarea",
"id": "css_popup_hover",
"label": "Popup Image Hover Styling",
"default": "bottom: -18%; opacity: 100%; width: 100%; filter: brightness(110%);"
},
{
"type": "textarea",
"id": "css_award",
"label": "Award Image Styling",
"default": "z-index: 3; object-position: center; position: absolute; top: -30%; right: 5%; opacity: 0%; transition: 0.4s ease; width: 35%;"
},
{
"type": "textarea",
"id": "css_award_hover",
"label": "Award Image Hover Styling",
"default": "top: -5%; opacity: 100%;"
},
{
"type": "range",
"id": "fade_out_duration",
"label": "Fade-Out Duration (ms)",
"default": 200,
"min": 100,
"max": 2000,
"step": 25
},
{
"type": "range",
"id": "fade_out_delay",
"label": "Fade-Out Delay (ms)",
"default": 50,
"min": 0,
"max": 1000,
"step": 25
},
{
"type": "range",
"id": "fade_in_delay",
"label": "Fade-In Delay between Boxes (ms)",
"default": 150,
"min": 50,
"max": 1000,
"step": 25
},
{
"type": "range",
"id": "fade_in_duration",
"label": "Fade-In Duration (ms)",
"default": 700,
"min": 100,
"max": 2000,
"step": 25
}

],
"blocks": [
{
"type": "gallery_item",
"name": "Gallery Item",
"settings": [
{
"type": "url",
"id": "link",
"label": "Link"
},
{
"type": "image_picker",
"id": "background",
"label": "Background Image"
},
{
"type": "image_picker",
"id": "logo",
"label": "Logo Image"
},
{
"type": "image_picker",
"id": "popup",
"label": "Popup Image"
},
{
"type": "image_picker",
"id": "award",
"label": "Award Image"
},
{
"type": "text",
"id": "tags",
"label": "Tags (comma separated)",
"default": "all"
}
]
},
{
"type": "button",
"name": "Filter Button",
"settings": [
{
"type": "text",
"id": "label",
"label": "Button Label",
"default": "Show All"
},
{
"type": "text",
"id": "tag",
"label": "Tag for Filtering",
"default": "all"
},
{
"type": "select",
"id": "row",
"label": "Row",
"default": "row1",
"options": [
{ "value": "row1", "label": "Row 1" },
{ "value": "row2", "label": "Row 2" }
]
}
]
}
],
"presets": [
{
"name": "MWG Dynamic Banners",
"category": "Custom"
}
]
}
{% endschema %}

LEAVE A COMMENT

Please note, comments must be approved before they are published


BACK TO TOP