Hero Banner Image Animation

By Nicholas Drishinski | Published

Adding a visually appealing hero banner to your Shopify store can significantly enhance the user experience and draw attention to your products. In this guide, we will walk you through the steps to create a hero banner with an animation that changes the hero image using Liquid, CSS, and JavaScript.

Code: https://github.com/ndrishinski/blogs/tree/master/homepage-banner-carousel-animation

Prefer Video?

Enjoy, oherwise read on!

Step 1: Create a New Section

  1. Log in to your Shopify Admin Panel.
  2. Navigate to Online Store > Themes.
  3. Click on Actions next to your current theme and select Edit code.
  4. In the Sections directory, click on Add a new section and name it new-banner.liquid.

Step 2: Add the Liquid Code

Paste the following code into your newly created file:


<style>
  #shopify-section-{{ section.id }} {
    position: relative;
    width: 100%;
    height: 100vh;
    overflow: hidden;
  }

  .escooter-hero-container {
    position: relative;
    width: 100%;
    height: 100vh;
    overflow: hidden;
  }

  /* Hero Image */
  .escooter-hero-image-container {
    position: absolute;
    inset: 0;
    z-index: 1;
  }

  .escooter-hero-image {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: opacity 1s ease;
  }

  @media (max-width: 768px) {
    .escooter-hero-image {
      object-fit: none;
    }

    .escooter-category-container {
      bottom: 0;
    }
  }

  .escooter-overlay {
    position: absolute;
    inset: 0;
    background-color: {{ section.settings.overlay_color }};
    z-index: 2;
  }

  /* Navigation Arrows */
  .escooter-nav-arrow {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 10;
    width: 48px;
    height: 48px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background-color: {{ section.settings.arrow_bg_color }};
    color: {{ section.settings.arrow_color }};
    border: none;
    cursor: pointer;
    transition: background-color 0.3s;
  }

  .escooter-nav-arrow:hover {
    background-color: {{ section.settings.arrow_hover_color }};
  }

  .escooter-nav-prev {
    left: 16px;
  }

  .escooter-nav-next {
    right: 16px;
  }

  /* Hero Content */
  .escooter-hero-content {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    color: {{ section.settings.text_color }};
    padding: 0 16px;
    z-index: 5;
  }

  .escooter-hero-content h1 {
    font-size: 3rem;
    font-weight: bold;
    letter-spacing: 2px;
    margin-bottom: 16px;
    transition: opacity 0.5s ease;
  }

  .escooter-hero-content p {
    font-size: 1.4rem;
    letter-spacing: 1px;
    margin-bottom: 32px;
    transition: opacity 0.5s ease;
  }

  .escooter-shop-btn {
    display: flex;
    align-items: center;
    gap: 8px;
    background-color: {{ section.settings.button_bg_color }};
    color: {{ section.settings.button_text_color }};
    font-weight: 500;
    padding: 12px 24px;
    border-radius: 9999px;
    text-decoration: none;
    transition: background-color 0.3s;
  }

  .escooter-shop-btn:hover {
    background-color: {{ section.settings.button_hover_color }};
  }

  /* Category Thumbnails */
  .escooter-category-container {
    position: absolute;
    bottom: 32px;
    left: 50%;
    transform: translateX(-50%);
    width: 100%;
    max-width: 1024px;
    padding: 0 16px;
    z-index: 10;
  }

  .escooter-category-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 8px;
  }

  .escooter-category-item {
    position: relative;
    overflow: hidden;
    border-radius: 8px;
    cursor: pointer;
  }

  .escooter-category-image {
    width: 100%;
    height: 96px;
    object-fit: cover;
    transition: transform 0.3s;
  }

  .escooter-category-item:hover .escooter-category-image {
    transform: scale(1.1);
  }

  .escooter-category-overlay {
    position: absolute;
    inset: 0;
    background-color: {{ section.settings.category_overlay_color }};
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .escooter-category-name {
    color: {{ section.settings.category_text_color }};
    font-size: 0.75rem;
    font-weight: 500;
    letter-spacing: 1px;
  }

  /* Progress bar styling */
  .escooter-progress-bar {
    position: absolute;
    bottom: 0;
    left: 0;
    width: 100%;
    height: 4px;
    background-color: rgba(0, 0, 0, 0.3);
    z-index: 2;
  }

  .escooter-progress {
    height: 100%;
    background-color: {{ section.settings.progress_bar_color }};
    width: 0%;
    transition: width 0.05s linear;
    display: block!important;
  }

  /* Image transition */
  .escooter-hero-image.fade-out {
    opacity: 0;
  }

  .escooter-hero-image.fade-in {
    opacity: 1;
  }

  /* Media Queries */
  @media (min-width: 768px) {
    .escooter-hero-content h1 {
      font-size: 4.5rem;
    }
    
    .escooter-hero-content p {
      font-size: 1.5rem;
    }
    
    .escooter-category-grid {
      grid-template-columns: repeat(4, 1fr);
      gap: 16px;
    }
    
    .escooter-category-image {
      height: 128px;
    }
    
    .escooter-category-name {
      font-size: 0.875rem;
    }
  }
</style>

<div class="escooter-hero-container" data-interval-time="{{ section.settings.interval_time }}">
  <!-- Hero Image -->
  <div class="escooter-hero-image-container" id="escooterHeroImageContainer">
    <!-- Hero images will be inserted here by JavaScript -->
  </div>
  <div class="escooter-overlay"></div>
  
  <!-- Navigation Arrows -->
  <button class="escooter-nav-arrow escooter-nav-prev" id="escooterPrevBtn">
    <i class="fas fa-chevron-left"></i>
  </button>
  
  <button class="escooter-nav-arrow escooter-nav-next" id="escooterNextBtn">
    <i class="fas fa-chevron-right"></i>
  </button>
  
  <!-- Hero Content -->
  <div class="escooter-hero-content">
    <h1 id="escooterHeroTitle"></h1>
    <p id="escooterHeroSubtitle"></p>
    <a href="{{ section.settings.button_link }}" class="escooter-shop-btn">
      {{ section.settings.button_text }} <i class="fas fa-arrow-right"></i>
    </a>
  </div>
  
  <!-- Category Thumbnails -->
  <div class="escooter-category-container">
    <div class="escooter-category-grid" id="escooterCategoryGrid">
      <!-- Categories will be inserted here by JavaScript -->
    </div>
  </div>
</div>

<script>
  document.addEventListener('DOMContentLoaded', () => {
    const sectionId = '{{ section.id }}';
    const container = document.querySelector(`#shopify-section-${sectionId} .escooter-hero-container`);
    
    if (!container) return;
    
    // Categories data
    const categories = [
      {% for block in section.blocks %}
        {
          id: {{ forloop.index0 }},
          name: {{ block.settings.category_name | json }},
          image: {{ block.settings.category_image | img_url: 'master' | json }},
          heroImage: {{ block.settings.hero_image | img_url: 'master' | json }},
          title: {{ block.settings.title | json }},
          subtitle: {{ block.settings.subtitle | json }}
        }{% unless forloop.last %},{% endunless %}
      {% endfor %}
    ];

    // DOM elements
    const heroImageContainer = container.querySelector('#escooterHeroImageContainer');
    const heroTitle = container.querySelector('#escooterHeroTitle');
    const heroSubtitle = container.querySelector('#escooterHeroSubtitle');
    const prevBtn = container.querySelector('#escooterPrevBtn');
    const nextBtn = container.querySelector('#escooterNextBtn');
    const categoryGrid = container.querySelector('#escooterCategoryGrid');

    // State
    let activeCategory = 0;
    let progress = 0;
    let intervalId = null;
    let currentHeroImage = null;
    let nextHeroImage = null;
    const intervalTime = parseInt(container.dataset.intervalTime) || 5000; // Default to 5 seconds
    const updateInterval = 50; // Update progress every 50ms
    const steps = intervalTime / updateInterval;

    // Initialize hero images
    function initHeroImages() {
      if (categories.length === 0) return;
      
      // Create initial hero image
      currentHeroImage = document.createElement('img');
      currentHeroImage.className = 'escooter-hero-image fade-in';
      currentHeroImage.src = categories[activeCategory].heroImage;
      currentHeroImage.alt = categories[activeCategory].name;
      heroImageContainer.appendChild(currentHeroImage);
      
      // Create next hero image (hidden initially)
      nextHeroImage = document.createElement('img');
      nextHeroImage.className = 'escooter-hero-image fade-out';
      nextHeroImage.style.opacity = 0;
      heroImageContainer.appendChild(nextHeroImage);
    }

    // Initialize categories
    function initCategories() {
      if (categories.length === 0) return;
      
      categoryGrid.innerHTML = '';
      
      categories.forEach((category, index) => {
        const categoryItem = document.createElement('div');
        categoryItem.className = `escooter-category-item`;
        categoryItem.dataset.index = index;
        
        categoryItem.innerHTML = `
          <img src="${category.image}" alt="${category.name}" class="escooter-category-image">
          <div class="escooter-category-overlay">
            <span class="escooter-category-name">${category.name}</span>
          </div>
          ${index === activeCategory ? '<div class="escooter-progress-bar"><div class="escooter-progress"></div></div>' : ''}
        `;
        
        categoryItem.addEventListener('click', () => {
          changeCategory(index);
        });
        
        categoryGrid.appendChild(categoryItem);
      });
      
      // Set initial hero content
      updateHeroContent();
    }

    // Update hero content based on active category
    function updateHeroContent() {
      if (categories.length === 0) return;
      
      const category = categories[activeCategory];
      
      // Update hero image with crossfade
      nextHeroImage.src = category.heroImage;
      nextHeroImage.alt = category.name;
      
      // Fade out current image, fade in next image
      currentHeroImage.style.opacity = 0;
      nextHeroImage.style.opacity = 1;
      
      // Swap references after transition
      setTimeout(() => {
        const temp = currentHeroImage;
        currentHeroImage = nextHeroImage;
        nextHeroImage = temp;
      }, 1000);
      
      // Update text content with a fade effect
      heroTitle.style.opacity = 0;
      heroSubtitle.style.opacity = 0;
      
      setTimeout(() => {
        heroTitle.textContent = category.title;
        heroSubtitle.textContent = category.subtitle;
        heroTitle.style.opacity = 1;
        heroSubtitle.style.opacity = 1;
      }, 500);
    }

    // Update category thumbnails
    function updateCategoryThumbnails() {
      const categoryItems = container.querySelectorAll('.escooter-category-item');
      
      categoryItems.forEach((item, index) => {
        // Remove progress bar from all items first
        const existingProgressBar = item.querySelector('.escooter-progress-bar');
        if (existingProgressBar) {
          item.removeChild(existingProgressBar);
        }
        
        // Add progress bar only to active category
        if (index === activeCategory) {
          const progressBar = document.createElement('div');
          progressBar.className = 'escooter-progress-bar';
          progressBar.innerHTML = '<div class="escooter-progress"></div>';
          item.appendChild(progressBar);
        }
      });
    }

    // Reset timer and progress
    function resetTimer() {
      if (intervalId) {
        clearInterval(intervalId);
      }
      progress = 0;
      startTimer();
    }

    // Start timer
    function startTimer() {
      intervalId = setInterval(() => {
        progress += (100 / steps);
        
        // Update progress bar
        const progressElement = container.querySelector('.escooter-category-item[data-index="' + activeCategory + '"] .escooter-progress');
        console.log('should be updating progress bar: ', progress, progressElement)
        
        if (progressElement) {
          progressElement.style.width = `${progress}%`;
        }
        
        // Move to next category when progress reaches 100%
        if (progress >= 100) {
          nextCategory();
        }
      }, updateInterval);
    }

    // Change to specific category
    function changeCategory(index) {
      activeCategory = index;
      progress = 0;
      
      updateHeroContent();
      updateCategoryThumbnails();
      resetTimer();
    }

    // Navigate to previous category
    function prevCategory() {
      const newIndex = activeCategory === 0 ? categories.length - 1 : activeCategory - 1;
      changeCategory(newIndex);
    }

    // Navigate to next category
    function nextCategory() {
      const newIndex = activeCategory === categories.length - 1 ? 0 : activeCategory + 1;
      changeCategory(newIndex);
    }

    // Event listeners
    prevBtn.addEventListener('click', prevCategory);
    nextBtn.addEventListener('click', nextCategory);

    // Initialize
    initHeroImages();
    initCategories();
    startTimer();
  });
</script>

{% schema %}
{
  "name": "New Hero Banner",
  "tag": "section",
  "class": "escooter-hero-section",
  "settings": [
    {
      "type": "color",
      "id": "text_color",
      "label": "Text Color",
      "default": "#ffffff"
    },
    {
      "type": "color",
      "id": "overlay_color",
      "label": "Overlay Color",
      "default": "rgba(0, 0, 0, 0.3)"
    },
    {
      "type": "color",
      "id": "button_bg_color",
      "label": "Button Background Color",
      "default": "#7de2e2"
    },
    {
      "type": "color",
      "id": "button_text_color",
      "label": "Button Text Color",
      "default": "#000000"
    },
    {
      "type": "color",
      "id": "button_hover_color",
      "label": "Button Hover Color",
      "default": "#6cd0d0"
    },
    {
      "type": "color",
      "id": "arrow_bg_color",
      "label": "Arrow Background Color",
      "default": "rgba(0, 0, 0, 0.5)"
    },
    {
      "type": "color",
      "id": "arrow_color",
      "label": "Arrow Color",
      "default": "#ffffff"
    },
    {
      "type": "color",
      "id": "arrow_hover_color",
      "label": "Arrow Hover Color",
      "default": "rgba(0, 0, 0, 0.7)"
    },
    {
      "type": "color",
      "id": "category_overlay_color",
      "label": "Category Overlay Color",
      "default": "rgba(0, 0, 0, 0.4)"
    },
    {
      "type": "color",
      "id": "category_text_color",
      "label": "Category Text Color",
      "default": "#ffffff"
    },
    {
      "type": "color",
      "id": "progress_bar_color",
      "label": "Progress Bar Color",
      "default": "#7de2e2"
    },
    {
      "type": "text",
      "id": "button_text",
      "label": "Button Text",
      "default": "Shop Now"
    },
    {
      "type": "url",
      "id": "button_link",
      "label": "Button Link"
    },
    {
      "type": "range",
      "id": "interval_time",
      "min": 3000,
      "max": 9000,
      "step": 1000,
      "unit": "ms",
      "label": "Slide Interval",
      "default": 5000
    }
  ],
  "blocks": [
    {
      "type": "category",
      "name": "Category",
      "limit": 4,
      "settings": [
        {
          "type": "text",
          "id": "category_name",
          "label": "Category Name",
          "default": "Category"
        },
        {
          "type": "image_picker",
          "id": "category_image",
          "label": "Category Thumbnail"
        },
        {
          "type": "image_picker",
          "id": "hero_image",
          "label": "Hero Background Image"
        },
        {
          "type": "text",
          "id": "title",
          "label": "Title",
          "default": "DOUBLE THE POWER"
        },
        {
          "type": "textarea",
          "id": "subtitle",
          "label": "Subtitle",
          "default": "OUR DUAL MOTOR ESCOOTERS BRING RIDES TO THE NEXT LEVEL."
        }
      ]
    }
  ],
  "presets": [
    {
      "name": "New Hero Banner",
      "blocks": [
        {
          "type": "category",
          "settings": {
            "category_name": "SPRING SALE",
            "title": "DOUBLE THE POWER",
            "subtitle": "OUR DUAL MOTOR ESCOOTERS BRING RIDES TO THE NEXT LEVEL."
          }
        },
        {
          "type": "category",
          "settings": {
            "category_name": "ELECTRIC BIKES",
            "title": "ELECTRIC BIKES",
            "subtitle": "EXPLORE FURTHER WITH OUR PREMIUM ELECTRIC BIKES."
          }
        },
        {
          "type": "category",
          "settings": {
            "category_name": "DUAL MOTOR ESCOOTERS",
            "title": "DUAL MOTOR ESCOOTERS",
            "subtitle": "EXPERIENCE UNMATCHED POWER AND CONTROL."
          }
        },
        {
          "type": "category",
          "settings": {
            "category_name": "POWERBOARDS",
            "title": "POWERBOARDS",
            "subtitle": "REDEFINE YOUR URBAN COMMUTE WITH CUTTING-EDGE TECHNOLOGY."
          }
        }
      ]
    }
  ]
}
{% endschema %}

Then open the 'theme.liquid' file and within the <head> section paste the following link:

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">

This code imports the font awesome library for icons!

Explanation of code

  • CSS Styles: The <style> section contains styles for the hero banner, including layout, colors, and animations.
  • HTML Structure: The main structure includes a container for the hero image, navigation arrows, and content (title, subtitle, and button).
  • JavaScript: The script handles the dynamic behavior of the hero banner, including image transitions and category updates.

Step 3: Add and Customize Your Section Settings

Now with our new file saved we can open up the theme editor and add our new section! It is named 'New Hero Banner'.

After adding your section you will have the ability to edit the style settings and add blocks which represent each category and image.

Conclusion

Congratulations! You have successfully added a hero banner with animation to your Shopify store. This feature not only enhances the visual appeal of your site but also helps in promoting your products effectively. Feel free to experiment with different styles and settings to make it uniquely yours!

Nick Drishinski

Nick Drishinski is a Senior Software Engineer with over 5 years of experience specializing in Shopify development. When not programming Nick is often creating development tutorials, blogs and courses or training for triathlons.