Shopify Scrolling Logo Carousel Section (Copy, Paste, Customize)

A scrolling logo carousel is perfect for showcasing press logos, partner brands, payment methods, or featured clients. Done right, it’s lightweight, continuous (no janky gaps), responsive, and pauses on hover for readability.

Scrolling logo

Below is a pure HTML/CSS/JS implementation you can paste straight into Shopify. It auto-duplicates your logos so the strip loops seamlessly in either direction at any speed you choose.


This is an example implementation — replace the image URLs, branding, colors, speed, and direction to match your store.





Where to paste this in Shopify



  1. Go to Online Store → Themes → Customize
  2. Open the template (Home is typical)
  3. Add section → Custom HTML (or Custom Liquid)
  4. Paste the entire snippet below
  5. Save and preview






Copy-Paste Example — Scrolling Logo Carousel



Paste everything from <!DOCTYPE html> to </html>.

DESIGNED FOR REFERENCE ONLY
<body>


  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Shopify Scrolling Logo Carousel</title>
  <style>
    :root{
      /* Tweak these tokens to match your brand */
      --lc-bg: #0f1115;        /* section background */
      --lc-fg: #e8ecf1;        /* optional heading color */
      --lc-height: 84px;       /* logo rail height */
      --lc-gap: 32px;          /* space between logos */
      --lc-pad: 20px;          /* outer padding */
      --lc-grayscale: 0;       /* set to 1 for grayscale logos */
      --lc-opacity: 1;         /* lower to .8 if you want subtle look */
    }

    html,body{ margin:0; font-family: Arial, sans-serif; background:#0b0d12; color:var(--lc-fg); }

    .logo-carousel{
      background: var(--lc-bg);
      padding: var(--lc-pad) 0;
      overflow: hidden;
      position: relative;
    }

    /* Optional heading */
    .lc-head{
      max-width: 1200px;
      margin: 0 auto 8px;
      padding: 0 18px;
      display: flex; align-items: center; justify-content: space-between; gap: 12px;
    }
    .lc-title{ font-size: 18px; letter-spacing:.2px; opacity:.9; }

    /* Viewport for the moving track */
    .lc-viewport{
      position: relative;
      max-width: 1200px;
      margin: 0 auto;
      padding: 0 18px;
      overflow: hidden;
    }

    /* The moving track */
    .lc-track{
      display: flex;
      align-items: center;
      gap: var(--lc-gap);
      height: var(--lc-height);
      will-change: transform;
      filter: grayscale(var(--lc-grayscale));
      opacity: var(--lc-opacity);
    }

    .lc-item{
      flex: 0 0 auto;
      display: inline-flex; align-items: center; justify-content: center;
      height: 100%;
    }
    .lc-item img{
      height: 100%;
      width: auto;
      object-fit: contain;
      display: block;
      pointer-events: none;
      user-select: none;
    }

    /* Animation states */
    .lc-animate-left  { animation: lc-marquee-left  linear infinite; animation-duration: var(--lc-duration, 20s); }
    .lc-animate-right { animation: lc-marquee-right linear infinite; animation-duration: var(--lc-duration, 20s); }

    @keyframes lc-marquee-left  { 0% { transform: translateX(0); } 100% { transform: translateX(var(--lc-shift, -50%)); } }
    @keyframes lc-marquee-right { 0% { transform: translateX(var(--lc-shift, -50%)); } 100% { transform: translateX(0); } }

    /* Pause on hover */
    .logo-carousel.paused .lc-track{ animation-play-state: paused !important; }

    /* Subtle edges fade (optional). Remove if you want hard edges. */
    .lc-fade-left,
    .lc-fade-right{
      position: absolute; top: 0; bottom:0; width: 60px; pointer-events: none;
      background: linear-gradient(to right, var(--lc-bg), transparent);
      z-index: 2;
    }
    .lc-fade-right{
      right: 0; left: auto;
      background: linear-gradient(to left, var(--lc-bg), transparent);
    }

    /* Responsive tweaks */
    @media (max-width: 740px){
      :root{ --lc-height: 64px; --lc-gap: 24px; }
      .lc-title{ font-size: 16px; }
    }
    @media (max-width: 460px){
      :root{ --lc-height: 56px; --lc-gap: 20px; }
    }
  </style>


  <section class="logo-carousel" id="logoCarousel" data-speed="80" pixels per second>
           data-direction="left"     <!-- 'left' or 'right' -->
           data-duplicate="2"        <!-- how many times to duplicate logos (min 2) -->
           data-pause-on-hover="true">
    <div class="lc-head">
      <div class="lc-title">Trusted by teams and brands worldwide</div>
    </div>

    <div class="lc-viewport">
      <div class="lc-fade-left" aria-hidden="true"></div>
      <div class="lc-fade-right" aria-hidden="true"></div>

      <!-- Track: put ONE set of logos here; JS will duplicate for seamless loop -->
      <div class="lc-track" id="lcTrack">
        <!-- Replace these logo URLs with your own (transparent PNG/SVG recommended) -->
        <div class="lc-item"><img src="https://upload.wikimedia.org/wikipedia/commons/a/ab/Apple-logo.png" alt="Apple"></div>
        <div class="lc-item"><img src="https://upload.wikimedia.org/wikipedia/commons/4/44/Microsoft_logo.svg" alt="Microsoft"></div>
        <div class="lc-item"><img src="https://upload.wikimedia.org/wikipedia/commons/5/51/IBM_logo.svg" alt="IBM"></div>
        <div class="lc-item"><img src="https://upload.wikimedia.org/wikipedia/commons/a/a9/Amazon_logo.svg" alt="Amazon"></div>
        <div class="lc-item"><img src="https://upload.wikimedia.org/wikipedia/commons/2/2f/Google_2015_logo.svg" alt="Google"></div>
        <div class="lc-item"><img src="https://upload.wikimedia.org/wikipedia/commons/0/08/Spotify_logo_with_text.svg" alt="Spotify"></div>
        <div class="lc-item"><img src="https://upload.wikimedia.org/wikipedia/commons/0/02/YouTube_social_white_squircle_%282017%29.svg" alt="YouTube"></div>
        <div class="lc-item"><img src="https://upload.wikimedia.org/wikipedia/commons/1/19/PayPal.svg" alt="PayPal"></div>
      </div>
    </div>
  </section>

  <script>
    (function(){
      const root = document.getElementById('logoCarousel');
      const track = document.getElementById('lcTrack');
      if(!root || !track) return;

      const dir = (root.getAttribute('data-direction') || 'left').toLowerCase();
      const speed = Math.max(20, parseInt(root.getAttribute('data-speed') || '80', 10)); // px/sec
      const dup = Math.max(2, parseInt(root.getAttribute('data-duplicate') || '2', 10));
      const pauseOnHover = root.getAttribute('data-pause-on-hover') === 'true';

      // 1) Ensure we have enough logos duplicated to loop seamlessly
      function duplicateChildren(times){
        const children = Array.from(track.children);
        for(let t=1; t<times; t++){
          children.forEach(ch => track.appendChild(ch.cloneNode(true)));
        }
      }
      duplicateChildren(dup);

      // 2) Compute total width and set shift/duration
      function setup(){
        root.classList.remove('paused');
        track.classList.remove('lc-animate-left','lc-animate-right');

        // total width of all children
        const total = Array.from(track.children).reduce((w, el) => w + el.getBoundingClientRect().width, 0) + 
                      (getGap() * (track.children.length - 1));

        const half = total / dup; // one original set width
        const shift = -half; // move by one set for infinite loop
        const duration = Math.max(6, Math.min(half / speed, 120)); // seconds

        track.style.setProperty('--lc-shift', shift + 'px');
        track.style.setProperty('--lc-duration', duration + 's');
        track.classList.add(dir === 'right' ? 'lc-animate-right' : 'lc-animate-left');
      }

      function getGap(){
        const gapStr = getComputedStyle(track).gap || '0';
        return parseFloat(gapStr) || 0;
      }

      // 3) Pause on hover (optional)
      if(pauseOnHover){
        root.addEventListener('mouseenter', () => root.classList.add('paused'));
        root.addEventListener('mouseleave', () => root.classList.remove('paused'));
        // Touch pause (tap to pause; tap outside to resume)
        root.addEventListener('touchstart', () => root.classList.add('paused'), {passive:true});
        document.addEventListener('touchstart', (e)=>{
          if(!root.contains(e.target)) root.classList.remove('paused');
        }, {passive:true});
      }

      // 4) Responsive recalculation
      let resizeTimer;
      function onResize(){
        clearTimeout(resizeTimer);
        resizeTimer = setTimeout(setup, 150);
      }
      window.addEventListener('load', setup);
      window.addEventListener('resize', onResize);
      window.addEventListener('orientationchange', onResize);
    })();
  </script>

</body>


Customize it fast



  • Add logos: Replace the <img> URLs with your own (SVG or transparent PNG recommended). Keep similar heights for a clean rail.
  • Speed: Change data-speed="80" (px/sec). Lower = slower; higher = faster.
  • Direction: data-direction="left" or "right".
  • Duplication: data-duplicate="2" usually works; bump to 3 if you have very few logos to avoid visible repeats too quickly.
  • Height & spacing: tweak --lc-height and --lc-gap.
  • Look: set --lc-grayscale: 1 for monochrome brand wall; adjust --lc-opacity for subtlety.
  • Edges: remove .lc-fade-left/right if you prefer hard edges.






Accessibility & performance tips



  • Alt text: keep concise brand names in alt attributes.
  • Focus order: if logos are decorative, you can wrap them in plain <div> as shown (not links). If they should link, wrap each in <a> and ensure clear focus styles.
  • Image size: serve 2× retina-ready but compressed assets; SVGs are ideal.
  • Motion: you can disable hover-pause by setting data-pause-on-hover="false"; consider leaving it on for readability.






Troubleshooting



  • Jumps or gaps → Increase data-duplicate so at least one full set is always off-screen while the other scrolls in.
  • Too fast/slow → Adjust data-speed; the script recalculates the exact duration from total width.
  • Logos look uneven → Standardize image canvas heights; keep transparent padding consistent.
  • Stutters on very old devices → Lower the number of logos or reduce --lc-height and --lc-gap.